diff options
author | Karl 'vollmerk' Vollmer <vollmer@ampache.org> | 2005-06-09 16:34:40 +0000 |
---|---|---|
committer | Karl 'vollmerk' Vollmer <vollmer@ampache.org> | 2005-06-09 16:34:40 +0000 |
commit | bcad40a05ab2dc2a341a3227e30b96668bce4500 (patch) | |
tree | 6fca27588d53a1b24705bd2834e9e643bb729bd1 | |
download | ampache-bcad40a05ab2dc2a341a3227e30b96668bce4500.tar.gz ampache-bcad40a05ab2dc2a341a3227e30b96668bce4500.tar.bz2 ampache-bcad40a05ab2dc2a341a3227e30b96668bce4500.zip |
New Import
326 files changed, 86336 insertions, 0 deletions
diff --git a/admin/access.php b/admin/access.php new file mode 100644 index 00000000..31793907 --- /dev/null +++ b/admin/access.php @@ -0,0 +1,85 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +require('../modules/init.php'); + + +/* Scrub in the Needed vars */ +$action = scrub_in($_REQUEST['action']); +$access_id = scrub_in($_REQUEST['access_id']); +$access = new Access($access_id); + +if (!$user->has_access(100)) { + header("Location: http://" . conf('web_path') . "/index.php?access=denied"); + exit(); +} + + +show_template('header'); + +show_menu_items('Admin'); +show_admin_menu('Access Lists'); +show_clear(); +if ( $action == 'show_confirm_delete' ) { + show_confirm_action(_("Do you really want to delete this Access Record?"), "admin/access.php", "access_id=" . $_REQUEST['access_id'] . "&action=delete_host"); +} +/*! + @action delete_host + @discussion deletes an access list entry +*/ +elseif ( $action == 'delete_host' ) { + $access->delete($_REQUEST['access_id']); + show_confirmation(_("Entry Deleted"),_("Your Access List Entry has been removed"),"admin/access.php"); + +} // delete_host +/*! + @action add_host + @discussion add a new access list entry +*/ +elseif ($action == 'add_host') { + + $access->create($_REQUEST['name'], $_REQUEST['start'],$_REQUEST['end'],$_REQUEST['level']); + show_confirmation(_("Entry Added"),_("Your new Access List Entry has been created"),"admin/access.php"); + +} // add_host +/*! + @action show_add_host + @discussion show the add host box +*/ +elseif ( $action == 'show_add_host' ) { + include(conf('prefix') . "/templates/show_add_access.inc"); +} +else { + $list = array(); + $list = $access->get_access_list(); + include(conf('prefix') ."/templates/show_access_list.inc"); +} +echo "<br /><br />"; + +show_admin_menu('Access Lists'); +show_menu_items('Admin'); + +?> + + +</body> +</html> diff --git a/admin/album.php b/admin/album.php new file mode 100644 index 00000000..7b8751f0 --- /dev/null +++ b/admin/album.php @@ -0,0 +1,114 @@ +<?php + +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Admin Album Mojo + Update the album information for the site. + +*/ + +require('../modules/init.php'); + + +if (!$user->has_access(100)) { + header("Location:" . conf('web_path') . "/index.php?access=denied"); + exit(); +} + + +if ( $action == 'Change Name' ) { + + update_album_name($album, $new_name); + + if ( $update_tag ) { + // get songs associated with this + $songs = get_songs_from_album($album); + + // run update_local_mp3 + $total_updated = update_local_mp3($new_name, 'album', $songs); + $update_text = "Updated the database and $total_updated local files."; + } + + // set the action to view so everybody can see the changes + $action = 'View'; +} + +show_template('header'); + +show_menu_items('Admin'); +show_admin_menu('Catalog'); + +?> + +<p>Use this form to change the name(s) of albums in the database. In order to update your +local MP3's your Apache user must have write-permission to your MP3's.</p> + +<form name="album" method="post" action="album.php"> +<table> + <tr> + <td>Select Album:</td> + <td> <?php show_album_pulldown($album) ?> </td> + <td> <input type=submit name=action value=View> </td> + </tr> +</table> +</form> + +<hr> + +<?php + +// if album exists then show some info +if ( $album and $action == 'View' ) { + $album_name = get_album_name($album); + +?> + +<p style="color: red;"><?= $update_text ?></p> + +<form name="album_change" method=post action="album.php"> + <table> + <tr> + <td>Album Name:</td> + <td><input type=text name="new_name" value="<?= $album_name ?>" size="50"></td> + <td> </td> + <td><input type=submit name=action value="Change Name"></td> + <tr> + <td> </td> <td><input type="checkbox" name="update_tag"> + Update MP3 tag <b>Note: this will only modify your local MP3's</b> + </td> + </tr> + </table> + <input type=hidden name=album value="<?= $album ?>"> +</form> + +<?php + + $song_ids = get_song_ids_from_album($album); + show_songs($song_ids, 0); +} + +show_footer(); +?> + +</body> +</html> diff --git a/admin/artist.php b/admin/artist.php new file mode 100644 index 00000000..ebec5db6 --- /dev/null +++ b/admin/artist.php @@ -0,0 +1,121 @@ +<?php + +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Admin Artist page + Update the artist information for the site. + +*/ + +require('../modules/init.php'); + + +if (!$user->has_access(100)) { + header("Location:". conf('web_path') . "/index.php?access=denied"); + exit(); +} + +$dbh = dbh(); + +if ( $action == 'Change Name' ) { + if ( $settings[demo_mode] == 'false' && $username != $settings[demo_user] ) { + $old_artist_name = get_artist_name($artist); + update_artist_name($artist, $new_name); + + if ( $update_tag ) { + // get songs associated with this + $song_ids = get_song_ids_from_artist($artist); + + // run update_local_mp3 + $total_updated = update_local_mp3($new_name, 'artist', $song_ids); + $update_text = "Updated $old_artist_name to $new_name and $total_updated local files."; + } + else { + $update_text = "Updated $old_artist_name to $new_name."; + } + + // set the action to view so everybody can see the changes + $action = 'View'; + } +} + +show_template('header'); + +show_menu_items(".."); +show_admin_menu('Catalog'); + +?> + +<p>Use this form to change the name(s) of artists in the database. In order to update your +local MP3's your Apache user must have write-permission to your MP3's.</p> + +<form name="artist" method="post" action="artist.php"> +<table> + <tr> + <td>Select Artist:</td> + <td> <?php show_artist_pulldown($artist) ?> </td> + <td> <input type=submit name=action value=View> </td> + </tr> +</table> +</form> + +<hr> + +<?php + +// if artist exists then show some info +if ( $artist and $action == 'View' ) { + $sql = "SELECT name FROM artist WHERE id='$artist'"; + $db_result = mysql_query($sql, $dbh); + + $r = mysql_fetch_row($db_result); + $artist_name = $r[0]; + +?> + +<p style="color: red;"><?= $update_text ?></p> + +<form name="artist_change" method=post action="artist.php"> + <table> + <tr> + <td>Artist Name:</td> <td><input type=text name="new_name" value="<?= $artist_name ?>" size="50"></td> + <td> </td> <td><input type=submit name=action value="Change Name"></td> + </tr> + <tr> + <td> </td> <td><input type="checkbox" name="update_tag"> + Update MP3 tag <b>Note: this will only modify your local MP3's</b> + </td> + </tr> + </table> + <input type="hidden" name="artist" value="<?= $artist ?>"> +</form> + +<?php + + show_albums_for_artist($artist); +} + +?> + +</body> +</html> diff --git a/admin/catalog.php b/admin/catalog.php new file mode 100644 index 00000000..d3c4bc6d --- /dev/null +++ b/admin/catalog.php @@ -0,0 +1,282 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +/*! + @header Admin Catalog + This document handles actions for catalog creation and passes them off to the catalog class +*/ + +require('../modules/init.php'); + +if (!$user->has_access(100)) { + access_denied(); +} + + +/* Set any vars we are going to need */ +$catalog = new Catalog($_REQUEST['catalog_id']); + +show_template('header'); + +/* Generate the menus */ +show_menu_items('Admin'); +show_admin_menu('Catalog'); +show_clear(); + + +/* Big switch statement to handle various actions */ +switch ($_REQUEST['action']) { + case 'fixed': + delete_flagged($flag); + $type = 'show_flagged_songs'; + include(conf('prefix') . '/templates/flag.inc'); + break; + + case _("Add to Catalog(s)"): + if (conf('demo_mode')) { break; } + if ($_REQUEST['catalogs'] ) { + foreach ($_REQUEST['catalogs'] as $catalog_id) { + $catalog = new Catalog($catalog_id); + $catalog->add_to_catalog($_REQUEST['update_type']); + } + } + include(conf('prefix') . '/templates/catalog.inc'); + break; + + case _("Add to all Catalogs"): + if (conf('demo_mode')) { break; } + $catalogs = $catalog->get_catalogs(); + + foreach ($catalogs as $data) { + $data->add_to_catalog($_REQUEST['update_type']); + } + include(conf('prefix') . '/templates/catalog.inc'); + break; + + case _("Update Catalog(s)"): + if (conf('demo_mode')) { break; } + if (isset($_REQUEST['catalogs'])) { + foreach ($_REQUEST['catalogs'] as $catalog_id) { + $catalog = new Catalog($catalog_id); + $catalog->verify_catalog($catalog_id->id,$_REQUEST['update_type']); + } + } + include(conf('prefix') . '/templates/catalog.inc'); + break; + + case _("Update All Catalogs"): + if (conf('demo_mode')) { break; } + $catalogs = $catalog->get_catalogs(); + + foreach ($catalogs as $data) { + $data->verify_catalog($data->id,$_REQUEST['update_type']); + } + include(conf('prefix') . '/templates/catalog.inc'); + break; + + case 'delete_catalog': + if (conf('demo_mode')) { break; } + if ($_REQUEST['confirm'] === 'Yes') { + $catalog = new Catalog($_REQUEST['catalog_id']); + $catalog->delete_catalog(); + } + include(conf('prefix') . '/templates/catalog.inc'); + break; + + case 'remove_disabled': + if (conf('demo_mode')) { break; } + $song = $_REQUEST['song']; + if (count($song)) { + $catalog->remove_songs($song); + echo "<p align=\"center\">Songs Removed... </p>"; + } + else { + echo "<p align=\"center\">No Songs Removed... </p>"; + } + include(conf('prefix') . '/templates/catalog.inc'); + break; + + case _("Clean Catalog(s)"): + if (conf('demo_mode')) { break; } + + // Make sure they checked something + if (isset($_REQUEST['catalogs'])) { + foreach($_REQUEST['catalogs'] as $catalog_id) { + $catalog = new Catalog($catalog_id); + $catalog->clean_catalog(0,$_REQUEST['update_type']); + } // end foreach catalogs + } + include(conf('prefix') . '/templates/catalog.inc'); + break; + case 'update_catalog_settings': + if (conf('demo_mode')) { break; } + $id = strip_tags($_REQUEST['catalog_id']); + $name = strip_tags($_REQUEST['name']); + $id3cmd = strip_tags($_REQUEST['id3_set_command']); + $rename = strip_tags($_REQUEST['rename_pattern']); + $sort = strip_tags($_REQUEST['sort_pattern']); + /* Setup SQL */ + $sql = "UPDATE catalog SET " . + " name = '$name'," . + " id3_set_command = '$id3cmd'," . + " rename_pattern = '$rename'," . + " sort_pattern = '$sort'" . + " WHERE id = '$id'"; + $result = mysql_query($sql, dbh()); + include(conf('prefix') . '/templates/catalog.inc'); + break; + + case _("Clean All Catalogs"): + if (conf('demo_mode')) { break; } + $catalogs = $catalog->get_catalogs(); + $dead_files = array(); + + foreach ($catalogs as $catalog) { + $catalog->clean_catalog(0,$_REQUEST['update_type']); + } + + include(conf('prefix') . '/templates/catalog.inc'); + break; + case 'add_catalog': + if (conf('demo_mode')) { break; } + if ($_REQUEST['path'] AND $_REQUEST['name']) { + /* Throw all of the album art types into an array */ + $art = array('id3'=>$_REQUEST['art_id3v2'],'amazon'=>$_REQUEST['art_amazon'],'folder'=>$_REQUEST['art_folder']); + /* Create the Catalog */ + $catalog->new_catalog($_REQUEST['path'], + $_REQUEST['name'], + $_REQUEST['id3set_command'], + $_REQUEST['rename_pattern'], + $_REQUEST['sort_pattern'], + $_REQUEST['type'], + $_REQUEST['gather_art'], + $_REQUEST['parse_m3u'], + $art); + include(conf('prefix') . '/templates/catalog.inc'); + } + else { + $error = "Please complete the form."; + include(conf('prefix') . '/templates/add_catalog.inc'); + } + break; + + case 'really_clear_stats': + if (conf('demo_mode')) { break; } + if ($_REQUEST['confrim'] == 'Yes') { + clear_catalog_stats(); + } + include(conf('prefix') . '/templates/catalog.inc'); + break; + + case 'show_add_catalog': + include(conf('prefix') . '/templates/add_catalog.inc'); + break; + + case 'clear_now_playing': + if (conf('demo_mode')) { break; } + clear_now_playing(); + show_confirmation(_("Now Playing Cleared"),_("All now playing data has been cleared"),"/admin/catalog.php"); + + break; + case 'Clear Catalog': + if (conf('demo_mode')) { break; } + show_confirm_action(_("Do you really want to clear your catalog?"), + "/admin/catalog.php", "action=really_clear_catalog"); + print("<hr>\n"); + break; + + case 'clear_stats': + if (conf('demo_mode')) { break; } + show_confirm_action(_("Do you really want to clear the statistics for this catalog?"), + "/admin/catalog.php", "action=really_clear_stats"); + print("<hr>\n"); + break; + + case 'show_disabled': + if (conf('demo_mode')) { break; } + $songs = $catalog->get_disabled(); + if (count($songs)) { + require (conf('prefix') . '/templates/show_disabled_songs.inc'); + } + else { + echo "<p class=\"error\" align=\"center\">No Disabled songs found</p>"; + } + break; + + case 'show_delete_catalog': + if (conf('demo_mode')) { break; } + show_confirm_action(_("Do you really want to delete this catalog?"), + "admin/catalog.php", + "catalog_id=" . $_REQUEST['catalog_id'] . "&action=delete_catalog"); + break; + + case 'show_flagged_songs': + if (conf('demo_mode')) { break; } + $type = $_REQUEST['action']; + include (conf('prefix') . '/templates/flag.inc'); + break; + + case 'Update Flags': + if (conf('demo_mode')) { break; } + echo "<pre>"; + print_r($_REQUEST); + echo "</pre>"; + break; + + case 'show_customize_catalog': + include(conf('prefix') . '/templates/customize_catalog.inc'); + break; + case 'gather_album_art': + + echo "<b>" . _("Starting Album Art Search") . ". . .</b><br /><br />\n"; + flush(); + + $catalogs = $catalog->get_catalogs(); + foreach ($catalogs as $data) { + $data->get_album_art(); + } + + echo "<b>" . _("Album Art Search Finished") . ". . .</b><br />\n"; + + break; + // (Added by Cucumber 20050216) + case 'dump_album_art': + $catalogs = $catalog->get_catalogs(); + + foreach ($catalogs as $data) { + $data->dump_album_art(); + } + break; + + default: + include(conf('prefix') . '/templates/catalog.inc'); + +} // end switch +echo "<br /><br />"; +show_admin_menu('Catalog'); +show_menu_items('Admin'); + +?> + +</body> +</html> diff --git a/admin/duplicates.php b/admin/duplicates.php new file mode 100644 index 00000000..739472c8 --- /dev/null +++ b/admin/duplicates.php @@ -0,0 +1,59 @@ +<?php + +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +// Allows users to search for duplicate songs in their catalogs + +require_once ("../modules/init.php"); +require_once( conf('prefix').'/lib/duplicates.php'); + + +if (!$user->has_access(100)) { + header ("Location: " . conf('web_path') . "/index.php?access=denied"); + exit(); +} + +$action = scrub_in($_REQUEST['action']); +$search_type = scrub_in($_REQUEST['search_type']); + +show_template('header'); + +show_menu_items('Admin'); +show_admin_menu('Users'); + + +switch ($action) +{ + case 'search': + $flags = get_duplicate_songs($search_type); + show_duplicate_songs($flags,$search_type); + break; + default: + show_duplicate_searchbox($search_type); +} + +show_footer(); +?> + +</body> +</html> diff --git a/admin/flags.php b/admin/flags.php new file mode 100644 index 00000000..f7965d03 --- /dev/null +++ b/admin/flags.php @@ -0,0 +1,91 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +/*! + @header Flags Mojo +*/ + +require_once ("../modules/init.php"); +require_once( conf('prefix').'/lib/flag.php'); + +if (!$user->has_access(100)) { + header ("Location: " . conf('web_path') . "/index.php?access=denied"); + exit(); +} + + +$action = scrub_in($_REQUEST['action']); +show_template('header'); + +show_menu_items('Admin'); +show_admin_menu('Users'); + +switch ($action) +{ + case 'show': + $flags = get_flagged_songs(); + show_flagged_songs($flags); + break; + case 'Set Flags': + case 'Update Flags': + $flags = scrub_in($_REQUEST['song']); + update_flags($flags); + $newflags = get_flagged_songs(); + show_flagged_songs($newflags); + break; + case 'Edit Selected': + $flags = scrub_in($_REQUEST['song']); + $count = add_to_edit_queue($flags); + if($count) show_edit_flagged(); + break; + case 'Next': + $song = scrub_in($_REQUEST['song']); + update_song_info($song); + show_edit_flagged(); + // Pull song ids from an edit queue in $_SESSION, + // And edit them one at a time + break; + case 'Skip': + $count = add_to_edit_queue(scrub_in($_REQUEST['song'])); + if($count) show_edit_flagged(); + case 'Flag Songs': + break; + case 'Remove Flags': + break; + case 'Clear Edit List': + unset($_SESSION['edit_queue']); + + case 'Done': + $song = scrub_in($_REQUEST['song']); + update_song_info($song); + default: + $flags = get_flagged_songs(); + show_flagged_songs($flags); + +} + +show_footer(); +?> + +</body> +</html> diff --git a/admin/index.php b/admin/index.php new file mode 100644 index 00000000..a830897a --- /dev/null +++ b/admin/index.php @@ -0,0 +1,96 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Admin Index + Do most of the dirty work of displaying the mp3 catalog + +*/ + +require ("../modules/init.php"); + +$action = scrub_in($_REQUEST['action']); + +if (!$user->has_access(100)) { + header ("Location: " . conf('web_path') . "/index.php?access=denied"); + exit(); +} + + +// let's set the preferences here so that they take affect on the fly +if ( $action == 'Update Preferences' ) { + update_site_preferences($preferences_id, 'true', $new_m_host, $new_w_host, + $new_site_title, $new_login_message, $new_session_lifetime, $new_font, + $new_background_color, $new_primary_color, $new_secondary_color, + $new_primary_font_color, $new_secondary_font_color, + $new_error_color, $new_popular_threshold); + // reload the preferences now + set_preferences(); +} + +show_template('header'); +show_menu_items('Admin'); + +if ( $action == 'show_site_preferences' ) { + show_admin_menu('Site Preferences'); +} +elseif ( ($action == 'show_users') || ($action == 'show_new_user')) { + show_admin_menu('Users'); +} +elseif ( $action == 'show_update_catalog' ) { + show_admin_menu('Catalog'); +} +else { + show_admin_menu('...'); +} + +if ( $action == 'Update Preferences' ) { + $action = 'show_preferences'; +} +elseif ( $action == 'show_update_catalog' ) { + show_update_catalog(); +} +elseif ( $action == 'show_file_manager' ) { + show_file_manager(); +} +elseif ( $action == 'show_site_preferences' ) { + $user = new User(0); + require (conf('prefix') . "/templates/show_preferences.inc"); +} +elseif ( $action == 'show_preferences' ) { + $user = new User($_REQUEST['user_id']); + require (conf('prefix') . "/templates/show_preferences.inc"); +} +elseif ( $action == 'show_orphaned_files' ) { + show_orphaned_files(); +} +else { + require (conf('prefix') . "/templates/show_admin_index.inc"); +} // if they didn't pick anything + +echo "<br /><br />"; +show_admin_menu(''); +show_menu_items('Admin'); +?> + +</body> +</html> diff --git a/admin/mail.php b/admin/mail.php new file mode 100644 index 00000000..d5aef413 --- /dev/null +++ b/admin/mail.php @@ -0,0 +1,139 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Mail Admin Page + Means to mail your users or give updates about the server + +*/ + +require('../modules/init.php'); + +if (!$user->has_access(100)) { + access_denied(); +} + + +$action = scrub_in($_POST['action']); +$to = scrub_in($_REQUEST['to']); +$subject = stripslashes(scrub_in($_POST['subject'])); +$message = stripslashes(scrub_in($_POST['message'])); + +if ( $action == 'send_mail' && !conf('demo_mode')) { + $user = new User(0,$_SESSION['userdata']['id']); + // do the mail mojo here + if ( $to == 'all' ) { + $sql = "SELECT * FROM user WHERE email IS NOT NULL"; + } + elseif ( $to == 'users' ) { + $sql = "SELECT * FROM user WHERE access='users' AND email IS NOT NULL"; + } + elseif ( $to == 'admins' ) { + $sql = "SELECT * FROM user WHERE access='admin' AND email IS NOT NULL"; + } + + $db_result = mysql_query($sql, dbh()); + + $recipient = ''; + + while ( $u = mysql_fetch_object($db_result) ) { + $recipient .= "$u->fullname <$u->email>, "; + } + + // Remove the last , from the recipient + $recipient = rtrim($recipient,","); + + $from = $user->fullname."<".$user->email.">"; + + // woohoo!! + mail ($from, $subject, $message, + "From: $from\r\n". + "Bcc: $recipient\r\n"); + + // tell them that it was sent + $complete_text = "Your message was successfully sent."; +} + +if ( empty($to) ) { + $to = 'all'; +} + +if ( empty($subject) ) { + $site_title = conf('site_title'); + $subject = "[$site_title] "; +} + +show_template('header'); + +show_menu_items('Admin'); +show_admin_menu('Mail Users'); +show_clear(); +?> + +<form name="mail" method="post" action="<?php echo conf('web_path'); ?>/admin/mail.php" enctype="multipart/form-data"> + +<p><font color="<?php echo $error_color; ?>"><?php echo $complete_text; ?></font></p> + +<table> + <tr> + <td><?php echo _("Mail to"); ?>:</td> + <td> + <select name="to"> + <option value="all" <?php if ($to == 'all') { echo "SELECTED"; } ?>>All</option> + <option value="users" <?php if ($to == 'user') { echo "SELECTED"; } ?>>Users</option> + <option value="admins" <?php if ($to == 'admin') { echo "SELECTED"; } ?>>Admins</option> + </select> + </td> + </tr> + + <tr> + <td><?php echo _("Subject"); ?>:</td> + <td> + <input name="subject" value="<?php echo $_POST['subject']; ?>" size="50"></input> + </td> + </tr> + + <tr> + <td valign="top"><?php echo _("Message"); ?>:</td> + <td> + <textarea class="input" name="message" rows="20" cols="70"><?php echo $message; ?></textarea> + </td> + </tr> + + <tr> + <td> </td> + <td> + <input type="hidden" name="action" value="send_mail" /> + <input type="submit" value="<?php echo _("Send Mail"); ?>" /> + </td> + </tr> +</table> + +</form> +<br /><br /> +<?php + show_admin_menu('Mail Users'); + show_menu_items('Admin'); +?> + +</body> +</html> diff --git a/admin/orphan.php b/admin/orphan.php new file mode 100644 index 00000000..dfeee13b --- /dev/null +++ b/admin/orphan.php @@ -0,0 +1,71 @@ +<?php + +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +/*! + @header Orphaned Admin Page + View and edit orphan files + +*/ + +require('../modules/init.php'); + + +if (!$user->has_access(100)) { + header("Location: " . conf('web_path') . "/index.php?access=denied"); + exit(); +} + + +if ( $type and $action == 'show_songs' ) { + print("<p style=\"font-size: 12px; font-weight: bold;\"> Orphaned Songs with missing $type information </p>"); + + $song_ids = get_orphan_songs($type); + show_songs($song_ids); +} + +show_template('header'); + +show_menu_items('Admin'); +show_admin_menu('Catalog'); + +if ( $action == 'show_orphan_songs' ) { + print("<p style=\"font-size: 12px; font-weight: bold;\"> Orphaned songs with no artist </p>"); + + $song_ids = get_orphan_songs(); + show_songs($song_ids); +} +elseif ( $action == 'show_orphan_albums' ) { + print("<p style=\"font-size: 12px; font-weight: bold;\"> Orphaned albums with no name </p>"); + + $song_ids = get_orphan_albums(); + show_songs($song_ids); +} + +?> + + +<hr> + +</body> +</html> diff --git a/admin/preferences.php b/admin/preferences.php new file mode 100644 index 00000000..2fa729eb --- /dev/null +++ b/admin/preferences.php @@ -0,0 +1,92 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Preferences page + Preferences page for whole site, and where + the admins do editing of other users preferences + +*/ + +require('../modules/init.php'); + + +if (!$user->has_access(100)) { + access_denied(); +} + +$user_id = intval(scrub_in($_REQUEST['user_id'])); + + +switch(scrub_in($_REQUEST['action'])) { + + case 'user': + $temp_user = new User(0,$user_id); + $user_id = $temp_user->id; + $fullname = "ADMIN - " . $temp_user->fullname; + $preferences = $temp_user->get_preferences(); + break; + case 'update_preferences': + if (conf('demo_mode')) { break; } + update_preferences($user_id); + if ($user_id != '0') { + $temp_user = new User(0,$user_id); + $fullname = "ADMIN - " . $temp_user->fullname; + $preferences = $temp_user->get_preferences(); + } + else { + $preferences = get_site_preferences(); + } + break; + case 'fix_preferences': + $temp_user = new User(0,$user_id); + $temp_user->fix_preferences(); + $preferences = $temp_user->get_preferences(); + break; + default: + $user_id = 0; + $preferences = get_site_preferences(); + $fullname = "Site"; + break; + +} // End Switch Action + + +// HEADER +show_template('header'); +show_menu_items('Admin'); +show_admin_menu('Admin Preferences'); +show_clear(); +// HEADER + +// Set Target +$target = "/admin/preferences.php"; + +// Show the default preferences page +require (conf('prefix') . "/templates/show_preferences.inc"); + + +// FOOTER +show_admin_menu('Admin Preferences'); +show_menu_items('Admin'); + +?> diff --git a/admin/song.php b/admin/song.php new file mode 100644 index 00000000..cf38a14f --- /dev/null +++ b/admin/song.php @@ -0,0 +1,182 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +/*! + @header Song Admin Files + Edit song information. Can be just DB or file based (update MP3 ID3 tags). + +*/ + +require('../modules/init.php'); +require_once(conf('prefix').'/lib/flag.php'); + +if (!$user->has_access('100')) { + access_denied(); +} + + +$action = scrub_in($_REQUEST['action']); +$song = scrub_in($_REQUEST['song']); + + +show_template('header'); + +show_menu_items('Admin'); +show_admin_menu('Catalog'); + +$song_obj = new Song($_REQUEST['song_id']); + +switch($action) +{ + case "Update": + case "update"; + update_song_info($song); + edit_song_info($song); + break; + case "Edit": + case "edit": + edit_song_info($song); + break; + case "disable": + + // If we pass just one, make it still work + if (!is_array($_REQUEST['song_ids'])) { $song_obj->update_enabled('disabled',$_REQUEST['song_ids']); } + else { + foreach ($_REQUEST['song_ids'] as $song_id) { + $song_obj->update_enabled('disabled',$song_id); + } // end foreach + } // end else + show_confirmation(_("Songs Disabled"),_("The requested song(s) have been disabled"),return_referer()); + break; + case "enabled": + // If we pass just one, make it still work + if (!is_array($_REQUEST['song_ids'])) { $song_obj->update_enabled('enabled',$_REQUEST['song_ids']); } + else { + foreach ($_REQUEST['song_ids'] as $song_id) { + $song_obj->update_enabled('enabled',$song_id); + } // end foreach + } // end else + show_confirmation(_("Songs Enabled"),_("The requested song(s) have been enabled"),return_referer()); + break; + + default: + echo "Don't know what to do yet."; +} + + +/* + @function edit_song_info + @discussion yea this is just wrong +*/ +function edit_song_info($song) { + $info = new Song($song); + preg_match("/^.*\/(.*?)$/",$info->file, $short); + $filename = htmlspecialchars($short[1]); + if(preg_match('/\.ogg$/',$short[1])) + { + $ogg = TRUE; + $oggwarn = "<br/><br><em>This file is an OGG file, which Ampache only has limited support for.<br/>"; + $oggwarn .= "You can make changes to the database here, but Ampache will not change the actual file's information.</em><br/><br/>"; + } + +echo <<<EDIT_SONG_1 +<p><b>Editing $info->title</b></p> +<form name="update_song" method="post" action="song.php"> +<table class="border" cellspacing="0"> + <tr class="table-header"> + <td colspan="3"><b>Editing $info->title</b></td> + </tr> + <tr class="odd"> + <td>File:</td> + <td colspan="2">$filename $oggwarn</td> + </tr> + <tr class="odd"> + <td>Title:</td> + <td colspan="2"><input type="text" name="title" size="60" value="$info->title"></td> + </tr> + <tr class="even"> + <td>Artist:</td> + <td> +EDIT_SONG_1; + show_artist_pulldown($info->artist); +echo <<<EDIT_SONG_2 + </td> + <td>or <input type="text" name="new_artist" size="30" value=""></td> + </tr> + + <tr class="odd"> + <td>Album:</td> + <td> +EDIT_SONG_2; + show_album_pulldown($info->album); +echo <<<EDIT_SONG_3 + </td> + <td>or <input type="text" name="new_album" size="30" value=""></td> + </tr> + <tr class="even"> + <td>Track:</td> + <td colspan="2"><input type="text" size="4" maxlength="4" name="track" value="$info->track"></input></td> + </tr> + <tr class="odd"> + <td>Genre:</td> + <td colspan="2"> +EDIT_SONG_3; + show_genre_pulldown($info->genre, 1); +echo <<<EDIT_SONG_4 + <tr class="even"> + <td>Year</td> + <td colspan="2"><input type="text" size="4" maxlength="4" name="year" value="$info->year"></input></td> + </tr> + +EDIT_SONG_4; +if(!$ogg) +{ + echo <<<EDIT_SONG_5 + <tr class="even"> + <td> </td> + <td><input type="checkbox" name="update_id3" value="yes"> Update id3 tags</input></td> + <td> </td> + </tr> +EDIT_SONG_5; +} +echo <<<EDIT_SONG_6 + <tr class="odd"> + <td> </td> + <td colspan="2"> + <input type="hidden" name="song" value="$song" /> + <input type="hidden" name="current_artist_id" value="$info->artist" /> + <input type="submit" name="action" value="Update" /> + </td> + </tr> +</table> + +</form> +EDIT_SONG_6; +} + +?> +<hr> + +</body> +</html> diff --git a/admin/users.php b/admin/users.php new file mode 100644 index 00000000..350d1289 --- /dev/null +++ b/admin/users.php @@ -0,0 +1,181 @@ +<?php + +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Users Admin Page + Handles User management functions + +*/ + +require_once ("../modules/init.php"); + +if (!$user->has_access(100)) { + access_denied(); +} + + +$action = scrub_in($_REQUEST['action']); + + +show_template('header'); + +show_menu_items('Admin'); +show_admin_menu('Users'); +show_clear(); + +$user_id = scrub_in($_REQUEST['user']); +$temp_user = new User($user_id); + +switch ($action) { + case 'edit': + if (conf('demo_mode')) { break; } + show_user_form($temp_user->id, + $temp_user->username, + $temp_user->fullname, + $temp_user->email, + $temp_user->access, + 'edit_user', + ''); + break; + + case 'update_user': + if (conf('demo_mode')) { break; } + + /* Clean up the variables */ + $username = scrub_in($_REQUEST['new_username']); + $fullname = scrub_in($_REQUEST['new_fullname']); + $email = scrub_in($_REQUEST['new_email']); + $access = scrub_in($_REQUEST['user_access']); + $pass1 = scrub_in($_REQUEST['new_password_1']); + $pass2 = scrub_in($_REQUEST['new_password_2']); + + /* Setup the temp user */ + $thisuser = new User($username); + + /* Verify Input */ + if (empty($username)) { + $GLOBALS['error']->add_error('username',_("Error Username Required")); + } + if ($pass1 !== $pass2 AND !empty($pass1)) { + $GLOBALS['error']->add_error('password',_("Error Passwords don't match")); + } + + /* If we've got an error then break! */ + if ($GLOBALS['error']->error_state) { + show_user_form($temp_user->id, + $thisuser->username, + $thisuser->fullname, + $thisuser->email, + $thisuser->access, + 'edit_user', + ''); + break; + } // if we've had an oops! + + if ($access != $thisuser->access) { + $thisuser->update_access($access); + } + if ($email != $thisuser->email) { + $thisuser->update_email($email); + } + if ($username != $thisuser->username) { + $thisuser->update_username($username); + } + if ($fullname != $user->fullname) { + $thisuser->update_fullname($fullname); + } + if ($pass1 == $pass2 && strlen($pass1)) { + $thisuser->update_password($pass1); + } + show_confirmation("User Updated", $thisuser->username . "'s information has been updated","admin/users.php"); + break; + case 'add_user': + if (conf('demo_mode')) { break; } + $username = scrub_in($_REQUEST['new_username']); + $fullname = scrub_in($_REQUEST['new_fullname']); + $email = scrub_in($_REQUEST['new_email']); + $access = scrub_in($_REQUEST['user_access']); + $pass1 = scrub_in($_REQUEST['new_password_1']); + $pass2 = scrub_in($_REQUEST['new_password_2']); + if (($pass1 !== $pass2)) { + $GLOBALS['error']->add_error('password',_("Error Passwords don't match")); + } + if (empty($username)) { + $GLOBALS['error']->add_error('username',_("Error Username Required")); + } + if (!$user->create($username, $fullname, $email, $pass1, $access)) { + $GLOBALS['error']->add_error('general',"Error: Insert Failed"); + } + /* If we end up with an error */ + if ($GLOBALS['error']->error_state) { + show_user_form('','$username','$fullname','$email','$access','new_user',''); + break; + } + show_confirmation("New User Added",$username . " has been created with an access level of " . $access,"admin/users.php"); + break; + case 'delete': + if (conf('demo_mode')) { break; } + show_confirm_action(_("Are you sure you want to permanently delete") . " $temp_user->fullname ($temp_user->username) ?", + "admin/users.php", + "action=confirm_delete&user=$temp_user->username"); + break; + + case 'confirm_delete': + if (conf('demo_mode')) { break; } + if ($_REQUEST['confirm'] == _("No")) { show_manage_users(); break; } + if ($temp_user->delete()) { + show_confirmation(_("User Deleted"), "$temp_user->username has been Deleted","admin/users.php"); + } + else { + show_confirmation(_("Delete Error"), _("Unable to delete last Admin User"),"admin/users.php"); + } + break; + case 'show_add_user': + if (conf('demo_mode')) { break; } + show_user_form('','','','','','new_user',''); + break; + + case 'update': + case 'disabled': + if (conf('demo_mode')) { break; } + $level = scrub_in($_REQUEST['level']); + $thisuser = new User($_REQUEST['user']); + if ($_SESSION['userdata']['access'] == 'admin') { + $thisuser->update_access($level); + } + show_manage_users(); + break; + + default: + show_manage_users(); + +} + +echo "<br /><br />"; +show_admin_menu('Users'); +show_menu_items('Admin'); + +?> + +</body> +</html> diff --git a/albumart.php b/albumart.php new file mode 100644 index 00000000..69fcd122 --- /dev/null +++ b/albumart.php @@ -0,0 +1,59 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/* + + @header Album Art +This pulls album art out of the file using the getid3 library +and dumps it to the browser as an image mime type. + +*/ + +require('modules/init.php'); + + +$album = new Album($_REQUEST['id']); + +// Check db first +$r = $album->get_art($_REQUEST['fast']); + +if (isset($r->art)) { + $art = $r->art; + $mime = $r->art_mime; + $found = 1; +} + +if (!$found) { + // Print a transparent gif instead +// header('Content-type: image/jpg'); +// readfile(conf('prefix') . "/docs/images/blankalbum.jpg"); + header('Content-type: image/gif'); + readfile(conf('prefix') . conf('theme_path') . "/images/blankalbum.gif"); +} +else { + // Print the album art + $extension = substr($mime,strlen($mime)-3,3); + header("Content-type: $mime"); + header("Content-Disposition: filename=" . $album->name . "." . $extension); + echo $art; +} + +?> diff --git a/albums.php b/albums.php new file mode 100644 index 00000000..92ece304 --- /dev/null +++ b/albums.php @@ -0,0 +1,181 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/* + + Do most of the dirty work of displaying the mp3 catalog + +*/ + +require_once("modules/init.php"); + +// We'll set any input parameters here +if(!isset($_REQUEST['match'])) { $_REQUEST['match'] = "Browse"; } +if(isset($_REQUEST['match'])) $match = scrub_in($_REQUEST['match']); +if(isset($_REQUEST['album'])) $album = scrub_in($_REQUEST['album']); +if(isset($_REQUEST['artist'])) $artist = scrub_in($_REQUEST['artist']); +$_REQUEST['artist_id'] = scrub_in($_REQUEST['artist_id']); + +show_template('header'); +show_menu_items('Albums'); +show_clear(); + +if ($_REQUEST['action'] === 'clear_art') { + if (!$user->has_access('25')) { access_denied(); } + $album = new Album($_REQUEST['album_id']); + $album->clear_art(); + show_confirmation(_("Album Art Cleared"),_("Album Art information has been removed form the database"),"/albums.php?action=show&album=" . $album->id); + +} // clear_art +// if we have album +elseif (isset($album)) { + $album = new Album($album); + $album->format_album(); + + require (conf('prefix') . "/templates/show_album.inc"); + + if (isset($artist) && $artist != 0) { + $song_ids = get_song_ids_from_artist_and_album($artist, $album->id); + } + else { + $song_ids = get_song_ids_from_album($album->id); + } + show_songs($song_ids,0,$album); +} // isset(album) + +// Finds the Album art from amazon +elseif ($_REQUEST['action'] === 'find_art') { + + if (!$user->has_access('25')) { access_denied(); } + + /* Echo notice if no amazon token is found, but it's enabled */ + if (in_array('amazon',conf('album_art_order')) AND !conf('amazon_developer_key')) { + echo "<br /><div class=\"fatalerror\">Error: No Amazon Developer Key set, amazon album art searching will not work</div>"; + } + + $album = new Album($_REQUEST['album_id']); + $result = $album->find_art($_REQUEST['cover']); + if ($result) { + show_confirmation(_("Album Art Located"),_("Album Art information has been located in Amazon. If incorrect, click \"Reset Album Art\" below to remove the artwork."),"/albums.php?action=show&album=" . $album->id); + echo " [ <a href=\"" . conf('web_path') . "/albums.php?action=clear_art&album_id=" . $album->id . "\">Reset Album Art</a> ]"; + echo "<p align=left><img src=\"" . conf('web_path') . "/albumart.php?id=" . $album->id . "\"></p>"; + echo "<p><form name=\"cover\" method=\"get\" action=\"".$_SERVER['PHP_SELF']."\">"; + echo "Enter URL to album art "; + echo "<input type=\"text\" size=\"40\" id=\"cover\" name=\"cover\" value=\"\" />\n"; + echo "<input type=\"hidden\" name=\"action\" value=\"find_art\" />\n"; + echo "<input type=\"hidden\" name=\"album_id\" value=\"$album->id\" />\n"; + echo "<input type=\"submit\" value=\"" . _("Get Art") . "\" />\n"; + echo "</form>"; + } + else { + show_confirmation(_("Album Art Not Located"),_("Album Art could not be located at this time. This may be due to Amazon being busy, or the album not being present in their collection."),"/albums.php?action=show&album=" . $album->id); + echo "<p><form name=\"cover\" method=\"get\" action=\"".$_SERVER['PHP_SELF']."\">"; + echo "Enter URL to album art "; + echo "<input type=\"text\" size=\"40\" id=\"cover\" name=\"cover\" value=\"\" />"; + echo "<input type=\"hidden\" name=\"action\" value=\"find_art\" />"; + echo "<input type=\"hidden\" name=\"album_id\" value=\"$album->id\" /> "; + echo "<input type=\"submit\" value=\"" . _("Get Art") . "\" />\n"; + echo "</form>"; + } +} // find_art + +// Updates Album from tags +elseif ($_REQUEST['action'] === 'update_from_tags') { + + $album = new Album($_REQUEST['album_id']); + + echo "<br /><b>" . _("Starting Update from Tags") . ". . .</b><br />\n"; + + $catalog = new Catalog(); + $catalog->update_single_item('album',$_REQUEST['album_id']); + + echo "<br /><b>" . _("Update From Tags Complete") . "</b> "; + echo "<a href=\"" . conf('web_path') . "/albums.php?action=show&album=" . $_REQUEST['album_id'] . "\">[" . _("Return") . "]</a>"; + +} // update_from_tags + +else { + + if (strlen($_REQUEST['match']) < '1') { $match = 'none'; } + + // Setup the View Ojbect + $view = new View(); + $view->import_session_view(); + + switch($match) { + case 'Show_all': + show_alphabet_list('albums','albums.php','show_all'); + echo "<form name=\"f\" method=\"get\" action=\"".$_SERVER['PHP_SELF']."\"><label for=\"match\" accesskey=\"S\">" . _("<u>S</u>how all albums") ."</label> <input type=\"text\" size=\"3\" id=\"match\" name=\"match\" value=\"\"></input><input type=\"hidden\" name=\"action\" value=\"match\"></input></form>\n"; + $offset_limit = 99999; + $sql = "SELECT id FROM album"; + break; + case 'Show_missing_art': + show_alphabet_list('albums','albums.php','show_missing_art'); + echo "<form name=\"f\" method=\"get\" action=\"".$_SERVER['PHP_SELF']."\"><label for=\"match\" accesskey=\"S\">" . _("<u>S</u>how all albums") ."</label> <input type=\"text\" size=\"3\" id=\"match\" name=\"match\" value=\"\"></input><input type=\"hidden\" name=\"action\" value=\"match\"></input></form>\n"; + $offset_limit = 99999; + $sql = "SELECT id FROM album where art is null"; + break; + case 'Browse': + case 'show_albums': + show_alphabet_list('albums','albums.php','browse'); + echo "<form name=\"f\" method=\"get\" action=\"".$_SERVER['PHP_SELF']."\"><label for=\"match\" accesskey=\"S\">" . _("<u>S</u>how only albums starting with") . "</label> <input type=\"text\" size=\"3\" id=\"match\" name=\"match\" value=\"\"></input><input type=\"hidden\" name=\"action\" value=\"match\"></input></form>\n"; + $sql = "SELECT id FROM album"; + break; + case 'none': + show_alphabet_list('albums','albums.php','a'); + echo "<p style=\"font: 10pt bold;\">". + _("Select a starting letter or Show all") . "</p>"; + echo "<form name=\"f\" method=\"get\" action=\"".$_SERVER['PHP_SELF']."\"><label for=\"match\" accesskey=\"S\">" . _("<u>S</u>how only albums starting with") . "</label> <input type=\"text\" size=\"3\" id=\"match\" name=\"match\" value=\"\"></input><input type=\"hidden\" name=\"action\" value=\"match\"></input></form>\n"; + $sql = "SELECT id FROM album WHERE name LIKE 'a%'"; + break; + default: + show_alphabet_list('albums','albums.php',$match); + echo "<form name=\"f\" method=\"get\" action=\"".$_SERVER['PHP_SELF']."\"><label for=\"match\" accesskey=\"S\">" . _("<u>S</u>how only albums starting with") . "<input type=\"text\" size=\"3\" id=\"match\" name=\"match\" value=\"$match\"></input><input type=\"hidden\" name=\"action\" value=\"match\"></input></p></form>\n"; + echo "<br /><br />"; + $sql = "SELECT id FROM album WHERE name LIKE '$match%'"; + } // end switch + + // if we are returning + if ($_REQUEST['keep_view']) { + $view->initialize(); + } + + // If we aren't keeping the view then initlize it + elseif ($sql) { + $db_results = mysql_query($sql, dbh()); + $total_items = mysql_num_rows($db_results); + if ($match != "Show_all") { $offset_limit = $_SESSION['userdata']['offset_limit']; } + $view = new View($sql, 'albums.php','name',$total_items,$offset_limit); + } + + else { $view = false; } + + if ($view->base_sql) { + $albums = get_albums($view->sql); + show_albums($albums,$view); + } + +} // else no album + +echo "<br /><br />"; +show_menu_items('Albums'); +?> + +</body> +</html> diff --git a/amp-mpd.php b/amp-mpd.php new file mode 100644 index 00000000..1fb4743e --- /dev/null +++ b/amp-mpd.php @@ -0,0 +1,140 @@ +<?php +/* + * nj-jukebox.php - Netjuke MPD-based jukebox. + * Copyright (C) 2003 Benjamin Carlisle (bcarlisle@24oz.com) + * http://mpd.24oz.com/ + * + * This has been modified to work with Ampache (http://www.ampache.org) It was + * initially written for NetJuke (http://netjuke.sourceforge.net/) + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +require_once("modules/init.php"); + +// Connect to the MPD +if (!class_exists('mpd')) { require_once(conf('prefix') . "/modules/mpd/mpd.class.php"); } +if (!is_object($myMpd)) { $myMpd = new mpd(conf('mpd_host'),conf('mpd_port')); } + +if (!$myMpd->connected) { + echo "<font class=\"error\">" . _("Error Connecting") . ": " . $myMpd->errStr . "</font><br />\n"; + log_event($_SESSION['userdata']['username'],' connection_failed ',"Error: Unable able to connect to MPD, " . $myMpd->errStr); +} +else { + switch ($_REQUEST['action']) { + case "add": + if (!$user->has_access(25)) { break; } + $song_ids = array(); + $song_ids[0] = $_REQUEST[song_id]; + addToPlaylist( $myMpd, $song_ids ); + break; + case "rem": + if (!$user->has_access(25)) { break; } + if ( is_null($myMpd->PLRemove($_REQUEST[id])) ) echo "ERROR: " .$myMpd->errStr."\n"; + header ("Location: " . conf('web_path')); + break; + case ' > ': + case "play": + if (!$user->has_access(25)) { break; } + if ( is_null($myMpd->Play()) ) echo "ERROR: " .$myMpd->errStr."\n"; + header ("Location: " . conf('web_path')); + break; + case "stop": + case ' X ': + if (!$user->has_access(25)) { break; } + if ( is_null($myMpd->Stop()) ) echo "ERROR: " .$myMpd->errStr."\n"; + header ("Location: " . conf('web_path')); + break; + case ' = ': + case "pause": + if (!$user->has_access(25)) { break; } + if ( is_null($myMpd->Pause()) ) echo "ERROR: " .$myMpd->errStr."\n"; + header ("Location: " . conf('web_path')); + break; + case '|< ': + case "Prev": + if (!$user->has_access(25)) { break; } + if ( is_null($myMpd->Previous()) ) echo "ERROR: " . $myMpd->errStr."\n"; + header ("Location: " . conf('web_path')); + break; + case ' >|'; + case "Next": + if (!$user->has_access(25)) { break; } + if ( is_null($myMpd->Next()) ) echo "ERROR: " . $myMpd->errStr."\n"; + header ("Location: " . conf('web_path')); + break; + case "shuffle": + if (!$user->has_access(25)) { break; } + if ( is_null($myMpd->PLShuffle()) ) echo "ERROR: " .$myMpd->errStr."\n"; + header ("Location: " . conf('web_path')); + break; + case "clear": + if (!$user->has_access(25)) { break; } + if ( is_null($myMpd->PLClear()) ) echo "ERROR: " .$myMpd->errStr."\n"; + header ("Location: " . conf('web_path')); + break; + case "loop": + if (!$user->has_access(25)) { break; } + if ($_REQUEST['val'] == "On") { $_REQUEST['val'] = '1'; } + else { $_REQUEST['val'] = '0'; } + if ( is_null($myMpd->SetRepeat($_REQUEST['val'])) ) echo "ERROR: " .$myMpd->errStr."\n"; + header ("Location: " . conf('web_path')); + break; + case "random": + if (!$user->has_access(25)) { break; } + if ($_REQUEST['val'] == "On") { $_REQUEST['val'] = '1'; } + else { $_REQUEST['val'] = '0'; } + if ( is_null($myMpd->SetRandom($_REQUEST['val']))) echo "ERROR: " .$myMpd->errStr."\n"; + header ("Location: " . conf('web_path')); + break; + case "adjvol": + if (!$user->has_access(25)) { break; } + if ( is_null($myMpd->AdjustVolume($_REQUEST[val])) ) echo "ERROR: " .$myMpd->errStr."\n"; + header ("Location: " . conf('web_path')); + break; + case "setvol": + if (!$user->has_access(25)) { break; } + if ( is_null($myMpd->SetVolume($_REQUEST[val])) ) echo "ERROR: " .$myMpd->errStr."\n"; + header ("Location: " . conf('web_path')); + break; + case "skipto": + if (!$user->has_access(25)) { break; } + if ( is_null($myMpd->SkipTo($_REQUEST[val])) ) echo "ERROR: " .$myMpd->errStr."\n"; + header ("Location: " . conf('web_path')); + break; + case "pladd": + if (!$user->has_access(25)) { break; } + $plist = new Playlist( $_REQUEST[pl_id] ); + $song_ids = $plist->get_songs(); + addToPlaylist( $myMpd, $song_ids ); + break; + case "albadd": + if (!$user->has_access(25)) { break; } + $album = new Album( $_REQUEST[alb_id] ); + $song_ids = $album->get_song_ids( ); + addToPlaylist( $myMpd, $song_ids ); + break; + case "show_control": + require (conf('prefix') . "/templates/show_mpdplay.inc"); + break; + default: + header ("Location: " . conf('web_path')); + break; + } // end switch + + // We're done let's disconnect + $myMpd->Disconnect(); +} // end else +?> diff --git a/artists.php b/artists.php new file mode 100644 index 00000000..3cb127d0 --- /dev/null +++ b/artists.php @@ -0,0 +1,126 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/* + + Do most of the dirty work of displaying the mp3 catalog + +*/ + +require_once("modules/init.php"); + +if (!isset($_REQUEST['match'])) { $_REQUEST['match'] = "Browse"; } +if (!isset($_REQUEST['action'])) { $_REQUEST['action'] = "match"; } +$action = scrub_in($_REQUEST['action']); + +show_template('header'); +show_menu_items('Artists'); +show_clear(); + + +switch($action) { + case 'show': + case 'Show': + show_alphabet_list('artists','artists.php'); + $artist = new Artist(scrub_in($_REQUEST['artist'])); + $artist->show_albums(); + break; + + case 'show_all_songs': + $artist = get_artist_name(scrub_in($_REQUEST['artist'])); + echo "<h2>" . _("All songs by") . " $artist</h2>"; + $song_ids = get_song_ids_from_artist($_REQUEST['artist']); + show_songs($song_ids); + break; + + case 'update_from_tags': + + $artist = new Artist($_REQUEST['artist']); + + echo "<br /><b>" . _("Starting Update from Tags") . ". . .</b><br />\n"; + + $catalog = new Catalog(); + $catalog->update_single_item('artist',$_REQUEST['artist']); + + echo "<br /><b>" . _("Update From Tags Complete") . "</b> "; + echo "<a href=\"" . conf('web_path') . "/artists.php?action=show&artist=" . $_REQUEST['artist'] . "\">[" . _("Return") . "]</a>"; + + break; + case 'match': + case 'Match': + $match = scrub_in($_REQUEST['match']); + preg_match("/^(\w*)/", $match, $matches); + show_alphabet_list('artists','artists.php',$match); + if ($match === "Browse") { + echo "<form name=\"f\" method=\"get\" action=\"".$_SERVER['PHP_SELF']."\">\n"; + echo "<label for=\"match\" accesskey=\"S\">"; + echo _("<u>S</u>how artists starting with") . "</label> \n"; + echo "<input type=\"text\" size=\"3\" id=\"match\" name=\"match\" value=\"\" />\n"; + echo "<input type=\"hidden\" name=\"action\" value=\"match\" />\n"; + echo "</form>\n"; + show_artists(); + } + elseif ($match === "Show_all") { + echo "<form name=\"f\" method=\"get\" action=\"".$_SERVER['PHP_SELF']."\">\n"; + echo "<label for=\"match\" accesskey=\"S\">"; + echo _("<u>S</u>how artists starting with") . "</label> "; + echo "<input type=\"text\" size=\"3\" id=\"match\" name=\"match\" value=\"\" />\n"; + echo "<input type=\"hidden\" name=\"action\" value=\"match\" />\n"; + echo "</form>\n"; + $_SESSION['view_offset_limit'] = 999999; + show_artists(); + } + else { + $chr = preg_replace("/[^a-zA-Z0-9]/", "", $matches[1]); + + echo "<form name=\"f\" method=\"get\" action=\"".$_SERVER['PHP_SELF']."\">\n"; + echo "<label for=\"match\" accesskey=\"S\">"; + echo _("<u>S</u>how artists starting with") . "</label> \n"; + echo "<input type=\"text\" size=\"3\" id=\"match\" name=\"match\" value=\"$chr\" />\n"; + echo "<input type=\"hidden\" name=\"action\" value=\"match\" />\n"; + echo "</p></form>\n"; + + if ($chr == '') { + show_artists('A'); + } + else { + show_artists($chr); + } + } + break; + + default: + echo "<form name=\"f\" method=\"get\" action=\"".$_SERVER['PHP_SELF']."\">\n"; + echo "<label for=\"match\" accesskey=\"S\">"; + echo _("<u>S</u>how artists starting with") . "</label> \n"; + echo "<input type=\"text\" size=\"3\" id=\"match\" name=\"match\" value=\"\" />\n"; + echo "<input type=\"hidden\" name=\"action\" value=\"match\" />\n"; + echo "</p></form>\n"; + show_alphabet_list('artists','artists.php'); + show_artists('A'); + break; + +} +echo "<br /><br />"; +show_menu_items('Artists'); +?> + + +</body> +</html> diff --git a/batch.php b/batch.php new file mode 100644 index 00000000..5cd94f42 --- /dev/null +++ b/batch.php @@ -0,0 +1,71 @@ +<?php +/* + + Copyright (c) 2004 batch.php by RosenSama + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/* + + creates and sends a zip of an album or playlist + zip is just a container w/ no compression + + uses archive.php from + http://phpclasses.mirrors.nyphp.org/browse/file/3191.html + can modify to allow user to select tar, gzip, or bzip2 + + I believe archive.php requires zlib support to be eanbled + in your PHP build. +*/ + + require_once('modules/init.php'); + require_once(conf('prefix') . "/lib/batch.php"); + //test that batch download is permitted (user or system?) + + /* Drop the normal Time limit constraints, this can take a while */ + set_time_limit(0); + + + if( batch_ok( ) ) { + switch( scrub_in( $_REQUEST['action'] ) ) { + case "pl": + $id = scrub_in( $_REQUEST['id'] ); + $pl = new Playlist( $id ); + $name = $pl->name; + $song_ids = $pl->get_songs(); + $song_files = get_song_files( $song_ids ); + set_memory_limit( $song_files[1]+16 ); + send_zip( $name, $song_files[0] ); + break; + case "alb": + $id = scrub_in( $_REQUEST['id'] ); + $alb = new Album( $id ); + $name = $alb->name; + $song_ids = $alb->get_song_ids(); + $song_files = get_song_files( $song_ids ); + set_memory_limit( $song_files[1]+16 ); + send_zip( $name, $song_files[0] ); + break; + default: + header( "Location:" . conf('web_path') . "/index.php?amp_error=Unknown action on batch.php: {$_REQUEST['action']}" ); + break; + } // action switch + } else { // bulk download permissions + header( "Location: " . conf('web_path') . "/index.php?amp_error=Download disabled" ); + } // no bulk download permissions + +?> diff --git a/bin/.htaccess b/bin/.htaccess new file mode 100644 index 00000000..3a428827 --- /dev/null +++ b/bin/.htaccess @@ -0,0 +1 @@ +Deny from all diff --git a/bin/archive/export_playlist.pl b/bin/archive/export_playlist.pl new file mode 100755 index 00000000..c6e0ccaf --- /dev/null +++ b/bin/archive/export_playlist.pl @@ -0,0 +1,80 @@ +#!/usr/bin/perl -w +# +# Exports playlists from ampache +# +# Fill in the site specific database connection parameters below before running +# + +use DBI; + +# Configure database connection parameters +my $db = "ampache"; # database +my $user = ""; # database user +my $pw = ""; # database user password + + +if ($#ARGV < 0) { + print "Usage: $0 <filename>\n"; + print " Exports Ampache playlists to <filename>.\n"; + exit; +} + + +open(OUT, "> $ARGV[0]") or die("Could not open '$ARGV[0]' for write - $!"); + +# Build DSNs +my $dsn = "dbi:mysql:database=$db;"; + +# Connect to database +my $dbh= DBI->connect($dsn, $user, $pw, + { RaiseError => 1, AutoCommit => 0 }); + + +# Prepare statements +my $sth = $dbh->prepare("SELECT id, name, owner, type FROM playlist"); +my $sth2 = $dbh->prepare("SELECT username FROM user + WHERE id = ?"); +my $sth3 = $dbh->prepare("SELECT song.file + FROM playlist_data, song + WHERE playlist_data.playlist = ? + AND playlist_data.song = song.id"); + +# Execute select and loop through results +$sth->execute(); +my $count = 0; +my ($id,$name,$owner,$type,$date,$file,$track); +while(($id,$name,$owner,$type) = $sth->fetchrow_array) { + if ($count > 0) { + # Use a blank line as a separator between playlists + print OUT "\n"; + } + + $count++; + + if ($owner =~ /^\d+$/) { + # Fetch username instead of id for owner + $sth2->execute($owner); + $owner = "unknown" unless (($owner) = $sth2->fetchrow_array); + $sth2->finish; + } + + # Date is not present in old ampache's + $date = 0 if (! defined($date)); + + print OUT "Name: $name\n"; + print OUT "Owner: $owner\n"; + print OUT "Type: $type\n"; + + # Grab songs for this playlist + $sth3->execute($id); + while(($file) = $sth3->fetchrow_array) { + print OUT "File: $file\n"; + } +} + +print "Exported $count playlists.\n"; + +# Clean up +$dbh->disconnect; +close(OUT); + diff --git a/bin/archive/import_playlist.pl b/bin/archive/import_playlist.pl new file mode 100755 index 00000000..4ce0e7b5 --- /dev/null +++ b/bin/archive/import_playlist.pl @@ -0,0 +1,118 @@ +#!/usr/bin/perl -w +# +# Imports playlists into ampache (from export_playlist.pl output) +# +# Fill in the site specific database connection parameters below before running +# + +use DBI; +use Data::Dumper; + +# Configure database connection parameters +my $db = "ampache3_1"; # database +my $user = ""; # database user +my $pw = ""; # database user password + + +if ($#ARGV < 0) { + print "Usage: $0 <filename>\n"; + print " Imports Ampache playlists from <filename>.\n"; + print " The format of <filename> should match the output of export_playlist.pl.\n"; + exit; +} + + +open(IN, "$ARGV[0]") or die("Could not open '$ARGV[0]' for read - $!"); + +# Build DSNs +my $dsn = "dbi:mysql:database=$db;"; + +# Connect to database +my $dbh = DBI->connect($dsn, $user, $pw, + { RaiseError => 1, AutoCommit => 0 }); + + +# Structure to contain playlists +my @playlists; + +# Parse file +my $i = 0; +while($line = <IN>) { + chomp $line; + + if ($line eq "") { + # Blank line means new playlist + $i++; + next; + } + + if ($line =~ /^ID: (.*)$/) { + $playlists[$i]->{id} = $1; + } + + if ($line =~ /^Name: (.*)$/) { + $playlists[$i]->{name} = $1; + } + + if ($line =~ /^Owner: (.*)$/) { + $playlists[$i]->{owner} = $1; + } + + if ($line =~ /^Type: (.*)$/) { + $playlists[$i]->{type} = $1; + } + + if ($line =~ /^File: (.*)$/) { + push @{$playlists[$i]->{files}}, $1; + } +} +close(IN); + +# Prepare statements +my $sth = $dbh->prepare("SELECT id FROM user + WHERE username = ?"); +my $sth2 = $dbh->prepare("INSERT INTO playlist + (name, owner, type) + values (?, ?, ?)"); +my $sth3 = $dbh->prepare("SELECT id FROM song + WHERE file = ?"); +my $sth4 = $dbh->prepare("INSERT INTO playlist_data + (playlist, song, track) + values (?, ?, ?)"); + +# Insert records into Ampache +my ($id,$name,$owner,$type,$file,$songid); +my $count = 0; +for ($i = 0; $i < $#playlists + 1; $i++) { + $count++; + + $name = $playlists[$i]->{name}; + + $sth->execute($playlists[$i]->{owner}); + $owner = 0 unless (($owner) = $sth->fetchrow_array); + $sth->finish; + + $type = $playlists[$i]->{type}; + + print "Importing playlist '$name'...\n"; + + # Create base playlist entry + $sth2->execute($name, $owner, $type); + $id = $dbh->{mysql_insertid}; + + # And add files to it + while($file = pop(@{$playlists[$i]->{files}})) { + $sth3->execute($file); + next unless (($songid) = $sth3->fetchrow_array); + $sth3->finish; + + $sth4->execute($id,$songid,0); + } +} + +print "Imported $count playlists.\n"; + +# Clean up +$dbh->disconnect; +close(IN); + diff --git a/bin/archive/migrate_user.pl b/bin/archive/migrate_user.pl new file mode 100755 index 00000000..cf36c868 --- /dev/null +++ b/bin/archive/migrate_user.pl @@ -0,0 +1,56 @@ +#!/usr/bin/perl -w +# +# Migrates users from Ampache v3.0 to Ampache v3.1. +# +# Fill in the site specific database connection parameters below before running +# + +use DBI; + +# Configure database connection parameters +my $db_old = "ampache"; # old database +my $db_new = "ampache3_1"; # new database +my $user_old = ""; # old database user +my $user_new = ""; # new database user +my $pw_old = "!"; # old database user password +my $pw_new = "!"; # new database user password + + +# Build DSNs +my $dsn_new = "dbi:mysql:database=$db_new;"; +my $dsn_old = "dbi:mysql:database=$db_old;"; + + +# Connect to old and new databases +my $dbh_new = DBI->connect($dsn_new, $user_new, $pw_new, + { RaiseError => 1, AutoCommit => 0 }); + +my $dbh_old = DBI->connect($dsn_old, $user_old, $pw_old, + { RaiseError => 1, AutoCommit => 0 }); + + +# Prepare select and insert statements +my $sth = $dbh_old->prepare("SELECT username, fullname, email, password, access + FROM user"); + +my $sth_update = $dbh_new->prepare("INSERT INTO user + (username, fullname, email, password, access, offset_limit) + VALUES (?, ?, ?, ?, ?, 50)"); + + +# Execute select and loop through results +$sth->execute(); +my ($f1,$f2,$f3,$f4,$f5); +my $count = 0; +while(($f1,$f2,$f3,$f4,$f5) = $sth->fetchrow_array) { + $sth_update->execute($f1,$f2,$f3,$f4,$f5); + $count++; +} + +print "Migrated $count users.\n"; + + +# Clean up +$dbh_old->disconnect; +$dbh_new->disconnect; + diff --git a/bin/catalog_update.php.inc b/bin/catalog_update.php.inc new file mode 100644 index 00000000..3f8e9a76 --- /dev/null +++ b/bin/catalog_update.php.inc @@ -0,0 +1,18 @@ +<?php + +$no_session='1'; +require ("../modules/init.php"); + +$sql = "SELECT id FROM catalog WHERE catalog_type='local'"; +$db_results = mysql_query($sql, dbh()); + +while ($r = mysql_fetch_row($db_results)) { + $catalog = new Catalog($r[0]); + + // Verify Existing + $catalog->verify_catalog(); + + // Look for new files + $catalog->add_to_catalog(); +} +?> diff --git a/bin/compare_config.php.inc b/bin/compare_config.php.inc new file mode 100644 index 00000000..af29b381 --- /dev/null +++ b/bin/compare_config.php.inc @@ -0,0 +1,55 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +$no_session = '1'; +require ("../modules/init.php"); +require ("../lib/debug.php"); + +$results = debug_read_config(conf('prefix') . "/config/ampache.cfg.php"); + +$dist_results = debug_read_config(conf('prefix') . "/config/ampache.cfg.php.dist"); + +echo "\nCHECKING CONF VARS... \n\n"; + +foreach ($dist_results['conf'] as $key=>$value) { + + if (!isset($results['conf'][$key])) { + echo "MISSING:"; + echo " $key = $value\n"; + } + +} // foreach dist + +echo "\nCHECKING LIBGLUE VARS...\n\n"; + +foreach ($dist_results['libglue'] as $key=>$value) { + + if (!isset($results['libglue'][$key])) { + echo "MISSING:"; + echo " $key = $value\n"; + } + + +} // foreach libglue + +?> + diff --git a/bin/create_genre b/bin/create_genre new file mode 100755 index 00000000..c9687e7b --- /dev/null +++ b/bin/create_genre @@ -0,0 +1,32 @@ +#!/usr/bin/perl + +# Copyright (c) 2000 Kveton.com +# All rights reserved. + +# $Id: create_genre,v 1.2 2003/11/24 05:53:12 vollmerk Exp $ +# $Source: /data/cvsroot/ampache/bin/create_genre,v $ + +# Create the genres in the database for ease of use + +use DBI; + +# User, pass and database names +my $user = '_user_'; +my $pass = '_password_'; +my $db_name = 'ampache'; +my $db_host = 'localhost'; + +$dbh = DBI->connect("dbi:mysql:database=$db_name;host=$db_host;port=3306", $user, $pass); +my $sql = qq{INSERT INTO genre (id,name) VALUES (?,?)}; +my $sth = $dbh->prepare($sql); + +open(GENRE, "< genres.txt"); + +while ( $line = <GENRE> ) { + chomp $line; + my ($id, $name) = split(/\./, $line); + print "$id : $name\n"; + $sth->execute($id,$name); +} + +1; diff --git a/bin/filesort.pl b/bin/filesort.pl new file mode 100755 index 00000000..a3347ab6 --- /dev/null +++ b/bin/filesort.pl @@ -0,0 +1,29 @@ +#!/usr/bin/perl -w +# +# Sorts your MP3s into directories based on the sort pattern specified +# in ampache + +use FindBind qw($Bin); +require "$Bin/init"; + +use Data::Dumper; +use Getopt::Long; + +Getopt::Long::Configure('bundling','no_ignore_case'); +GetOptions + ("h|help" => \$usage, + "t|test" => \$pretend, + "a|all" => \$all, + "s|sort" => \$sort, + "c|clean" => \$clean, + "v|verbose" => \$verbose); + +if ($help) { + usage(); +} + + +# +# Pull in Data from Ampache +# + diff --git a/bin/fileupdate.pl b/bin/fileupdate.pl new file mode 100755 index 00000000..4353d057 --- /dev/null +++ b/bin/fileupdate.pl @@ -0,0 +1,346 @@ +#!/usr/bin/perl -w + +# Find and file away MP3's. Run multiple times and will +# ignore addition of duplicates in db (based on MD5 hash +# of full file path. + +use FindBin qw($Bin); +require "$Bin/init"; + +use Data::Dumper; +use Getopt::Long; + +use vars qw($help $pretend $id3 $rename $sort $all $verbose); + +Getopt::Long::Configure('bundling','no_ignore_case'); +GetOptions + ("h|help" => \&usage, + "p|pretend" => \$pretend, + "i|id3" => \$id3, + "r|rename" => \$rename, + "s|sort" => \$sort, + "a|all" => \$all, + "rename_all" => \$rename_all, + "sort_all" => \$sort_all, + "v|verbose" => \$verbose); + +if ( !$help && !$all && !$id3 && !$rename && !$sort && !$rename_all && !$sort_all ) { + usage(); +} + +if ($help) { + usage(); +} + +if($id3 or $all) +{ + my @flagged = $ampache->get_table_where("flagged","WHERE type = 'setid3'"); + + foreach my $update(@flagged) + { + my @info = $ampache->get_song_info($update->{'song'}); + my $cmd = update_id3_tag($ampache,@info); + if($rename or $all) + { + if($verbose){ print "Marking for rename after id3\n"; } + if(!$pretend){ $ampache->change_flags(@info,'setid3','ren'); } + } + else + { + if($sort or $all) + { + if($verbose){ print "Marking for sort after id3\n"; } + if(!$pretend){ $ampache->change_flags(@info,'setid3','sort'); } + } + else + { + if($verbose){ print "Stopping after id3 update\n"; } + if(!$pretend){ $ampache->change_flags(@info,'setid3','notify'); } + } + } + } +} + +if($rename or $all) +{ + my $filename = ''; + my @flagged = $ampache->get_table_where("flagged","WHERE type = 'ren'"); + foreach my $update (@flagged) + { + my @info = $ampache->get_song_info($update->{'song'}); + my $cmd = rename_file($ampache,\$filename,@info); + if(!$pretend){ $ampache->update_song($cmd,@info); } + if($sort or $all) + { + if($verbose){ print "Marking for sort after rename\n"; } + if(!$pretend){ $ampache->change_flags(@info,'ren','sort'); } + } + else + { + if($verbose){ print "Updating filename in DB after rename\n"; } + if(!$pretend){ $ampache->change_flags(@info,'ren','notify'); } + } + } +} + +if ($rename_all) { + my $filename = ''; + my @flagged = $ampache->get_table_where("catalog,song","WHERE catalog.catalog_type='local' AND catalog.id=song.catalog","song.id AS song"); + foreach my $update (@flagged) { + my @info = $ampache->get_song_info($update->{'song'}); + my $cmd = rename_file($ampache,\$filename,@info); + if(!$pretend){ $ampache->update_song($cmd,@info); } + } # End Foreach +} # End Rename All + +if ($sort_all) { + my $filename = ''; + my @flagged = $ampache->get_table_where("catalog,song","WHERE catalog.catalog_type='local' AND catalog.id=song.catalog","song.id AS song"); + foreach my $update(@flagged) + { + my @info = $ampache->get_song_info($update->{'song'}); + my $cmd = sort_file($ampache,\$filename,@info); + if(!$pretend){ $ampache->update_song($cmd,@info); } + if($verbose){ print "Updating filename in DB after sort\n"; } + if(!$pretend){ $ampache->change_flags(@info,'sort','notify'); } + } +} # End Sort ALL + + +if($sort or $all) +{ + my $filename = ''; + my @flagged = $ampache->get_table_where("flagged","WHERE type = 'sort'"); + foreach my $update(@flagged) + { + my @info = $ampache->get_song_info($update->{'song'}); + my $cmd = sort_file($ampache,\$filename,@info); + if(!$pretend){ $ampache->update_song($cmd,@info); } + if($verbose){ print "Updating filename in DB after sort\n"; } + if(!$pretend){ $ampache->change_flags(@info,'sort','notify'); } + } +} + +# # # # # +# subs +# # # # # # # + +# %A = album name +# %a = artist name +# %C = catalog path (for the specified song) +# %c = comment +# %g = genre +# %y = year +# %T = track number +# %t = song title +# +# %filename I use for filename + +sub get_catalog_setting +{ + my ($self,$catalog,$setting) = @_; + #bless $self; + my $cmd = $self->get_catalog_option($catalog,$setting); + return $cmd; +} + +sub update_id3_tag +{ + my ($self,$song) = @_; + my $id3set = get_catalog_setting($self,$song->{'catalog'},'id3_set_command'); + $id3set =~ s/\Q%A\E/$song->{'album'}/g; + $id3set =~ s/\Q%a\E/$song->{'artist'}/g; + $id3set =~ s/\Q%C\E/$song->{'catalog'}/g; + $id3set =~ s/\Q%c\E/$song->{'comment'}/g; + if(($song->{'genre'} * 1) < 255){$id3set =~ s/\Q%g\E/$song->{'genre'}/g;} + else{$id3set =~ s/ -g %g//g;} + $id3set =~ s/\Q%T\E/$song->{'track'}/g; + $id3set =~ s/\Q%t\E/$song->{'title'}/g; + $id3set =~ s/\Q%y\E/$song->{'year'}/g; + $id3set =~ s/\Q%filename\E//g; + # $id3set =~ s/([\'\"])/\\$1/g; + my $filename = $song->{'file'}; + my $id3tag_command = "$id3set \"$filename\""; + return do_call($id3tag_command); +} + +sub rename_file +{ + my ($self,$filename,$song) = @_; + my $ren_pattern = get_catalog_setting($self,$song->{'catalog'},'rename_pattern'); + #my $sort_pattern = get_catalog_setting($self,$song->{'catalog'},'sort_pattern'); + my $basedir; + if( $song->{'file'} =~ m/^(.*)\/.*?$/ ) + { + $basedir = $1; + } + else{ die "Could not determine base directory for $song->{'file'}\n"; } + + # We want to pad track numbers with leading zeros: + if($song->{'track'} < 10) + { + $song->{'track'} = "0".$song->{'track'}; + } + + # we need to clean title,album,artist,comment,genre,track, and year + $song->{'title'} =~ s/[\/]/-/g; + $song->{'album'} =~ s/[\/]/-/g; + $song->{'artist'} =~ s/[\/]/-/g; + $song->{'comment'} =~ s/[\/]/-/g; + $song->{'genre'} =~ s/[\/]/-/g; + $song->{'track'} =~ s/[\/]/-/g; + $song->{'year'} =~ s/[\/]/-/g; + + $ren_pattern =~ s/\Q%A\E/$song->{'album'}/g; + $ren_pattern =~ s/\Q%a\E/$song->{'artist'}/g; + $ren_pattern =~ s/\Q%C\E/$song->{'catalog'}/g; + $ren_pattern =~ s/\Q%c\E/$song->{'comment'}/g; + $ren_pattern =~ s/\Q%g\E/$song->{'genre'}/g; + $ren_pattern =~ s/\Q%T\E/$song->{'track'}/g; + $ren_pattern =~ s/\Q%t\E/$song->{'title'}/g; + $ren_pattern =~ s/\Q%y\E/$song->{'year'}/g; + $ren_pattern =~ s/\Q%filename\E/$song->{'file'}/g; + my $oldfilename = $song->{'file'}; + my $newfilename = $basedir . "/" . $ren_pattern; + # result is backslashes in filename + # $newfilename =~ s/([\'\"])/\\$1/g; + + print "\tNew: $newfilename -- OLD: $oldfilename\n"; + + if(! -e "$newfilename") + { + my $ren_command = "mv \"$oldfilename\" \"$newfilename\""; + $filename = $newfilename; + do_call($ren_command); + return $filename; + } + else + { + print STDERR "File exists: $newfilename\n"; + $filename = $oldfilename; + return $filename; + } +} + +sub sort_file +{ + my ($self, $filename, $song) = @_; + my $basename; + my $basedir; + if( $song->{'file'} =~ m/^(.*)\/(.*?)$/ ) + { + $basename = $2; + $basedir = $1 + } + else{ die "Could not determine base name for $song->{'file'}\n"; } + + # we need to clean title,album,artist,comment,genre,track, and year + $song->{'title'} =~ s/[\/]/-/g; + $song->{'album'} =~ s/[\/]/-/g; + $song->{'artist'} =~ s/[\/]/-/g; + $song->{'comment'} =~ s/[\/]/-/g; + $song->{'genre'} =~ s/[\/]/-/g; + $song->{'track'} =~ s/[\/]/-/g; + $song->{'year'} =~ s/[\/]/-/g; + + my $location = get_catalog_setting($self,$song->{'catalog'},'sort_pattern'); + $location =~ s/\Q%A\E/$song->{'album'}/g; + $location =~ s/\Q%a\E/$song->{'artist'}/g; + $location =~ s/\Q%C\E/$song->{'catalog'}/g; + $location =~ s/\Q%c\E/$song->{'comment'}/g; + $location =~ s/\Q%g\E/$song->{'genre'}/g; + $location =~ s/\Q%T\E/$song->{'track'}/g; + $location =~ s/\Q%t\E/$song->{'title'}/g; + $location =~ s/\Q%y\E/$song->{'year'}/g; + # result is wrong paths + # $location =~ s/([\'\"])/\\$1/g; + + create($location); + + # The basename is calculated so we can see if the file already exists + if(! -e "$location/$basename") + { + my $cmd = "/bin/mv \"".$song->{'file'}."\" \"$location\""; + my $ret = do_call($cmd); + if(empty_dir($basedir)) + { + print "Removing empty directory $basedir\n"; + $cmd = "/bin/rmdir \"$basedir\""; + do_call($cmd); + } + $filename = $location."/".$basename; + return $filename; + } + else + { + print STDERR "File exists: $location/$basename\n"; + $filename = $song->{'file'}; + return $filename; + } +} + +sub usage +{ + my $usage = qq{ + fileupdate [--id3|--rename|--rename_all|--sort|--sort_all|--all] [--help] [--pretend] [--verbose] + --pretend Display command taken, without actually doing anything. + + --id3 Update id3 tags for all files flagged with 'id3' + + --rename Rename files flagged with 'rename' + + --rename_all Renames all files based on id3 info + + --sort Sort files flagged with 'sort' + + --sort_all Sort all files based on id3 info + + --all Performs id3 update, rename, and sort + for all files flagged with 'id3' + --verbose Shows detailed information about what's happening. + + --help This message + }; + die $usage; +} + +sub do_call +{ + my @cmd = @_; + my $return = 0; + + if($verbose && !$pretend){ print "@cmd\n";} + if($pretend){ print "@cmd\n"; } + else + { + $return = system @cmd; + } + return $return; +} + +sub create +{ + my ($path) = @_; + if(! -e $path) + { + return do_call("mkdir","-p",$path); + } + return 1; +} + +# empty_dir borrowed from Tom Phoenix (rootbeer@teleport.com) +# posted in comp.lang.perl.misc on 3/21/97 + +sub empty_dir ($) +{ + local(*DIR, $_); + return unless opendir DIR, $_[0]; + while (defined($_ = readdir DIR)) { + next if /^\.\.?$/; + closedir DIR; + return 0; + } + closedir DIR; + 1; +} +1; diff --git a/bin/genres.txt b/bin/genres.txt new file mode 100755 index 00000000..968f68c1 --- /dev/null +++ b/bin/genres.txt @@ -0,0 +1,148 @@ +1.Classic Rock +2.Country +3.Dance +4.Disco +5.Funk +6.Grunge +7.Hip-Hop +8.Jazz +9.Metal +10.New Age +11.Oldies +12.Other +13.Pop +14.R&B +15.Rap +16.Reggae +17.Rock +18.Techno +19.Industrial +20.Alternative +21.Ska +22.Death Metal +23.Pranks +24.Soundtrack +25.Euro-Techno +26.Ambient +27.Trip-Hop +28.Vocal +29.Jazz+Funk +30.Fusion +31.Trance +32.Classical +33.Instrumental +34.Acid +35.House +36.Game +37.Sound Clip +38.Gospel +39.Noise +40.AlternRock +41.Bass +42.Soul +43.Punk +44.Space +45.Meditative +46.Instrumental Pop +47.Instrumental Rock +48.Ethnic +49.Gothic +50.Darkwave +51.Techno-Industrial +52.Electronic +53.Pop-Folk +54.Eurodance +55.Dream +56.Southern Rock +57.Comedy +58.Cult +59.Gangsta +60.Top 40 +61.Christian Rap +62.Pop/Funk +63.Jungle +64.Native American +65.Cabaret +66.New Wave +67.Psychadelic +68.Rave +69.Showtunes +70.Trailer +71.Lo-Fi +72.Tribal +73.Acid Punk +74.Acid Jazz +75.Polka +76.Retro +77.Musical +78.Rock & Roll +79.Hard Rock +80.Folk +81.Folk-Rock +82.National Folk +83.Swing +84.Fast Fusion +85.Bebob +86.Latin +87.Revival +88.Celtic +89.Bluegrass +90.Avantgarde +91.Gothic Rock +92.Progressive Rock +93.Psychedelic Rock +94.Symphonic Rock +95.Slow Rock +96.Big Band +97.Chorus +98.Easy Listening +99.Acoustic +100.Humour +101.Speech +102.Chanson +103.Opera +104.Chamber Music +105.Sonata +106.Symphony +107.Booty Bass +108.Primus +109.Porn Groove +110.Satire +111.Slow Jam +112.Club +113.Tango +114.Samba +115.Folklore +116.Ballad +117.Power Ballad +118.Rhythmic Soul +119.Freestyle +120.Duet +121.Punk Rock +122.Drum Solo +123.A capella +124.Euro-House +125.Dance Hall +126.Goa +127.Drum & Bass +128.Club-House +129.Hardcore +130.Terror +131.Indie +132.BritPop +133.Negerpunk +134.Polsk Punk +135.Beat +136.Christian Gansta Rap +137.Heavy Metal +138.Black Metal +139.Crossover +140.Contemporary Christian +141.Christian Rock +142.Merengue +143.Salsa +144.Thrash Metal +145.Anime +146.JPop +147.Synthpop +255.Unknown diff --git a/bin/init b/bin/init new file mode 100755 index 00000000..6d396cf5 --- /dev/null +++ b/bin/init @@ -0,0 +1,11 @@ +#! /usr/bin/perl +# Copyleft Ampache.org + +# This script loads up the path to the perl module +# + +use FindBin qw($Bin); +use lib "$Bin/../lib/perl/Local/Ampache/blib/lib"; +use Local::Ampache; +$ampache = new Local::Ampache("$Bin/.."); + diff --git a/bin/moosic b/bin/moosic new file mode 100755 index 00000000..1f9460ac --- /dev/null +++ b/bin/moosic @@ -0,0 +1,6 @@ +#!/usr/bin/python + +import sys +from moosic.client.cli.main import main + +main(sys.argv) diff --git a/bin/moosicd b/bin/moosicd new file mode 100755 index 00000000..70313995 --- /dev/null +++ b/bin/moosicd @@ -0,0 +1,6 @@ +#!/usr/bin/python + +import sys +from moosic.server.main import main + +main(sys.argv) diff --git a/bin/parse_m3u.php.inc b/bin/parse_m3u.php.inc new file mode 100644 index 00000000..9b720bb3 --- /dev/null +++ b/bin/parse_m3u.php.inc @@ -0,0 +1,47 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +/* Name of the filename who's tags you want to read */ +$filename = "/data/music/Live/L/Life'll Kill Ya/Warren Zevon.m3u"; + + +$no_session = '1'; +require ("../modules/init.php"); + +$handle = fopen($filename,'r'); + +$data = fread($handle,filesize($filename)); + +$results = explode("\n",$data); + +foreach ($results as $value) { + $value = trim($value); + if (preg_match("/\.[A-Za-z0-9]{3}$/",$value)) { + echo "$value \n"; + $sql = "SELECT id FROM song WHERE file LIKE '%" . sql_escape($value) . "'"; + $db_results = mysql_query($sql, dbh()); + $foo = mysql_result($db_results,'id'); + echo "\t Results: " . $foo . "\n"; + print_r($foo); + } + +} // end foreach + +?> diff --git a/bin/print_amazon.php.inc b/bin/print_amazon.php.inc new file mode 100755 index 00000000..09118ae7 --- /dev/null +++ b/bin/print_amazon.php.inc @@ -0,0 +1,42 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +/* Name of the filename who's tags you want to read */ +$search['album_name'] = "Ariels"; +$search['artist_name'] = "Bent"; + + + +$no_session = '1'; +require ("../modules/init.php"); +echo "<pre>\n"; +$amazon = new AmazonSearch(conf('amazon_developer_key')); +// Prevent the script from timing out +set_time_limit(0); +$search_term = $search['artist_name'] . " " . $search['album_name']; +$amazon->search(array('artist' => $search['artist_name'], 'album' => $search['album_name'], 'keywords' => $serch_term)); +$amazon->lookup($amazon->results); + +echo "Search Term: $search_term\n"; +echo "Artist: " . $search['artist_name'] . " AND Album: " . $search['album_name'] . "\n"; +print_r($amazon->results); +echo "\n</pre>"; +?> + diff --git a/bin/print_tags.php.inc b/bin/print_tags.php.inc new file mode 100644 index 00000000..b199eeea --- /dev/null +++ b/bin/print_tags.php.inc @@ -0,0 +1,34 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +/* Name of the filename who's tags you want to read */ +$filename = "/data/music/Upload/woo.spx"; + + +$no_session = '1'; +require ("../modules/init.php"); +echo "<pre>"; +$info = new Audioinfo(); +$results = $info->info($filename); + + +print_r($results); +?> + diff --git a/config/.htaccess b/config/.htaccess new file mode 100644 index 00000000..3a428827 --- /dev/null +++ b/config/.htaccess @@ -0,0 +1 @@ +Deny from all diff --git a/config/ampache.cfg.php.dist b/config/ampache.cfg.php.dist new file mode 100644 index 00000000..74ed1583 --- /dev/null +++ b/config/ampache.cfg.php.dist @@ -0,0 +1,461 @@ +##<?php exit(); ?>## +#################### +# General Config +#################### +[conf] +#################### + +#################### +# Path Vars +#################### + +# The path to your ampache install +# Do not put a trailing / on this path +# For example if your site is located at http://localhost +# than you do not need to enter anything for the web_path +# if it is located at http://localhost/music you need to +# set web_path to /music +# DEFAULT: "" +#web_path = "" + + +# Lang (define the locale you want to use +# this is a cheeseball fix for now it will be smarter +# later. +# DEFAULT: en_US +#lang = "en_US" + +#################### +# The libglue Vars # +#################### +[libglue] +#################### + +### +# Below are the variables for the Local Database that will do Auth +### + +# Hostname of your Database (default is localhost) +# DEFAULT: localhost +local_host = localhost + +# Name of your ampache database (default is ampache) +# DEFAULT: ampache +local_db = ampache + +# Username for your ampache database +# DEFAULT: "" +local_username = username + +# Password for your ampache database (can't be blank!) +# DEFAULT: "" +local_pass = password + +# Login Length in seconds for local logins +# DEFAULT: 900 +local_length = 900 + +# This is the DOMAIN for the cookie that stores your session key +# this must be set to the domain of your host or you will not be +# able to log in make sure you including the leading . +# This is not needed unless you are using libglue for more than one +# website, and you are using SSO +# DEFAULT: "" +#sess_domain = .yourwebsite.com + +# Name of the Session/Cookie that will sent to the browser +# default should be fine +# DEFAULT: ampache +sess_name = ampache + +# Lifetime of the Cookie, 0 == Forever (until browser close) , otherwise in terms of seconds +# DEFAULT: 0 +sess_cookielife = 0 + +# Is the cookie a "secure" cookie? +# DEFAULT: 0 +sess_cookiesecure = 0 + +# Path your copy of libglue (Included with Ampache) +# Uncomment this if you have moved libglue to a non-standard location +# DEFAULT: /libglue +#libglue_path = "/libglue" + +# Pre-Defined Error messages +# you should not need to edit these +empty_field = "You left one or more fields empty. Please enter both your username and password to log in." +bad_auth_cred = "Unable to authenticate using this service: This is most likely a configuration mistake by the site administrator." +user_not_found = "Username not found." +login_failed = "Bad username or password." +connect_error = "Could not connect to authentication server." + +#################### +# The conf vars! # +#################### +[conf] +#################### + +# Nuff Said +# DEFAULT: Ampache :: For The Love of Music +site_title = "Ampache :: For The Love Of Music" + +# Use Access List +# Toggle this on if you want ampache to pay attention to the access list +# and only allow streaming/downloading/xml-rpc from known hosts by default +# xml-rpc will not working without this on. +# DEFAULT: false +#access_control = "false" + +# Require Session +# If this is set to true ampache will make sure that the URL passed when +# attempting to retrive a song contains a valid Session ID This prevents +# others from guessing URL's +# DEFAULT: true +require_session = "true" + +# Use XML-RPC +# Allow XML-RPC connections, if you don't want _any_ possibility of your +# catalog being streamed from another location comment this out +# DEFAULT: false +#xml_rpc = "false" + +# This setting allows/disallows using zlib to zip up an entire +# playlist/album for download. Even if this is turned on you will +# still need to enabled downloading for the specific user you +# want to be able to use this function +# DEFAULT: false +#allow_zip_download = "false" + +# This setting turns on/off public registration. It is +# recommended you leave this off, as it will allow anyone to +# sign up for an account on your server. +# DEFAULT: false +# THIS IS CURRENTLY BROKEN! +#allow_public_registration = "false" + +# This sets which ID3 tag takes precedence. +# we've found for those of you who don't have +# good v2 tags it's sometimes nice to keep the v1 +# until you've fixed v2 +# POSSIBLE VALUES: id3v1 id3v2 +# DEFAULT: id3v2,id3v1 +id3tag_order = "id3v2" +id3tag_order = "id3v1" + +# Un comment if don't want ampache to follow symlinks +# DEFAULT: false +#no_symlinks = "false" + +# Use auth? +# If this is set to "Yes" ampache will require a valid +# Username and password. If this is set to no then ampache +# will not ask you for a username and password. No is only +# recommended for internal only instances +# DEFAULT true +use_auth = "yes" + +# This options will turn on/off Demo Mode +# If Demo mode is on you can not play songs or update your catalog +# in other words.. leave this commented out +# DEFAULT: false +#demo_mode = "false" + +# Memory Limit +# This defines the "Min" memory limit for PHP if your php.ini +# has a lower value set Ampache will set it up to this. If you +# set it below 16MB getid3() will not work! +# DEFAULT: 16 +#memory_limit = 16 + +# Album Art Preferred Filename +# Specify a filename to look for if you always give the same filename +# i.e. "folder.jpg" Ampache currently only supports jpg/gif and png +# Especially useful if you have a front and a back image in a folder +# comment out if ampache should search for any jpg,gif or png +# DEFAULT: folder.jpg +#album_art_preferred_filename = "folder.jpg" + +# Album Art Gather Order +# Simply arrange the following in the order you would like +# ampache to search if you want to disable one of the search +# method simply comment it out valid values are +# POSSIBLE VALUES: id3 folder amazon +# DEFAULT: id3,folder,amazon +album_art_order = "id3" +album_art_order = "folder" +album_art_order = "amazon" + +# Album Art in Now Playing +# Set this to true if you want the now playing box to display +# album art from said album +# DEFAULT: true +play_album_art = "true" + +# Amazon Developer Key +# This is needed in order to actually use the amazon album art +# DEFAULT: false +#amazon_developer_key = "" + +# Debug +# If this is enabled Ampache will get really chatty +# warning this can crash browser during catalog builds due to +# the amount of text that is dummped out this will also cause +# ampache to write to the log file +# DEFAULT: false +#debug = "false" + +# Path to Log File +# This defines where you want ampache to log events to +# this will only happen if debug is turned on. Do not +# include trailing slash. Default is /tmp +# DEFAULT: /tmp +#log_path = "/tmp" + +# Max Upload Size +# This sets what the max filesize for an uploaded +# file, this is good at preventing someone from +# filling up your HDD. It is mesured in bytes +# Example 1024 = 1K, 1048576 = 1MB +# Default size limit is 10Mb +# DEFAULT: 10485760 +max_upload_size = "10485760" + +# Charset of generated HTML pages +# Default of iso-8859-1 should work for most poeple +# DEFAULT: iso-8859-1 +site_charset = iso-8859-1 + +########################################################## +# These Option Control which playback methods are allowed +########################################################## + +# Stream Playback +# Disable this if you don't want to allow people to stream +# using HTTP without downsampling +# DEFAULT: true +allow_stream_playback = true + +# Downsampling Playback +# Disable this if you don't want to allow people to downsample +# songs before they are streamed +# DEFAULT: false +#allow_downsample_playback = false + +# LocalPlay Playback +# Disable this if you don't want to allow people to pick the +# local playback method +# DEFAULT: false +#allow_local_playback = false + +# MPD Playback +# Disable this if you don't want to allow people to push things +# to a MPD server as defined below +# DEFAULT: false +#allow_mpd_playback = false + +# Icecast Playback +# Disable this if you don't have an IceCast server to push +# music to +# DEFAULT: false +#allow_icecast_playback = false + +# Slim Server Playback +# Disable this if you don't have a SlimServer to push music +# to +# DEFAULT: false +#allow_slim_playback = false + +####################################################### +# These options control how searching works +####################################################### + +# choices are: artist,album,song_title,song_genre,song_year,song_bitrate,song_min_bitrate,song_filename +# DEFAULT: song_title +search_field = song_title + +# choices are: exact,fuzzy +# DEFAULT: fuzzy +search_type = fuzzy + +####################################################### +# This option controls what Ampache sends for the Stream name. This +# is most valuable when then 'Type of Playback' is set to downsample. +# because lame seems to strip id3 tags. if you want the Ampache default +# just leave this option commented out. +# +# the format supports the followning options: +# +# %A = album name +# %a = artist name +# %C = catalog path +# %c = id3 comment +# %g = genre +# %T = track number +# %t = song title +# %y = year +# %basename = current filename (just the actual filename) +# %catalog = catalog name +# %filename = current filename (full path) +# %type = song type (mp3, ogg, ...) +# +# DEFAULT: %a - %A - %t +#stream_name_format = %a - %A - %t + + +####################################################### +# These options control the down-sampling feature +# this requires you to install some applications such +# as lame that can re-encode the mp3 for you. +# we recommend mp3splt and lame +# %FILE% = filename +# %OFFSET% = offset +# %SAMPLE% = sample rate +# %EOF% = end of file in min.sec +# DEFAULT: mp3splt -qnf "%FILE%" %OFFSET% %EOF% -o - | lame --mp3input -q 3 -b %SAMPLE% -S - - +downsample_cmd = mp3splt -qnf "%FILE%" %OFFSET% %EOF% -o - | lame --mp3input -q 3 -b %SAMPLE% -S - - + +####################################################### +# These options control the "local play" feature. This requires +# a playlist manager such as moosic, winamp, xmms etc which +# can be controlled via command line. +# The defaults below are for moosic, a python based music +# player daemon. You must currently start the daemon +# yourself, because it doesn't fork right for launch +# inside apache. +# Valid replacements are: +# %URL% = url to the song +# %AMOUNT% = amount to increase or decrese the volume by (optional) +# +# HACK altert - run moosicd as www-data user, and +# then set the HOME env var so moosic client +# can find the folder it needs b4 every call... +# Commenting this all out, unless you uncomment it... +### ADD - add song to playlist +# DEFAULT: export HOME='/var/www'; moosic -n add %URL% +#localplay_add = "export HOME='/var/www'; moosic -n add %URL%" +### STOP - stop the playback. +# DEFAULT: export HOME='/var/www'; moosic stop +#localplay_stop = "export HOME='/var/www'; moosic stop" +### PLAY - begin stopped or paused playback. +# DEFAULT: export HOME='/var/www'; moosic play +#localplay_play = "export HOME='/var/www'; moosic play" +### PAUSE - pause the player +# DEFAULT: export HOME='/var/www'; moosic pause +#localplay_pause = "export HOME='/var/www'; moosic pause" +### NEXT - Skip to the next song in the list +# DEFAULT: export HOME='/var/www'; moosic next +#localplay_next = "export HOME='/var/www'; moosic next" +### PREV - Skip to the next song in the list +# DEFAULT: export HOME='/var/www'; moosic previous +#localplay_prev = "export HOME='/var/www'; moosic previous" +### VOLUME UP - increase the volume +# DEFAULT: amixer -q set Master %AMOUNT%%+ +#localplay_volplus = "amixer -q set Master %AMOUNT%%+" +### VOLUME DOWN - decrease the volume +# DEFAULT: amixer -q set Master %AMOUNT%%- +#localplay_volminus = "amixer -q set Master %AMOUNT%%-" +### START - NOT USED - strt the player daemon. +# DEFAULT: export HOME='/var/www'; moosicd & +#localplay_start = "export HOME='/var/www'; moosicd &" +### CLEAR - remove all from playlist and stop playing. +# DEFAULT: export HOME='/var/www'; moosic wipe +#localplay_clear = "export HOME='/var/www'; moosic wipe" +### KILL - stop the player daemon +# DEFAULT: export HOME='/var/www'; moosic die +#localplay_kill = "export HOME='/var/www'; moosic die" +### This must return a 1 line "status report" which is +### displayed under the player controls. +# DEFAULT: export HOME='/var/www'; moosic state|grep 'items in the' +#localplay_status = "export HOME='/var/www'; moosic state|grep 'items in the'" + +####################################################### +# these options allow you to configure your rss-feed +# layout. rss exists of two parts, main and song +# main is the information about the feed +# song is the information in the feed. can be multiple +# items. +# +# +# rss_main_title = the title for your feed. +# DEFAULT: Ampache for the love of Music +rss_main_title = Ampache for the love of Music +# rss_main_description = the description for your feed +# DEFAULT: Rss feed for Ampache so you can monitor who is listening to what +rss_main_description = Rss feed for Ampache so you can monitor who is listening to what +# rss_main_copyright = here you can enter copyright information if you wish +# DEFAULT: copyright (c) Speedy B for Ampache +rss_main_copyright = copyright (c) Speedy B for Ampache +# rss_main_language = the feed language. Some feed readers use this. +# DEFAULT: nl +rss_main_language = nl +# rss_song_description = The description of the song. +# It has to start with <![CDATA[ +# and end with ]]>. this is because xml wont parse if strange +# characters are used in the id3-tag +# usable items: +# $song->f_title +# $song->f_album +# $user->fullname +# $artist +# $album +# DEFAULT: <![CDATA[$song->f_title @ $album played by $user->fullname]]> +rss_song_description = <![CDATA[$song->f_title @ $album played by $user->fullname]]> +###################################################### + +####################### +# ICECAST2 Settings # +####################### +# These settings are for the ICECAST2 support +# built into Ampache. +###################################################### +# Tracklist Filename +# This defines the file that the tracklist +# for icecast is written to, this file must +# be writeable by the web server process +# DEFAULT: /tmp/tracklist.txt +#icecast_tracklist = "/tmp/tracklist.txt" + +# Icecast Command +# This is the command that is run when ampache +# attempts to start up icecast. %FILE% represents +# the icecast_tracklist variable (Filename) +# DEFAULT: /usr/local/bin/ices -c /usr/local/etc/ices.conf -F %FILE% -B +#icecast_command = "/usr/local/bin/ices -c /usr/local/etc/ices.conf -F %FILE% -B" + + +##################################################### + +################### +# MPD Settings # +################### +# These settings are for the MPD support +# built into Ampache. +##################################################### +# MPD Port +# This defines which port that ampache attempts to +# connect to MPD on. +# DEFAULT: 6600 +#mpd_port = "6600" + +# MPD Hostname +# This is the hostname of the computer running MPD +# DEFAULT: localhost +#mpd_host = "localhost" + +# MPD Password +# This is the password for the MPD server +# DEFAULT: "" +#mpd_pass = "" + + +# MPD Method +# This is the method you want to use to pass your +# music to your MPD player. Possible values are +# file and url. I highly recommend using the URL +# method as it requires less configuration. +# POSSIBLE VALUES: file url +# DEFAULT: file +#mpd_method = "file" + + +##################################################### diff --git a/config/ampache.cfg.php.dist.de b/config/ampache.cfg.php.dist.de new file mode 100644 index 00000000..6ca2c172 --- /dev/null +++ b/config/ampache.cfg.php.dist.de @@ -0,0 +1,247 @@ +#################### +# Hauptkonfiguration +#################### +[conf] +#################### + +#################### +# Pfadvariablen +#################### + +# Der Pfad zur Ampache Installation +# WICHTIG: Kein / hinter das Ende der Adresse setzen! +# Nehmen wir an, Ampache ist unter http://localhost +# erreichbar, dann muss hier nichts eingegeben werden. +# Falls Ampache jedoch z.B. unter http://localhost/music +# liegt, so wird an dieser stelle /music angegeben. +#web_path = "" + + +# Lang definiert die Sprache, die genutzt wird. +#lang = "fr_FR.UTF-8" + +######################### +# Die libglue Variablen # +######################### +[libglue] +#################### + +# Unterhalb sind die Variablen für die Datenbank, mit der +# die Benutzer und auch die MP3-Informationen gespeichert werden. + +# Hostname des Servers (Standard ist localhost) +local_host = localhost + +# Name der Datenbank (Standard ist ampache) +local_db = ampache + +# Benutzername für die Ampache-Datenbank +local_username = username + +# Passwort für die Datenbank von Ampache (Darf nicht leer gelassen werden!) +local_pass = password + +# Dauer einer Loginsession +local_length = 900 + + +# Dies ist die Domain, fuer die das Cookie, in dem der Sessionkey gespeichert wird, +# ausgestellt wird. Diese Variable muss die Domain oder der Host des Systems sein. +# Andernfalls wird es unmoeglich sein, sich einzuloggen. +# Bitte ueberpruefe, ob zu Beginn ein . steht. (erforderlich!) +# Dies ist jedoch nicht notwendig, solange du die libglue fuer mehr als eine Seite +# nutzt. +# sess_domain = .yourwebsite.com + +# Name der Session/des Cookie, dass zum Browser geschickt wird. +# Der vorgebene Wert sollte ausreichen. +sess_name = ampache + +# Lebenszeit des Cookies. 0 == Immer (bzw. bis der Browser geschlossen wird), +# andernfalls wird hier die Lebenszeit in Sekunden erwartet +sess_cookielife = 0 + +# Ist das Cookie ein "sicheres" Cookie? +sess_cookiesecure = 0 + +# Pfad zur libglue (ist bei Ampache dabei) +# Kommentiere dies aus, wenn die libglue an einem anderen als dem Standardort liegt. +#libglue_path = "/data/ampache/libglue" + +# Vordefinierte Fehlermeldungen. +# Diese sollten nicht editiert werden +empty_field = "Du hast eines oder mehrere Felder frei gelassen. Bitte gebe deinen Benutzernamen und das Passwort ein, um dich einzuloggen." +bad_auth_cred = "Anmeldung fehlgeschlagen. Dies liegt oft an einem Konfigurationsfehler seitens des Admins" +user_not_found = "Benutzername nicht gefunden." +login_failed = "Falscher Benutzername oder Passwort." +connect_error = "Konnte nicht zum Anmeldungsserver verbinden." + +# Falls diese Datei existiert, kann sich niemand einloggen. +# Standard ist $prefix . /libglue/gone.fishing +# Kommentiere dies aus, falls die Datei anders heissen soll +#stop_auth = "/data/music/ampache/libglue/gone.fishing" + +################################# +# Die Konfigurationsvariablen # +################################# +[conf] +#################### + +# Titel der Seite +site_title = "Ampache for the love of Music" + +# Benutzer Zugriffsliste +# Schalte dies ein, falls Ampache auf die Zugrifssliste achten soll, und +# Streaming/Downloading/XML-RPC nur von bekannten Hosts erlauben soll. +# XML-RPC funktioniert nicht, ohne dass dieses angeschaltet ist. +#access_control = "true" + +# Erzwinge Session +# Falls dies Variable auf true gesetzt ist, prueft Ampache, ob die URL +# eine gueltige Session ID besitzt. Dies hilft Einbrueche zu verhindern, +# die durch das Erraten von Session IDs auftreten koennen. +#require_session = "true" + +# Benutze XML-RPC +# Erlaube XML-RPC Verbindungen. Falls du nicht willst, dass dein Katalog +# von einem anderen Ort aus gestreamt wird, kommentier dies aus. +#xml_rpc = "true" + +# Sperren +# Sperre Lieder, wenn das gleiche Lied bereits gespielt wird. +#lock_songs = "true" + + +# Erzwinge HTTP-Abspielen +# Diese Einstellung ist standardmaeszig an, und erzwingt, dass m3u-URLS +# erstellt werden, selbst wenn du https nutzt. Diese Einstellung wurde +# vorgenommen, da es unseres Wissens keine https mp3-Streamingprogramme +# gibt ... +force_http_play = "true" + +# HTTP Port +# Bitte setze diese Option, wenn du force_http_play nutzt, und wenn dein +# httpd auf einem anderen Port als 80 laeuft. +#http_port = "80" + +# Falls diese Funktion auf false steht, wirst du spuerbare +# Performanceschuebe merken. +# Es ist empfehlenswert, dies auf false zu lassen, da die Funktion eh +# nicht genutzt wird ... :P +do_mp3_md5 = "false" + +# Hier wird das Interval angegeben, in dem der aktuelle Fortschritt +# beim Katalogisieren ausgegeben wird. Bei grossen Katalogen sollte +# diese Zahl moeglichst gering gehalten werden. +catalog_echo_count = "100" + +# Diese Option legt fest, welcher ID3 Tag bevorzugt wird. +# Diese Funktion wurde fuer diejenigen angelegt, die noch keine +# v2-Tags angelegt haben. Somit ist es bei manchen Nutzern hilfreich, +# v1 zu nutzen, bis die Kataloge auf v2 umgestellt sind. +id3tag_order = "id3v2" +id3tag_order = "id3v1" + +# Kommentiere dies aus, wenn du nicht moechtest, dass Ampache +# symlinks folgt. +#no_symlinks = "true" + +# Benutze Authentifizierung? +# Falls dies auf "Yes" gesetzt ist, sind zum Anmelden ein gueltiger +# Benutzername und ein gueltiges Passwort erforderlich. +# Falls dies auf Nein gesetzt ist, fragt Ampache nicht nach einem +# Benutzernamen und Passwort. +# "No" ist nur fuer Testsysteme empfehlenswert. +use_auth = "yes" + +# Kuenstler- & Album Zwischenspeicherlimit +# Um die Katalog-Aktualisierungen zu beschleunigen, und um die Last +# auf MySQL zu reduzieren nutzen wir eine Art Zwischenspeicher, wo +# anhand einer ID MySQL-Abfragen gespeichert werden. +# Du kannst diese Option frei nach deinem belieben abaendern ... +album_cache_limit = "25" +artist_cache_limit = "50" + +# Diese Option schaltet den Demomodus wahlweise an oder aus. +# Falls der Demomodus angeschaltet ist, kannst du keine Songs +# abspielen, oder den Katalog aktualisieren. In anderen Worten .. +# Lass es besser auskommentiert. +# demo_mode = "true" + +# Amazon Developer Key +# Dies ist erforderlich, um die Amazon-Coversuche zu nutzen. +# Unter https://associates.amazon.com/exec/panama/associates/join/developer/application.html +# bekommst du einen solchen Key. +#amazon_developer_key = "" + +# Hier wird das minimale Speicherlimit fuer PHP definiert. Falls +# PHP einen niedrigeren Wert in der php.ini hat, wird Apache den +# hier definierten Wert nutzen. +# Achtung: Den Wert auf keinen Fall unter 16MB setzen, sonst +# funktioniert getid3() nicht mehr! +# memory_limit = 16 + +# Aktualisierungs-Interval +# Falls dieser Wert gesetzt ist, wird Ampache die Hauptseite +# alle X Sekunden aktualisieren (neu laden). +# refresh_interval = 180 + + +####################################################### +# Diese Optionen definieren, wie die Suche ablaeuft. +# Folgende Moeglichkeiten existieren: artist,album,song_title,song_genre,song_year,song_bitrate,song_min_bitrate,song_filename +search_field = song_title +# Suchtypen: fuzzy und exact +search_type = fuzzy + +######################################################### +# Diese Optionen kontrolieren das "Downsampling" feature. +# Jedoch sind hierzu Tools wie lame erforderlich, mit +# denen man mp3s umwandeln kann. +# Wir empfehlen mp3splt und lame. +# %f = Dateiname +# %o = offset +# %s = sample rate +downsample_cmd = mp3splt -qnf "%f" %o EOF -o - | lame --mp3input -q 3 -b %s -S - - +# Temporaere Datei +downsample_tmp = /tmp/ampache.log + +####################################################### +# These options control the "local play" feature. This requires +# a playlist manager such as moosic, winamp, xmms etc which +# can be controlled via command line. +# The defaults below are for moosic, a python based music +# player daemon. You must currently start the daemon +# yourself, because it doesn't fork right for launch +# inside apache. +# Valid replacements are: +# %URL% = url to the song +# Diese Optionen dienen der Konfiguration der "local play" Funktion. +# Dies erfordert ein Programm, dass mit Playlisten umgehen kann, und +# per Kommandozeile kontrolliert werden kann. Beispiele hierfuer +# sind moosic, winamp oder xmms. +# Die untenstehenden Standardwerte gelten fuer moosic, einen +# python-basierten music-player-daemon. Jedoch ist es erforderlich, +# den Daemon selber zu starten, da dies nicht automatisch passiert. +# Moegliche Veraenderungsmoeglichkeiten sind: +# %URL% = URL zum Lied +# +### ADD - Fuege einen Song zur Playlist hinzu +#localplay_add = "export HOME='/var/www'; moosic -n add %URL%" +### STOP - Beende das Abspielen des Songs +#localplay_stop = "export HOME='/var/www'; moosic stop" +### PLAY - Setze einen gestoppten Song fort, oder starte neu +#localplay_play = "export HOME='/var/www'; moosic play" +### PAUSE - Pausiere den Player +#localplay_pause = "export HOME='/var/www'; moosic pause" +### NEXT - Waehle den naechsten Song in der Liste +#localplay_next = "export HOME='/var/www'; moosic next" +### START - NICHT GENUTZT - starte den Player +#localplay_start = "export HOME='/var/www'; moosicd &" +### CLEAR - Alles von der Playlist loeschen und Abspielen starten +#localplay_clear = "export HOME='/var/www'; moosic wipe" +### KILL - Beende den Player +#localplay_kill = "export HOME='/var/www'; moosic die" +### Diese Zeile sollte einen 1 Zeilen langen "Status Report" umfassen, +### der unter den Player-Bedienelementen dargestellt wird. +#localplay_status = "export HOME='/var/www'; moosic state|grep 'items in the'" diff --git a/config/motd.php.dist b/config/motd.php.dist new file mode 100644 index 00000000..59daab53 --- /dev/null +++ b/config/motd.php.dist @@ -0,0 +1,3 @@ +<!-- This file contains any "Message Of The Day" Type information --> +<!-- It will be included below the log-in form on the login page. --> + diff --git a/docs/CHANGELOG b/docs/CHANGELOG new file mode 100755 index 00000000..e0c00ac6 --- /dev/null +++ b/docs/CHANGELOG @@ -0,0 +1,771 @@ +-------------------------------------------------------------------------- +--------- Ampache -- CHANGELOG --------- +-------------------------------------------------------------------------- + +-------------------------------------------------------------------------- + v.3.3.1: + - Fixed hardcoded HTTP reference in list_header.inc + (Thx hongyi_gao) + - Fixed refresh javascript for main page. + - Fixed <html lang=> tag so that it validates (Thx XGizzmo) + + +-------------------------------------------------------------------------- + v.3.3.1-Beta2 05/22/2005: + - Included new Greyblock Theme (Thx Shieldb) + - Fixed playlists if use_auth == FALSE + - Tweaked CSS classing in an attempt to improve themeing. This + breaks all previous themes. (Thx mkeadle) + - Fixed problem with Color Boxes in IE (Thx rperkins) + - Tweaked the Main page adding most popular albums as well as + spliting out the mpd control and now playing. + (Thx Nedko and reflous) + - Fixed a problem with directories named '0' (Thx Protagonist) + - Fixed lack of seeding of RAND() which would cause Pre PHP 4.2 + to not really have random playlists. + - Fixed a bug where guests could change their own password and + control MPD + - Fixed a ton of class formating inconsistencies as well as tweaked + a few tables. (Thx Rperkins) + - Fixed some consistency issues with where the A-Z listing was + between Albums and Artists, Added Bolding of currently + selected Letter/Number (Thx Rperkins) + - Fixed a problem with the admin preferences where the theme + colors wouldn't reset, if the target theme is the current + one of the user setting it (Thx Nedko) + - Fixed a problem with the CHARSET not being passed correctly + (Thx Nedko) + + +-------------------------------------------------------------------------- + v.3.3.1-Beta1 05/01/2005: + - Added Random Play for Playlists + - Added Per User config option to set ellipse thresholds as well + as some index.php tweaks (Thx Nedko) + - Added support for SPX files. + - Fixed a problem that occurred when a userfield contained a single + quote (username,fullname etc) + - Turkish Translation, Charset iso-8859-9 added (Thx vireas) + - Flipped Actions on MPD Control, clicking on the title now skips + to the song, clicking on number removes it (Thx rastan) + - Tweaked Preferences look, adding color boxes showing the color + of the preference, and misc html/spelling fixes + (Thx Rperkins) + - Added Random On/Off to MPD controls and truncated songs with ... + (Thx Orion88) + - Added ability to pass a URL to MPD allowing it to be on a + different computer than the MP3's this also makes setup of + MPD a lot easier. + - Fixed Connected User Count. + - Fixed random HTML errors that caused custom themes to look wrong + + +-------------------------------------------------------------------------- + v.3.3.1-Alpha2 04/23/2005: + - Added ability to import M3U's as playlists on catalog build and + from the playlist screen, note the m3u must exist on the + server. Uploading from client is not working + - Fixed a bug that caused it always to generate a m3u file when + using downsampling + - Added support for .mpc files + - Added .htaccess and renamed all /bin files to .php.inc so that + the webserver, even if it ignores the .htaccess won't try + to run the scripts + - Fixed ampache.cfg and /docs references in /install.php + (Thx rperkins) + - Fixed a typo that caused ASX playlists to not be populated with + the user_id as they should (Thx weidercs) + - Fixed a problem where when creating a new user it wouldn't take + the values from "Admin Preferences" as it should. + - Fixed catalog toolbox so it uses the classes rather than a + hardcoded color + - Fixed Installer which still had ../ references (Thx fakenick) + - Fixed a few more ../ references + - Fixed redirect to update.php on login after you've already + done the update. (Thx Orion88) + - Fixed login.php so that it loads the theme that is set in the + admin preferences correctly + +-------------------------------------------------------------------------- + v.3.3.1-Alpha1 04/21/2005: + - Added Themeing Ability to Ampache, see /themes/classic for an + example of how to do it + - Added Burgundy Theme (Thx s1amson) + - Moved everything into / instead of /docs you should now be able + to extract ampache directly into your webroot and have it + work perfectly :-) + - Added Config file compare to test.php + - Added config values to control allowed playback methods + - Added SlimServer class *Not Finished + - Tweaked catalog "Total Time" so it's a little more consistent + (Thx Andy Morgan) + - Fixed playback problem with Windows Media Player caused by a + misplaced Partial-Content header entry. + - Renamed ampache.cfg --> ampache.cfg.php and added <?php exit(); ?> + to prevent display of config file in web interface. this is + the first step towards moving away from the /docs style + (Thx s1amson) + +-------------------------------------------------------------------------- + v.3.3 04/17/2005: + - Fixed seeking and lack of http headers during normal playback + (Thx Nikk) + - Fixed random play bug where it wouldn't return any songs due + to a malformed sql statement. (Thx J) + - Fixed a typo that caused the song format to be ignored by play.php + (Thx Nikk) + - Fixed lack of an error message if amazon album art was a search + method, but no developer key was specified + - Fixed the memory allocation code. + - Fixed a lack of status reporting during the album art searches + now prints out Searched 100. . . like all other catalog + functions. + - Added User Registration (Thx Terry) *Not Finished! + - Fixed problem where an error would occur if only one album + art gathering method was selected. + - Fixed problem where it would continue to search for album art + when updating multiple songs from the same album where art + has already been found. + - Added Debug Script for Amazon Album Art search (in /bin) + - Cleaned up some dirty HTML, and redudant functions + - Fixed lack of redirect to the Install page if no config file is + found + - Fixed login page so it respects the values set in the database + for background color etc + - Fixed lack of cookie deletion on logout, and lack of session + removal... + - Added forced Garbage Collection at least 20% of the time. + - Fixed Installation Script, admin/changeme is no longer the + default username/password. Installation script creates + initial admin user + - Fixed html, and lack of web_path definitions on the account + page, also spruced up the look a little bit + - Switched all short tags to long tags (<? --> <?php) + - Fixed preferences so that it doesn't display an input field + if you don't have access to change said preference + - Fixed album art saying it's found when it really wasn't + - Fixed problem where changes to preferences weren't respected + if use_auth = false + - Fixed download, and direct link, both were not respecting + the song->type + - Tweaked m3u generation removing the \ before the filename to + prevent mp3blaster from failing (Thx Rubin) + - Added command line script /bin/compare_config looks at + ampache.cfg.dist and compares it to the ampache.cfg looking + for missing config values. + - Tweaked db update check so it does it on every page load, rather + than just on login.php (so use_auth=no gets checked) + - Fixed problem with user create and user edit where it wasn't doing + any really good error checking, or notifying you when it + failed to update/create + - Tweaked now playing in an attempt to allivate some now playing + floods that people were seeing + +-------------------------------------------------------------------------- + v.3.3-Beta4 03/27/2005: + - Added Batch Download functions (Thx RosenSama) + - Tweaked Main Page format (Thx Nedko Arnaudov) + - Added Full Album/Full Artist option to Random Play + - Fixed Amazon Album Art gathering, changed from SOAP + method to REST method, now works with PHP5 and PHP4! + - Fixed problem with ' being escaped one to many times in a + playlist name + - Tweaked MPD play so it uses the playlist_type and is accessed + by simply selecting the "Play" action + + +-------------------------------------------------------------------------- + v.3.3-Beta3 03/17/2005: + - Fixed a problem with the preferences and display of the logout + button and inability to edit/disable songs/playlists when + use_auth = no + - Added volume controls to local play (Thx Vlad) + - Fixed a problem of importing comments from mp3 files (Thx Cucumber) + - Fixed a typo that caused the account you were logged in as to be + deleted rather then the account you wanted to delete. + - Added pulling missing song info from filename based upon the catalogs + file patterns. + - Improved error logging and handling. + - Added album art dump from database to file system (Thx Cucumber) + - Added show albums with no art on Albums browse page (Thx Cucumber) + - Fixed a problem where Localplay wouldn't return to index as it + should (Thx Jason) + - Added Installation Script (/install.php) + - Fixed lack of an sql_escape on comment which could break inserts + if comment contained " or ' + - Added default log_path and better error message if unable to write + to the file + - Removed _SERVER['PHP_SELF'] reference on alphabet function due + to the fact it doesn't always get passed. + - Removed old setup.php in favor of new install.php + - Upgraded Moosic from 1.2.5 --> 1.5.1 which fixes some playback + issues (Thx soloport) + - Added Prev button to localplay and send the song name to the player + rather than simply song.mp3 (Thx jason) + - Added Customizable Stream Format, see config (Thx Cucumber) + - Added sort by Year on album page. + - Fixed some minor issues with the XMLRPC code. + - Fixed typo in style-sheet (Thx Nedko Arnaudov) + - Added cleaned up favicon (Thx Nedko Arnaudov) + - Added paging and sort by username/fullname & last_seen on + admin user page + - Fixed issue with non-us chr when truncating using ... on + global popular and album/artist views (Thx Nedko Arnaudov) + - Fixed importing of non-us chr from OGG files (Thx Nedko Arnaudov) + - Fixed an issue were duplicate headers would be sent during + downsampling, also remove extra db connection in play/index.php + - Fixed problem where now playing wouldn't show a username if use_auth + was disabled + - Fixed a problem which prevented you from updating a user + - Fixed an error in the logic that caused all art methods to be + searched regardless of config settings + - Added ASX playlists (Thx Samir Kuthiala) + - Added check for Iconv in /test.php + +-------------------------------------------------------------------------- + v.3.3-Beta2 02/09/2005: + - Added config option for site charset defaults to iso-8859-1 + (Thx Nedko Arnaudov) + - Fixed unhandled soapclient errors with PHP5 - Note Amazon + album art search still doesn't work. It just doesn't + return an error. + - Fixed problem with winamp playback on .oggs + - Added "Remember Me" button that overrides local length setting + and sets a 1 year cookie + - Added new RSS page (Thx Speedy B) + - Changed how preferences are handled once again. In the process + fixed numerous bugs with preferences. + - Added Apply To All in admin preferences, letting a full admin + reset a specific pref for all users at once. + - Update Catalog no longer overwrites changes made in the interface + - Added MPD patch (Thx RosenSama) + - Suppress Error in /docs/play/index.php if fopen fails + - Fixed Random playback, it is now actually a random number of songs + in a random order from said artist/album + - Fixed Comment not getting set during song flag (Thx RosenSama) + - Added gimped support for m4a (ITunes) files, Genre and Track # + aren't imported due to getid3() limitations + - Fixed some XML-RPC issues that cropped up with newer versions + of PHP + - Improved fix for Mysql 4.1 PASSWORD function, should always + work now. + - Added basic logging functions (for debug) + - Fixed downsample so it actually looks at the ampache.cfg for the + command to run instead of being hardcoded in + - Added ability to set preferred filename for folder album art search + along with ability to set order of search methods (Thx Mike) + - Started tweaking MPD patch so that it can be accessed as a + play type + + +-------------------------------------------------------------------------- + v.3.3-Beta1 12/26/2004: + - Fixed problem with download not detecting mime types and not seeing + true/false value of preference + - Added Patch from Shine with a _ton_ of gettext updates and an almost + complete German translation! + - Fixed automatic detection of server port (Thx Corsin) + - Fixed missing prefix on Albums by Artist page (Thx ianneub) + - Removed a large chunk of unneeded code from Main page + - Fixed some preferences problems which were allowing users to define + download/upload etc + - Fixed Upload functions created by Lamar to account for other changes + I've made to ampache, upload now shows up in menu bar + if you have upload enabled + - Added CLI catalog_update.php file in /bin that updates all local + catalogs + - Updated nusoap library to newest version (12/15/2004) + - Fixed upload, now requires a readable upload dir before even attempting + to upload, and correctly inserts/quarantines files + - Fixed a problem where the catalog clean wasn't removing files from + playlists when they were removed from the catalog + - Added 'pretty' count of songs checked during catalog clean + - Added simple m3u playlist format and fixed a small typo in the pls + playlists + - Added Direct Link that can be drug to winamp to "append" to + playlists (Thx jason) + - Fixed incorrect redirect on Disable/Enable of songs + - Fixed login problems due to change in HASH style with Mysql 4.1 + - Fixed Albums with multiple artist giving incorrect song count and showing + single artist, rather than "Various" + +-------------------------------------------------------------------------- + v.3.3-Alpha3.1 11/29/2004: + - Fixed two typo's in /docs/playlist.php (Thx smichaelis) + - Added a or die to the table drop in /update.php to prevent silent + failure of update. + - Added check for session support on /test.php + +-------------------------------------------------------------------------- + v.3.3-Alpha3 11/28/2004: + - Fixed duplicate web_path entry in preferences (Thx KlaasVaag) + - Fixed some problems with the flagging single quotes and genre + should now work (Thx Cocobu) + - Added WMA support (Thx Ldary) + - Added new Getid3 version + - Fixed typo that prevented play selected on artist page from + working (Thx tPassive) + - Added WMA Album Art support... maybe + - Fixed problem with [Prev] & [Next] wrapping to a new line + - Added filename used for songs with no title + - Fixed ability to disable last account, or remove last admin account + - Added year to "Albums" view + - Fixed inability to delete playlists + - Fixed a problem introduced while cleaning up /lib/album.php + - Added new Stream class to make adding play types easier + - Added correct PLS file support. Set via a config option + - Added Favicon (Thx Rubin) + - Added German README/INSTALL/MIGRATION/ampache.cfg.dist (Thx phil) + - Added French Translation (Thx Cocobu) + - Removed defunct "findfile" script that was completely broken + - Added 1000 songs & All to random play (Thx clouser) + - Added Folder based search for any .jpg or .gif as album art only + works on catalog update & build (Thx dromio && roark) + - Added config options that define where ampache looks for art + - Fixed logic error on album art page, and redundant checks + - Fixed Non-Us CHR on ogg && id3v1 tags files importing incorrectly + - Fixed some web_path and prefix problems + - Fixed single quotes in folder names preventing the entire directory + from being indexed + - Added New Blank Album Image (Thx Aaron La'gere) + - Added Per Artist & Per Album Update From Tags + - Added GetText to albums.php, playlist.php, lib.php, index.php and + ui.php as well as new messages file. (Thx Shine) + - Added Album art shown on Now Playing (Thx Rubin/Shine) turned on by + setting play_album_art = "true" in ampache.cfg + - Fixed problem where flagged table wasn't getting cleared when you + deleted the song that it referenced + - Added "Guest" user level which can view, but not play or change + anything + - Added user_catalog table for future catalog access control, this + feature is not yet implemented. + - Added FLAC file support + +-------------------------------------------------------------------------- + v.3.3-Alpha2 11/08/2004: + - Improved error checking on Mysql connection it now redirects to + /test.php on failure rather than just throwing an error + - Added upload.php play/pupload.php & templates/show_upload.inc + for upload functionality (Thx Lamar!) + - Fixed a $dbh --> dbh() problem (Thx phil) + - Fixed the preferences requirement on update.php (Thx phil) + - Fixed "Fuzzy Counting" in the README file index. + - Fixed a typo in playlist that caused the header redirect + not to work + - Fixed setting of song->played value (Thx Mkeadle) + - Fixed Admin preferences for user 0 (what new users get) + - Added initial GetText, translation, support + - Added some of the initial French translation using to babblefish + - Fixed a problem with the album art clearing from the db correctly + - Added initial DE translation (Thx Phil) + - Fixed a problem with single-quotes in filenames breaking catalog + builds (Thx Naund) + - Added initial IceCast Support (Thx Thomas) + - Fixed incorrect naming of Local Play variable which caused it + not to work at all (Thx jpolansky) + - Added Valid Session Checking to play code that, if require_session + is set prevents anyone without a valid sid from playing music + - Added Album Year to Albums by Artist View and album table in db + +-------------------------------------------------------------------------- + v.3.3-Alpha1 10/04/2004: + - Fixed non-us chr showing up incorrectly + - Fixed session garbage collection + - Added check for function_exists iconv + - Added check for existence of mysql_query function to test.php + - Fixed a problem with verify single catalog (Thx Framercy) + - Reworked the Preferences, adding most of the non-critical + preferences from the config file and putting them into + the web interface + - Tweaked the DB to work with the new preferences + - Update.php now has a font size and bgcolor (defaults) + - Force short_tag = on in init.php + - Show Albums by Artist only checks DB for album art (faster) + - Added View Full Album Art (click on art on single album page) + - Removed dead code from /modules/lib.php + - Tweaked Personal Stats page. Moved out of lib.php into user + functions and cleaned them up a bit + - Removed extra , from Mail function and fixed From address + http://bugs.ampache.org/bug_update_page.php?bug_id=1 + - Fast Update on Update Catalog function has some "Fuzzy" logic + - If Fast Update isn't checked, Update Catalog looks for album art + in the id3 tags + - Update Catalog now has "Checked 100...." messages like the + add to catalog function + - Added check for soapclient class already existing. (Thx Hopson) + - Tweaked Album Art on Albums by Artist Page (Thx clader) + - Added Play Random & Play All links to Albums By Artist Page + - Removed extra queries from Albumart.php + - Fixed Playlist Delete (Thx kellin) + - use_auth = "no" works as advertised + - Ability to import Album Art on catalog build from id3v2 tags + +-------------------------------------------------------------------------- + v.3.2 08/11/2004: + - Fixed XMLRPC duplicate function problems + - Fixed getid3() issues by manually setting memory limit for php + if current setting is below 16M + - Suppressed errors that occurred when PHP-GD tried to read a gif + image. + - Added auto refresh of index.php (Thx vireas) + - Fixed a problem where saying no to a user delete deleted it + anyway. (Thx Dogsbody) + - Fixed a problem with admins updating users preferences + - Removed Edit button from delete confirmation for playlists + - Improved /test.php a tiny bit + +-------------------------------------------------------------------------- + v.3.2-Beta2 07/12/2004: + - Yet more improvements to album art code, now checks for 1x1 + images if you have GD installed (Thx mikej) + - Fixed a problem where \n or other whitespace would get into + album name + - Fixed catalog update destroying tags on ogg and rm files + - Added RSS feed page (thx speedyb) see /rss.php + - Fixed a problem with libglue that cropped up with 4.3.8 + - Added install.pl (initial release) + - Fixed last seen again.... + - Fixed up the test.php to it actually works correctly + - Added get album art from url Thx gwynnebaer + - Added config option to set default search type Thx gwynnebaer + - Tweaked headers to make them nicer with large numbers Thx gwynnebaer + - Tweaked preferences code so it works as advertised. + - Updated to GETID3() 1.7.1-b1 Woohooo! + +-------------------------------------------------------------------------- + v.3.2-Beta1 07/02/2004: + - Tweaked getid3 library in an attempt to prevent non-fatal + foreach error + - Replaced "no album art" image (thx Gargamale) + - Last Seen now actually works, can be viewed on the user + screen in the admin section + - Fixed a Artist catalog problem introduced with the new + getid3 library + - Now takes into account https vs http using _SERVER['https'] + variable + - Added force_http_play which ignores https and always forms the + urls in the m3u as http, default is on + - Added http_port in case your http server isn't running on port 80 + - Fixed a typo that caused clean_artist not to work with mysql 3.x + - Logical Random play query (Thx Famercry and mikepell) + - Updated the XMLRPC library and hopefully improved it a little :) + - Improved Ampache.pm no longer requires secrets file and automatically + find path information (ignore errors :P) + - Added rename_all & sort_all to fileupdate.pl in /bin + - Fixed a problem where play_type == 'local_play' wouldn't actually + do anything + - Added sweet new album art code from MikeJ that searches Amazon + (Requires Developer Key, see config file) + - Applied some fixes to the album art (Thx gwynnebaer!) + - Added MOTD on the login page (see README) + - Fixed another seek problem (Thx gwynnebaer) + + +-------------------------------------------------------------------------- + v.3.2-Alpha3 06/13/2004: + - Added last_seen to user table now tracks when they last + visited ampache + - Changed the preferences table to key,value pairs makes it + easier to add new preferences without having to update + the database again + - Put in initial down-sampling work + - Updated Ampache.pm and fileupdate.pl (Thanks Matt Shaffer and Nikk) + - Fixed a problem with the play count when you tried to seek + a file. + - Made the single album view a little nicer looking + - Added "Reset Album Art" action that removes the album art from the + database and re-querys the mp3s + - You can now select multiple genres when using Play Random + - Changed default action for albums/artists to browse per Alphi's + recommendation. + - Fixed a bug introduced into the config file. + - Finally fixed web_path so that you only need to define the path + to ampache (ie /music) rather than the full URL such as + http://localhost/music. + - Added another fix so that it takes into account the port when + logging in (was ignoring it before) Thx DogsBody + - Fixed a playback problem where song would reset after the + php max execution time Thx Nicolas Savoret + - Fix for some web_path vs web_host problems Thx Nick Wilson + - Fixed it so that disabling a user actually works now + - Playback now pays attention to disabled status and make sure + uid ends up being a valid user + - Reworked preferences, adding play_type in place of multi-cast + down-sample and local_play + - Down-sampling should now work if play_type is set to down-sample + and you do a little manual configuration + - Tweaked filename passed to players so that oggs work a little + better - Thx Dale Cooper and Gwynnebaer. + - Upgrade to newest GetID3 library - Thx Gwynnebaer! + - Initial support for RM files (Not Tested) + +-------------------------------------------------------------------------- + v.3.2-Alpha2.1 04/27/2004: + - Fixed a problem with the user functions which was handling + passwords in a _very_ bad way. + - Updated the title tag on now_playing + +-------------------------------------------------------------------------- + v.3.2-Alpha2 04/24/2004: + - Put Prefix back in Artist name + - Fixed Text echoed out during a catalog update, and made the + catalog update actually work! + - Fixed Prefix problems on album view and simplified the code + for displaying albums. + - Albums by Artist page now shows all of the Album art. (Thx MrBlahh) + - New Blank Album Image + - Weighted Random Play (Thx Mikepell) + - Fixed a the removal of disabled songs. + - Cleaned up and fixed basic user functions + - Reorganized the Main Page moving recently added albums/artists + on to the front page. + - Fixed Catalog Functions so that it removes old stats when + you clean/update/delete. + - Updated Database getting ready for XML-RPC + - Reintroduced Access Lists allowing for XML-RPC permissions + and stream/download permissions + - Added XML-RPC code back in (Experimental & DANGEROUS!!) + must be turned on in ampache.cfg + - Moved Config File to $ampache/config from $ampache/modules + makes more sense there.... + - First step towards quick time playback capabilities (Thx Nick) + - Fixed a problem with catalog genre names that put an extra + slash in genres with " or '. + - Added Clear Now Playing under catalog tools in case you get some + funky data stuck in the now playing queue. + - Fixed user deletion. Preferences and stats were being left behind + - Hopefully fixed Album/Artist/Song Cleaning so that it works with + Mysql 3.23 (We were using 4.0+ sql syntax) + - Updated Now Playing to show album link and shortened song title + if needed + - User functions should always return to the user page when done. + +-------------------------------------------------------------------------- + v.3.2-Alpha1 03/23/2004: + - Added Now Playing + - Updated the migration tool (update.php) + - Added ability to turn on/off ability to download songs + - First step towards upload capabilities + - Early version of "Local Play" + - Per User preferences + - "Legalize" option that only allows one copy of a song to be played + at any one time. + - Fixes for popular songs being incorrectly reported. + - Fixes to Play All Songs by Artist/Album + - Improvements to playlists. + - Many other minor bug fixes + +-------------------------------------------------------------------------- + v.3.1.3 01/11/2004: + - Fixed a link problem on the logout page + - Fixed Full Album not being displayed in the title tag on show_songs + - Doesn't try to show album art for Unknown Albums + - Fixed a config problem introduced in 3.1.2 + - Stops looking for album art after finding a single one (reduces load) + + +-------------------------------------------------------------------------- + v.3.1.2 12/30/2003: + - Fixed Single Song Per Album bug if the album title contained + a single-quote + - Fixed problems with quotes, and special chr in id3 tags + - Fixed a few html problems + - New version of Getid3() see www.getid3.org + - Fixed Connected user count looking incorrect + - Cleaned up HTML in show_songs. + - Now returns an error if adding a user fails + - Fixed Stat Clean and Catalog Delete still happening even if you + clicked no + - Removed Clean All Catalogs and Access link because those + features are currently broken + +-------------------------------------------------------------------------- + v.3.1.1 12/26/2003: + - Fixed a problem with the clean catalog function not actually + working correctly, also added a check-box to auto delete + dead songs + - Fixed a problem with readconfig not working on windows + - Fixed a problem where dead songs would get added to a playlist + - Added a missing break in the case function of admin/catalog.php + - Removed preferences tab because it doesn't actually work + - Made Catalog Update not display errors if it can't find the file + - Catalog Update should no longer time out + - Added in some escaping for single quotes in some extra id3 + fields + +-------------------------------------------------------------------------- + v.3.1, 12/23/2003: + - Fixed problem with quick search on artist only allowing 1 chr + - Fixed broken image on albums with no art + - General HTML cleanup + - Changed link name to "Play" from m3u + - Ordered genre pull down by name rather than ID + - Make it not look for album art if it wasn't viewing an album + - Removed random play stuff from album page + +-------------------------------------------------------------------------- + v.3.1-Beta2, 12/16/2003: + - Fixed double http:// in a few places (Thx Lamar) + - Typo in Form variable (Thx Lamar) + - Album Playlist Fixes (Thx Lamar) + - Added trailing slash to admin links (Thx MrBlahh) + - Removed from register globals requirements + +-------------------------------------------------------------------------- + v.3.1-Beta1, 12/10/2003: + - Completed support for OGG files (Thx Hopson for original code) + - Fixed Viewing Albums + - Addtype no longer required in apache config. Headers + are now passed in that delicate grey zone where all the + browsers we can find seem to work.. (Thx Rubin) + - Fixed it so that you no longer have to edit init.php (Thx Rubin) + - Added view all songs from this artist + - Hopefully fixed libglue once and for all... + +-------------------------------------------------------------------------- + v.3.1-Alpha5, 11/29/2003: + - Added Duplicate song checker to catalog tools (Thx Alphi) + - Fixed missing Genre check when updating id3 tags + - Added Disable Option for Admins when showing songs + - Removed some un-needed files + - Fixed 'Fuzzy' Math in list_header (Thx Andy) + - Fixed stats on the main page (Thx Andy) + - General Code Cleanup (Thx Andy) + - Fixed double login problem + +-------------------------------------------------------------------------- + v.3.1-Alpha4, 11/27/2003: + - Yet more search options + - Fixed a few more admin tool issues + - Added a play via Genre + Catalog (Thx Rubin) + - Fixed Random Play (Rubin) + - Fixed catalog so that if file exists but isn't readable + it doesn't add it to the catalog empty (Thx Andy Morgan) + - Fixed update.php to check what version of the db is being run + and update accordingly (Rubin) + - Added bare bones for Album Art support from the mp3 files (Rubin) + - Fixed Play Selected from Albums/Artists + +-------------------------------------------------------------------------- + v.3.1-Alpha3, 11/25/2003: + - Fixed some installation problems + - Added a few new search options + - Fixed catalog delete, and other misc link problems + - Added migration tools (Thx Andy Morgan) + - Re-worked Genre + +-------------------------------------------------------------------------- + v.3.1-Alpha2, 11/24/2003: + - Start of complete re-write of ampache code + - Fixed register globals problem, should no longer be required to be on + - Added ID3V2 tag support + - Improved playlists, added track var + - New Looks and feel thanks to Ben Shields + - Completely rebuilt cataloging again... + - Fixed orphaned files + +========================================================================== + v.3.0, 04/05/2002: + - Added Randall Ehren to the "Ampache Development Team" :-) + - Completely rebuilt catalog mechanism + - Remote catalog updates via XML-RPC + - Fixed orphaned file interface + - New tools to update ID3 tags easier + - Changed admin interface to be easier to use + - Many bug fixes + +-------------------------------------------------------------------------- + v.2.5, 03/04/2002: + - Bug fixes and code cleanup + - Final mod_mp3 only version + + v.2.0, 02/05/2002: + - Added stats page to clean up "Home" + - Made default album/artist view start with match=A + - Cleaned up admin/users interface to show who's logged on + - Added ability to anonymously mail all users via admin + +-------------------------------------------------------------------------- + v.2.0rc2, 01/18/2002: + - Fixed update catalog tools to remove songs that have changed + - Added support for the mod_mp3 MySQL dispatch -> you no longer need + to restart apache for new songs + - Minor interface fixes (spelling, wording, etc) + - Added per-user statistics + - Made album/artist views easier to digest + +-------------------------------------------------------------------------- + v.1.21, 07/29/2001: + - Minor bug fixes from various users (see readme) + - Updated Album and Artist views + - Added play all songs from artist and randomize songs from artist + +-------------------------------------------------------------------------- + v.2.0rc1, 01/10/2002: + - User/session management for admin + - fix 'Greatest Hits' problem + - Wrote setup.php for initial setup of server + - Reworked administration pages + - Per user profile settings/updates + - Playlists stored per user + - Private/Public playlists + - Show most popular songs/artist + - Added play.php wrapper to track song play + - Added stats for number of times played for artist/album/song + - Add demo mode + - Tweak album view to show artist as well + - Change format/view of "Home" page + - show songs for an artist on album page + - fixed single song play + - select all feature for song view + - greatly enhanced search result capabilities + - Show how many users connected + +--------------------------------------------------------------------------- + v.1.20, 07/22/2001: + - Lots and Lots of bug fixes + - Replaced mp3.php class with Sandy McArthur, Jr's id3 class + - Song editor -> update DB and/or song file -> integrate with orphan files + - Add genre support and allow genre playback and stats + - Add track support ... now should order in album order + automatically if ID3v1.1 tags used + - ID3v1.1 support for writing/reading files + - Tweak filefind to make filename be name of orphaned songs + - Moved "Orphaned Files" into the admin section + +-------------------------------------------------------------------------- + v.1.10, 05/08/2001: + - PHP-only version now; can catalog all MP3's via PHP + - Tweaked perl script to not return SQL errors + - More interface tweaks to make site look purdy + +-------------------------------------------------------------------------- + v.1.07, 05/04/2001: + - Changed URL for mod_mp3 to media.tangent.org + - Tweaked interface even more and cleaned up build process even more. + +-------------------------------------------------------------------------- + v.1.06, 05/04/2001: + - Many more updates to the tools and interface. + +-------------------------------------------------------------------------- + v.1.04, 04/29/2001: + - Prettied up interface some more. + - Tweaked code for .pls support (added artist, album) + - Fixed filefind to work around .AppleDouble directories + +-------------------------------------------------------------------------- + v.1.03, 04/29/2001: + - All kinds of build changes. + - Added support for .pls + +-------------------------------------------------------------------------- + v.1.02, 04/29/2001: + - Minor build changes. + +-------------------------------------------------------------------------- + v.1.01, 04/29/2001: + - First version. +-------------------------------------------------------------------------- + + diff --git a/docs/GPL-LICENSE b/docs/GPL-LICENSE new file mode 100755 index 00000000..d65c21bb --- /dev/null +++ b/docs/GPL-LICENSE @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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.) +^L +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/docs/INSTALL b/docs/INSTALL new file mode 100755 index 00000000..7efd24dc --- /dev/null +++ b/docs/INSTALL @@ -0,0 +1,189 @@ +------------------------------------------------------------------------------- +------------------ INSTALL - Ampache v.3.3 - 03/27/2005 ----------------------- +------------------------------------------------------------------------------- + + I'm assuming that you have Apache, PHP and MySQL running when you + get to this point. + + If you're upgrading from 3.0 to 3.1 please refer to the MIGRATION Notes. + Your database can't be re-used, but fear not the Ampache guys thoughtfully + included scripts to port the data from your old database to the new one. + (They get an extra beer for that one) + + If you're upgrading from 3.1 to a newer version refer to the MIGRATION Notes. + There should be an automated update script which will allow you to keep your + current database. + + If at any time during this install you can't figure out where you have gone + wrong check out /test.php for help. + + 1. Installing Using the Web Interface: + + As of 3.3-Beta3 Ampache includes an web based installation script. In order + for the script to work correctly you will need a user that has Database + create and modify rights for your mysql server. To use this script simply + visit /install.php. If you get a Access Denied make sure that your /config + directory does not contain an existing ampache.cfg + + Web Install: + Step 1 - Inserting the database, this requires you to enter + a username/pass for MySQL that is able to create + a brand new database and insert new tables. This does + not have to be the user you actually run ampache as + Step 2 - Creating the Config file, this step asks for a 'user' + level account for MySQL that has full access over + the newly created ampache database, this can be the + same as the last step, but it is not recommended. + Ampache will attempt to write the config file directly + to the /config directory, if it isn't able to it + should prompt you to download the ampache.cfg simply + put it into /config and then visit the login page. + Step 3 - Creating the Initial User Account, you will be asked + for a username and password for the administrator + account. + + Enjoy! If you have any problems with the web installer please report them + to vollmerk@ampache.org Thanks! + + + 2. The Long of Setting Up Ampache + + 2.1 Configuring Apache Server + There are really two choices here. You can either configure a virtual + server for the ampache services, or you can just configure a new directory + directive for ampache. There are advantages and disadvantages for both. + + If you configure a new virtual server, it has it's own log files which + could be useful for separating the ampache web traffic from the regular + web server traffic. + + If you configure a new directory directive for ampache, the ampache + statistics will be in with all the other web traffic, but it may be a + little easier (but not by much). + + We've included cronolog lines. These are not required, but for + troubleshooting we recommend them. + + for a separate virtual server httpd.conf reads: + + [snip] + + <VirtualHost 192.168.100.2:80> + ServerName tunes.ampache.org + + ServerAdmin webmaster@ampache.org + DocumentRoot /data/www/ampache + + DirectoryIndex index.php + + </VirtualHost> + + [snip] + + Now perform an 'apache restart' and apache should be configured. + + 2.2 Configuring Your MySQL Server + Setup a user and pass for your music db and create the music db. + + Run: 'mysql -u user -p musicdb < sql/ampache.sql' + to create the music db and tables. + + 2.3 Configuring Ampache + + 2.3.1 Configuring motd.php + + Copy config/motd.php.dist to config/motd.php + + Edit this file however you like, with either php code or straight html. + The output will be displayed below the login box on login.php. + + + 3. Running Ampache For The First Time + + Point your browser at your new ampache webpage and you should get + the installation page. It will run you through inserting your + database, creating your config file and setting up your first user + + Grab A Beer.... + + 3.1 Setting up a catalog + First, create your local catalogs. Do this my first clicking + `Add a catalog', and entering the path for the root of your + collection of MP3 files. There is no need to enter sub directories + since the update tool will recursively catalog all subdirectories. + + You can enter multiple paths, so this means that you can access + multiple directories, and hence multiple hard disks. I solved this + particular problem by patching the kernel to include logical volume + management, but that's a completely different story. + + 3.2 Updating your Catalog + If everything went correctly, you are now looking at an empty ampache. + In order to populate the database with all the tag information from + your MP3 files, you'll need to go to the `Admin' page, and select + the `Catalog' link. + + Finally, you want to click the `Update All Catalogs' button in the + middle of the Catalog page and go for coffee (or any other beverages + you like) as this will take a little bit of time. The web server + is now searching for and opening each of the MP3 files in your + collection, pulling the ID3 tag data out, and using these to populate + your ampache database. + + Final Note on MP3 Tags: + Since you the value of ampache is directly related to the data in the + database, and this data is obtained from the ID3 tags in your MP3 files, + it really pays to have all your tags populated and in order. + + One of the best tools that I've run across to do this is: + EasyTAG - Tag editor for MP3 and OGG files + http://easytag.sourceforge.net + + It runs right on the Linux machine, and is quite a bit faster at updating + tags than any PC based programs that have to access the MP3 across a + Samba share point. But this does not mean that you can't update tags + this way. Just that the local Linux program can access the MP3 faster. + + If you insist on using a windows version another good tool can also be + found on sourceforge at: http://massid3lib.sourceforge.net/ + + 3.3 Adding Users + To Add Users simply click admin->users->Add a new user and enter + the appropriate information + + + 3.4 Sorting and updating files (Under Development) + When updating catalog preferences new fields added in v3.1 include + ID3 set command + Filename pattern + Sort Pattern + + When these fields are populated a periodic update may be performed by scheduling + the fileupdate.pl program to run at timed intervals. This program will query the + database and attempt any requested updates. Before fileupdate.pl can be run, the + Ampache.pm file must be edited to reflect your archive information. + + Usage of fileupdate.pl is as follows: + + fileupdate [--id3|--rename|--sort|--all] [--help] [--pretend] [--verbose] + --pretend Display command taken, without actually doing anything. + --id3 Update id3 tags for all files flagged with 'id3' + --rename Rename files flagged with 'rename' + --sort Sort files flagged with 'sort' + --all Performs id3 update, rename, and sort + for all files flagged with 'id3' + --verbose Shows detailed information about what's happening. + --help This message + + + + An example usage would be to schedule a cron tab which will run fileupdate.pl with + the appropriate arguments which runs every 6 hours. + + EXAMPLE: + If you were to place the following line in your crontab: + * 24 * * * /apache/bin/fileupdate.pl -all + + any updates applied to the database would be applied at midnight everyday + + diff --git a/docs/INSTALL.de b/docs/INSTALL.de new file mode 100644 index 00000000..65123e23 --- /dev/null +++ b/docs/INSTALL.de @@ -0,0 +1,252 @@ +------------------------------------------------------------------------------- +------------------ INSTALL - Ampache v.3.2 - 08/11/2004 ----------------------- +------------------------------------------------------------------------------- + + Ich gehe davon aus, dass Apache, PHP und MySQL bereits laufen, wenn du hier + angelangt bist. + + Falls du von 3.0 auf 3.1 upgradest, schau dir bitte die MIGRATION Hinweise + an. Die Datenbank kann nicht weitergenutzt werden, aber glücklicherweise + haben wir einige Scripts entwickelt, um die Datenbank in das neue Format + zu konvertieren. (Dafür gibts ein extra Bier :P) + + Falls während der Installation irgendetwas schieflaufen sollte, schau dir + zunächst die /test.php an, um an Hilfe zu gelangen. + + 1. Schnellinstallation: + + mysql -u <Benutzername> -p <Datenbankname> < $root/ampache/sql/ampache.sql + + Editiere die /etc/apache/httpd.conf + Lass das Webroot auf $root/ampache/docs zeigen. + [snip] + Alias /ampache/ /usr/share/ampache/docs/ + + <Directory /usr/share/ampache/docs> + Options Indexes MultiViews + AllowOverride None + Order allow,deny + Allow from all + </Directory> + [snip] + Starte danach Apache neu. + + Verschiebe $root/ampache/config/ampache.cfg.dist nach + $root/ampache/config/ampache.cfg + Editiere $root/ampache/config/ampache.cfg + Besuche $web_adresse/login.php und melde dich mit den folgenden Daten an: + Benutzername: admin + Passwort: changeme + + + 2. Der längere Weg + + 2.1 Apache konfigurieren + An dieser Stelle gibt es zwei Möglichkeiten. Etnweder kannst du einen + Virtual Server für Ampache konfigurieren, oder du kannst Amapche einfach + in ein freies Verzeichnis installieren. Beide Wege besitzen Vor- und + Nachteile. + + Wenn du einen Virtual Server einrichtest, besitzt er seine eigenen + Logfiles, die hilfreich beim Berechnen des Traffics sein könnten + (Unterscheidung von Ampache- und normalem Traffic). + + Wenn du ein neues Verzeichnis für Ampache nutzt, wird der Traffic von + Ampache zusammen mit dem der anderen Anwendungen brechnet. Der Vorteil + hier ist, dass die Installation u.U. einfacher ist. + + We've included cronolog lines. These are not required, but for + troubleshooting we recommend them. + + Der Eintrag für einen eigenen Virtual Server lautet folgendermaßen: + + [snip] + + <VirtualHost 192.168.100.2:80> + ServerName tunes.ampache.org + + ServerAdmin webmaster@ampache.org + DocumentRoot /data/www/ampache/docs + + DirectoryIndex index.php + + </VirtualHost> + [snip] + + Nac einem Neustart sollte Ampache nun richtig konfiguriert sein. + + 2.2 MySQL konfigurieren + Wir legen an dieser Stelle einen eigenen User für Ampache an. + Dazu ist folgender Befehl erforderlich: + + 'mysql -u user -p pass < sql/ampache.sql' + + 2.3 Konfiguration von Ampache + + 2.3.1 Konfiguration der ampache.cfg + + Kopiere zuächst die config/ampache.cfg.dist nach + config/ampache.cfg. + Öffne dann die config/ampache.cfg.dist und editiere die + Vairablen: + + # Hostname des Datenbankservers + local_host = localhost + + # Datenbankname + local_db = ampache + + # Datenbank-Benutzername + local_username = <mysql_login_name> + + # Datenbank-Passwort + local_pass = <password> + + # Logindauer in Sekunden + local_length = 900 + + # Dies ist die Domain, fuer die das Cookie, in dem der Sessionkey gespeichert wird, + # ausgestellt wird. Diese Variable muss die Domain oder der Host des Systems sein. + # Andernfalls wird es unmoeglich sein, sich einzuloggen. + # Bitte ueberpruefe, ob zu Beginn ein . steht. (erforderlich!) + # Dies ist jedoch nicht notwendig, solange du die libglue fuer mehr als eine Seite + # nutzt. + # sess_domain = .yourwebsite.com + + # Name der Session/des Cookie, dass zum Browser geschickt wird. + sess_name = ampache + + # Lebenszeit des Cookies. 0 == Immer (bzw. bis der Browser geschlossen wird), + # andernfalls wird hier die Lebenszeit in Sekunden erwartet + sess_cookielife = 0 + + # Ist das Cookie ein "sicheres" Cookie? + sess_cookiesecure = 0 + + prefix = "/<ampache_root>" + # This should not include http:// or any part of the host name + # ampache detects hostname and port automaticly + # Hier sollte kein http:// oder irgendein Teil des Hostnames + # stehen. Hostname und Port werden automatisch erkannt. + web_path = "/<path to ampache>" + site_title = "Ampache!!!" + + # Sollte ausgeschaltet bleiben, ansonsten wird die Performance sehr drunter leiden. + do_mp3_md5 = "FALSE" + + # Hier wird das Interval angegeben, in dem der aktuelle Fortschritt + # beim Katalogisieren ausgegeben wird. Bei grossen Katalogen sollte + # diese Zahl moeglichst gering gehalten werden. + catalog_echo_count = "25" + + # Diese Option legt fest, welcher ID3 Tag bevorzugt wird. + # Diese Funktion wurde fuer diejenigen angelegt, die noch keine + # v2-Tags angelegt haben. Somit ist es bei manchen Nutzern hilfreich, + # v1 zu nutzen, bis die Kataloge auf v2 umgestellt sind. + id3tag_order = "id3v2" + id3tag_order = "id31v" + + # Kommentiere dies aus, wenn du nicht moechtest, dass Ampache + # symlinks folgt. + #no_symlinks = "true" + + # Benutze Login-/Authentifizierungssystem? + use_auth = "yes" + + # Kuenstler- & Album Zwischenspeicherlimit + # Um die Katalog-Aktualisierungen zu beschleunigen, und um die Last + # auf MySQL zu reduzieren nutzen wir eine Art Zwischenspeicher, wo + # anhand einer ID MySQL-Abfragen gespeichert werden. + # Du kannst diese Option frei nach deinem belieben abaendern ... + album_cache_limit = "25" + artist_cache_limit = "50" + + 2.3.2 Configuring motd.php + + Copy config/motd.php.dist to config/motd.php + + Edit this file however you like, with either php code or straight html. + The output will be displayed below the login box on login.php. + + + 3. Der erste Start von Ampache + + Besuche mit deinem Browser die frisch installierte Ampache-Seite, + und es sollte das Anmeldeformular erscheinen. + Das Passwort und der Benutzername für den ersten Login lauten: + Benutzer: admin + Passwort: changeme + + Jetzt kannst du dir ein Bier holen .. ;) + + 3.1 Einen Katalog einrichten + Zunächst gilt es, einen Katalog einzurichten. Dies geschieht, indem + man auf "Katalog hinzufügen" klickt, und dort den Pfad zur + MP3-Sammlung angibt. Es ist nicht notwendig, alle Unterverzeichnise + einzeln anzugeben, da die angegebenen Verzeichnise rekursiv + durchsucht werden. + + 3.2 Aktualisieren der Kataloge + Falls alles einwandfrei gelaufen, hast du nun einen leeren Ampache + vor dir. Um diesen Zustand zu veraendern, gehe unter der + Administrationsseite auf den Katalog-Link. + + Nun fehlt nur noch ein Klick auf "Alle Kataloge aktualisieren", und + die Datenbank wird gefuehlt. Dies kann eine Weile dauern, da der + Webserver nun jede einzelne Datei auf ihren ID3-Tag hin untersucht, + und diese Daten in die Datenbank einspeist. + + Final Note on MP3 Tags: + Abschliessende Bemerkung zu den ID3-Tags: + Da Ampache seine Daten aus den ID3-Tags bezieht, ist es wirklich + empfehlenswert, diese sauber und geordnet zu halten. + + Eines der besten Tools für diesen Zweck ist EasyTAG, ein Tageditor + für MP3- und OGG-Dateien: + http://easytag.sourceforge.net + + Es läuft direkt auf dem Linuxrechner, und ist somit ein wenig schneller, + als wenn man die ID3-Tags erst ueber Samba oder NFS aktualisiert. Das + heisst natuerlich nicht, dass dies geht .. Es geht lediglich darum, dass + dieses Programm schneller auf die ID3-Tags zugreifen kann. + + Falls du darauf bestehen solltest, Windows zu nutzen, findet sich unter + http://massid3lib.sourceforge.net/ eine weitere gute Software, für den + selben Zweck. + + 3.3 Benutzer hinzufügen + Um Benutzer hinzufügen, genügt es unter Administration -> Benutzer + -> Benutzer hinzufügen zu gehen, und das dortige Formular mit den + entsprechenden Informationen zu versehen. + + 3.4 Sortieren und Aktualisieren der Dateien (Wird noch entwickelt) + In Verbindung mit dem Update der Katalogeinstellungen, sind in v3.1 + folgende Felder hinzugekommen: + ID3-Kommando + Dateinamenmuster + Sortiermuster + + Nachdem diese Felder mit Werten versehen wurden, steht einem regelmäßigem + Update mithilfe von fileupdate.pl nichts mehr im Wege. + Dieses Programm fragt die Datenbank ab, und nimmt die neusten Aktualisierungen + vor. Bevor fileupdate.pl ausgeführt werden kann, muss die Ampache.pm editiert + werden, sodass sie die notwendigen Archivinformatioenen enthält. + + Benutzung von fileupdate.pl: + + fileupdate [--id3|--rename|--sort|--all] [--help] [--pretend] [--verbose] + --pretend Stelle das angegebene Kommando dar, ohne etwas zu machen. + --id3 Aktualisiere ID3-Tags von allen mit 'id3' markierten Dateien + --rename Benenne alle mit 'rename' markierten Dateien + --sort Sortiere alle mit 'sort' markierten Dateien + --all Nehme ID3-Update vor, bennene und sortiere alle Dateien um, + die mit 'id3' markiert wurden. + --verbose Zeige detailierte Informationen + --help Diese Nachricht + + + + Beispiel: + Folgender Eintrag nimmt regelmäßige um Mitternacht alle erforderlichen Updates + vor: + * 24 * * * /apache/bin/fileupdate.pl -all diff --git a/docs/MIGRATION b/docs/MIGRATION new file mode 100755 index 00000000..42449308 --- /dev/null +++ b/docs/MIGRATION @@ -0,0 +1,39 @@ +------------------------------------------------------------------------------- +--------- MIGRATION - Ampache v.3.3 ----------- +------------------------------------------------------------------------------- + + +- Migrating from Ampache 3.3 --> 3.3.X+ + + Rename your /config/ampache.cfg to /config/ampache.cfg.php + +- Migrating from Ampache-3.1.0 --> 3.3 + + Follow the instructions found in $yourwebsite/update.php + +- Migrating from Ampache-3.1-Alpha2 or Alpha --> Ampache 3.1-Alpha3 + + Please visit $yourwebsite/update.php to update the genre table. + If you are updating from Alpha2 to Alpha3 or higher. + This will invalidate your current catalog. + +- Migrating from Ampache-3.0 --> Ampache3.1 + + There are currently a few tools to help migrate your Users and your + playlists from Ampache 3.0 to Ampache 3.1. + + * Note these tools will _NOT_ work against 3.2 or 3.3 they are no + longer maintained. + + 1. Install and setup Ampache3.1 in a new Database. + + 2. Edit the approiate parameters in the following files + + /bin/migrate_user.pl + /bin/export_playlist.pl + /bin/import_playlist.pl + + 3. Run the desired script. + + These perl scripts were created by Andy Morgan + diff --git a/docs/MIGRATION.de b/docs/MIGRATION.de new file mode 100644 index 00000000..5dd6c8ce --- /dev/null +++ b/docs/MIGRATION.de @@ -0,0 +1,31 @@ +$CVSHeader: ampache/MIGRATION,v 1.2 2004/03/23 08:33:18 vollmerk Exp $ + +MIGRATION - Ampache v.3.1 - 11/26/2003 + +- Umwandlung von Ampache-3.1.X --> Ampache 3.3 + + Folge den Anweisungen unter $ampache/update.php + +- Umwandlung von Ampache-3.1-Alpha2 oder Alpha --> Ampache 3.1-Alpha3 + + Bitte besuche $ampache/update.php um die Genre-Tabelle zu + aktualisieren. + Falls du von Alpha2 auf Alpha3 aktualisierst, wird dein + Katalog jedoch ungültig, und muss neu angelegt werden. + +- Umwandeln von Ampache-3.0 --> Ampache3.1 + + Es gibt momentan einige Tools, um die Benutzer und Playlisten + von Ampache 3.0 auf Ampache 3.1 upzudaten. + + 1. Installiere und konfiguriere Ampache3.1 in einer neuen + Datenbank. + + 2. Editiere die entsprechenden Parameter in diesen Dateien: + /bin/migrate_user.pl + /bin/export_playlist.pl + /bin/import_playlist.pl + + 3. Führe das entsprechende Script aus. + + Die Perlskripte wurden von Andy Morgan erstellt. diff --git a/docs/README b/docs/README new file mode 100755 index 00000000..d33f798d --- /dev/null +++ b/docs/README @@ -0,0 +1,149 @@ +------------------------------------------------------------------------------- +--------- README - Ampache v.3.3 ----------- +------------------------------------------------------------------------------- + +Contents: + + 1. Intro + a) Supported File-Types + b) Supported Stream Methods + c) Current Translations + d) Thanx + 2. Getting all the components + 3. Setting Up + a) Upgrading + 4. License + a) Donations (Beer!) + 5. Contact info + +1. Intro: + + Ampache is a PHP-based interface to a MySQL database + where information about your audio files are stored. The songs + are streamed using PHP; older versions required mod_mp3 but this + version no longer does. The songs are cataloged via PHP + scripts. + + These tools are heavily dependent on quality tags in your audio + files and or the file system organization. If you've kept them + up-to-date or organized this is the tool for you. + + See CHANGELOG for version information. + + A) Supported File-Types + + Ampache currently supports the following audio file types. If + you would like ampache to support an additional file type please + contact us on irc, or the forums and we will investigate adding + them. Thanks + + - MP3 (id3v1 && id3v2) + - OGG + - WMA + - FLAC + - RM + - AAC/M4A + - MPC + + B) Supported Stream Methods + + Ampache currently supports the following different methods for + streaming your audio files. Please contact us if you would like + to see additional methods. + + - Single Stream + - extended m3u + - simple m3u + - standard pls + - ASX + - Realtime Downsampled + - Localplay using Moosic + - Localplay using MPD + - Icecast2 Stream + + C) Current Translations + + Ampache is currently translated into the following languages. If + you are interested in updating an existing translation or adding + a new one please contact us at translation@ampache.org or see + /locale/base/TRANSLATIONS for more instructions. + + - English (en_US) + - German (de_DE) + - French (fr_FR) *Partial + + D) A Special Thanks: + Thanx to those who've helped us make Ampache so useable: + Scott Kveton - Head Nacho, inventer of all that is Ampache + Robert Hopson - Libglue, Playlists, Ogg support.. and much more + Andy Morgan - Protagonist + RosenSama - Developer + Randall Ehren (Initial XML-RPC) + s1amson (lots of beta testing) + Caleb Crome (bug fixes and enhancements) + Mike Payson + Jon Disnard + Adriaan Peters (numerous patches and bug fixes) + Rob Kaandorp (time calculation fix) + Ian Cote (MP3.php tweaks) + Kurt Lieber (random fixes) + Maan Bsat (random fixes) + latka (from media.tangent.org site) for orphaned song ideas + Lamar Hansford (README/INSTALL improvements) & Upload + And many many more... + +2. Getting all the components + + Apache >= 1.3.19 http://www.apache.org OR other webserver + PHP >= 4.1.2 http://www.php.net + PHP4-Mysql + PHP4-Session + PHP4-gd (recommended) + PHP4 ICONV & ZLIB support (recommended) + MySQL >= 3.23 http://www.mysql.com + Perl >= 5 (Optional) + 16MB of Ram + +3. Setting Up + + Ampache: + If you're upgrading from 3.0 to 3.1 you will not be able to re-use + your database or config.php files. You will need to follow the entire + Ampache install guidelines oultined in the INSTALL file. + + +3a. Upgrading Your Ampache Install + + If you are upgrading from an older version of Ampache we recommend + moving the old directory out of the way, extracting the new copy in + its place and then re-creating the config file. All database updates + will be handled by the /update.php script. There is no need to insert + the /sql/ampache.sql if you have an existing installation. + + +4. License + + This Application falls under the Standard GPL. See Licence + included with this tar file + +4a. Donations + + We don't want your money but we will take your beer. If you + love ampache and want to encourage us to write more please + visit ampache.org for information on how to donate some beer + to our cause. If you can't send beer a postcard would also + be great. E-mail beer@ampache.org for shipping address + +5. Contact Info + + Hate it? Love it? Let us know. Let us know if you think of any + more features, bugs, etc. + + Public SVN: https://svn.ampache.org/ + IRC: irc.ampache.org #ampache (Freenode) + Forums: http://ampache.org/forums + Bugs: http://bugs.ampache.org + Demo: http://ampache.org/demo + + Ampache Development Team + dev@ampache.org diff --git a/docs/README.de b/docs/README.de new file mode 100644 index 00000000..8e547c15 --- /dev/null +++ b/docs/README.de @@ -0,0 +1,114 @@ +------------------------------------------------------------------------------- +--------- README - Ampache v.3.2 - 06/07/2004 ----------- +------------------------------------------------------------------------------- + +Inhalt: + + 1. Einleitung + a) Danksagungen + 2. Komponenten herunterladen + 3. Einrichten + a) Upgraden + 4. Lizenz + a) Spenden (Bier!) + 5. Kontaktinformationen + +1. Einleitung: + + Ampache stellt eine PHP-basierte Oberfläche dar, die + ihre Informationen über MP3s in einer MySQL-Datenbank ablegt. + Die Lieder werden per PHP gestreamt. Ältere Versionen erfordern + mod_mp3, aber neuere Versionen arbeiten auch ohne mod_mp3. + Die Songs werden mit Hilfe von PHP-Skripten katalogisiert. + Mit dieser Version kommen viele neue Funktionen hinzu. Es wurden + die Katalogfunktionen überarbeitet, sodass diese insbesondere auf + größeren Systemen nun einfacher zu nutzen sind. Weiterhin wurden + erste Ansätze implementiert, die es ermöglichen, die eigenen + Kataloge mit denen von anderen Ampache-Systemen zu teilen oder + gemeinsam zu nutzen. Weitere Informationen hierzu kann man im + "Katalog" Bereich bei den Administratorwerkzeugen finden. + + Diese Kataloge basieren auf den ID3 Tags der MP3s. Wenn die Tags + regelmäßig gepflegt und aktuell sind, dann ist dies das geeignete + Werkzeug. + + Im CHANGELOG stehen weitere Informationen zu dieser Version. + + Danksagungen: + a) Dank an diejenigen, die dazu beigetragen haben, dass Ampache + das geworden ist, was es heute ist: + Scott Kveton - Hauptperson, der die Idee zu Ampache hatte + Robert Hopson - Libglue, Playlisten, Ogg Unterstützung & vieles mehr + Andy Morgan - Protagonist + Caleb Crome (Bugfixes und Verbesserungen) + Mike Payson + Jon Disnard + Adriaan Peters (Eine Vielzahl an Patches und Bugfixes) + Rob Kaandorp (Zeitberechnungsfix) + Ian Cote (MP3.php Verbesserungen) + Kurt Lieber (diverse Verbesserungen) + Maan Bsat (diverse Verbesserungen) + latka (von media.tangent.org) für die Vorschläge zur Behandlung von + Liedern ohne ID3-Tags (orphaned song) + Lamar Hansford (README/INSTALL Verbesserungen) & Upload + Und viele weitere ... + + +2. Komponenten herunterladen + + Apache >= 1.3.19 http://www.apache.org + PHP >= 4.1.2 http://www.php.net + MySQL >= 3.23 + Ampache nutzt die REPLACE Funktion von MySQL, was eine Version + von >= 3.23 erfordert. + http://www.mysql.com + 16MB RAM + +3. Einrichten + + Ampache: + Bei einem Upgrade von Version 3.0 auf 3.1 können vorhandenen + Datenbanktabellen und die config.php nicht mehr genutzt werden. + Diese müssen in das aktuelle Format konvertiert werden. Weitere + Informationen hierzu finden sich in der INSTALL Datei. + + +3a. Eine vorherige Ampache-Installation upgraden + + Für ein Upgrade empfiehlt sich die folgenden Vorgehenseweise: + - Eine Kopie dies alten Verzeichnisses anlegen. + - + (Falls du von einer älteren Ampache-Installation upgradest, empfehlen + wir dir, das alte Verzeichnis zu verschieben, das neue Archiv an + dieser Stelle zu entpacken und dann die config.php neu zu erstellen. + Alle Datenbankupgrades werden vom /update.php Skript erledigt. + Es ist also nicht notwendig die /sql/ampache.sql neu einzufügen, + falls bereits eine Ampache-Installation besteht.) + + +4. License + + Ampache steht unter der Standard GPL. Nähere Informationen finden + sich in der Datei, die in diesem Archiv mitgeliefert wird. + +4a. Spenden + + Wir wollen euer Geld nicht, aber wir nehmen gerne euer Bier an. + Wenn dir Ampache gefällt, und du uns ermutigenn willst, das Programm + weiter zu verbessern, dann besuche ampache.org um Informationen + darüber zu bekommen, wie du uns Bier spenden kannst. + Falls das nicht möglich ist, freuen wir uns auch über eine Postkarte. + +5. Kontaktinformationen + + Du hasst Ampache? Du liebst es? Lass es uns wissen. Lass es uns wissen, + wenn du an neuen Features interessiert bist, Bugs melden willst usw. + + IRC: irc.ampache.org #ampache (Freenode) + Foren: http://ampache.org/forums + Bugs: http://bugs.ampache.org + + Ampache Development Team + ampache-dev@lists.oregonstate.edu + + Karl Vollmer, Robert Hopson & Andy Morgan diff --git a/download/index.php b/download/index.php new file mode 100644 index 00000000..25972606 --- /dev/null +++ b/download/index.php @@ -0,0 +1,61 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Download Document + @discussion Downloads a song to the user, if they have download permission. + Special thanks to the Horde project for their Browser class that makes this so easy. +*/ + +require('../modules/init.php'); +require(conf('prefix') . '/lib/Browser.php'); + +$browser = new Browser(); + +/* If we are running a demo, quick while you still can! */ +if (conf('demo_mode') || !$user->has_access('25')) { + access_denied(); +} + + + + +if ($user->prefs['download']) { + if ($_REQUEST['song_id']) { + if ($_REQUEST['action'] == 'download') { + $song = new Song($_REQUEST['song_id']); + $song->format_song(); + $song->format_type(); + $song_name = $song->f_artist_full . " - " . $song->title . "." . $song->type; + + // Use Horde's Browser class to send the right headers for different browsers + // Should get the mime-type from the song rather than hard-coding it. + header("Content-Length: " . $song->size); + $browser->downloadHeaders($song_name, $song->mime, false, $song->size); + + $fp = fopen($song->file, 'r'); + fpassthru($fp); + fclose($fp); + } + } +} + diff --git a/favicon.ico b/favicon.ico Binary files differnew file mode 100644 index 00000000..a362d96f --- /dev/null +++ b/favicon.ico diff --git a/flag.php b/flag.php new file mode 100644 index 00000000..d5c358ba --- /dev/null +++ b/flag.php @@ -0,0 +1,55 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/* + + This will allow users to flag songs for having broken tags or bad rips. + +*/ + +require_once("modules/init.php"); + +$action = scrub_in($_REQUEST['action']); +$song = scrub_in($_REQUEST['song']); + +if ( $action == 'flag_song') { + $flagged_type = scrub_in($_REQUEST['flagged_type']); + $comment = scrub_in($_REQUEST['comment']); + insert_flagged_song($song, $flagged_type, $comment); + $flag_text = _("Flagging song completed."); + $action = 'flag'; +} + +?> +<?php show_template('header'); ?> +<?php + $highlight = "Home"; + show_menu_items($highlight); + + if ( $action == 'flag' ) { + $type = 'show_flagged_form'; + $song_id = $song; + + include(conf('prefix') . "/templates/flag.inc"); + } + +show_footer(); +?> +</body> +</html> diff --git a/images/ampache-dark-bg.gif b/images/ampache-dark-bg.gif Binary files differnew file mode 100755 index 00000000..e9fc72c9 --- /dev/null +++ b/images/ampache-dark-bg.gif diff --git a/images/ampache-light-bg.gif b/images/ampache-light-bg.gif Binary files differnew file mode 100755 index 00000000..262430b8 --- /dev/null +++ b/images/ampache-light-bg.gif diff --git a/images/ampache-mid.gif b/images/ampache-mid.gif Binary files differnew file mode 100755 index 00000000..57376ea4 --- /dev/null +++ b/images/ampache-mid.gif diff --git a/images/ampache.gif b/images/ampache.gif Binary files differnew file mode 100755 index 00000000..fb110191 --- /dev/null +++ b/images/ampache.gif diff --git a/images/blank-pixel.gif b/images/blank-pixel.gif Binary files differnew file mode 100644 index 00000000..17d43908 --- /dev/null +++ b/images/blank-pixel.gif diff --git a/images/blankalbum.gif b/images/blankalbum.gif Binary files differnew file mode 100644 index 00000000..a1d25b40 --- /dev/null +++ b/images/blankalbum.gif diff --git a/images/blankalbum.jpg b/images/blankalbum.jpg Binary files differnew file mode 100644 index 00000000..468301bd --- /dev/null +++ b/images/blankalbum.jpg diff --git a/images/headphone.gif b/images/headphone.gif Binary files differnew file mode 100755 index 00000000..74a66e11 --- /dev/null +++ b/images/headphone.gif diff --git a/images/table.gif b/images/table.gif Binary files differnew file mode 100755 index 00000000..89761b38 --- /dev/null +++ b/images/table.gif diff --git a/index.php b/index.php new file mode 100644 index 00000000..4d344ad5 --- /dev/null +++ b/index.php @@ -0,0 +1,139 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Index of Ampache + @discussion Do most of the dirty work of displaying the mp3 catalog + +*/ +require_once("modules/init.php"); +show_template('header'); +show_menu_items('Home'); +show_clear(); +$action = scrub_in($_REQUEST['action']); + +if (conf('refresh_limit') > 0) { show_template('javascript_refresh'); } +?> +<p style="font-size: 8pt; font-weight: bold;"> + <?php echo _("Welcome to"); ?> <a href="http://www.ampache.org/index.php">Ampache v.<?php echo conf('version'); ?></a> +<?php if (conf('use_auth')) { ?> + <?php echo _("you are currently logged in as") . " " . $user->fullname ." (". $user->username .")"; ?> +<?php } ?> +<?php if (conf('theme_name')) { ?> +<!-- Theme: <?php echo conf('theme_name'); ?> (<?php echo get_theme_author(conf('theme_name')); ?>) --> +<?php } ?> +</p> +<!-- Big Daddy Table --> +<table style="padding-left:5px;padding-right:5px;padding-top:5px;padding-bottom:5px;" > +<tr> + <td style="padding-left:17px;" valign="top" colspan="2"> + <?php show_now_playing(); ?> + </td> +</tr> +<tr><td colspan="2"> </td></tr> +<tr> + <td valign="top"> + <!-- Left table --> + <table border="0"> + <tr> + <td valign="top" align="right"> + <?php show_local_catalog_info(); ?> + </td> + <td valign="top" align="left"> + <?php + if ( $items = get_global_popular('album') ) { + show_info_box(_("Most Popular Albums"), 'album',$items); + } + ?> + </td> + </tr> + <tr><td colspan="2"> </td></tr> + <tr> + <td valign="top" align="right"> + <?php + if ( $items = get_global_popular('artist') ) { + show_info_box(_("Most Popular Artists"), 'artist', $items); + } + ?> + </td> + <td valign="top" align="left"> + <?php + if ( $items = get_global_popular('song') ) { + show_info_box(_("Most Popular Songs"), 'song', $items); + } + ?> + </td> + </tr> + <tr><td colspan="2"> </td></tr> + <tr> + <td valign="top" align="right"> + <?php + if ( $items = get_newest('artist') ) { + show_info_box(_("Newest Artist Additions"), '', $items); + } + ?> + </td> + <td valign="top" align="left"> + <?php + if ( $items = get_newest('album') ) { + show_info_box(_("Newest Album Additions"), '', $items); + } + ?> + </td> + </tr> + <tr><td colspan="2"> </td></tr> + <tr> + <td colspan="2" valign="top"> + <?php + show_random_play(); + ?> + </td> + </tr> + </table> + </td> + <td valign="top"> + <!-- Right table --> + <table border="0"> + <tr> + <td valign="top" rowspan="7"> + <?php + if($user->prefs['play_type'] == 'local_play') { + show_local_control(); + echo "<br />"; + } elseif ($user->prefs['play_type'] == 'mpd') { + show_mpd_control(); + echo "<br />"; + } else { + echo " "; + } + ?> + </td> + </tr> + </table> + </td> + <!-- End Right Table --> +</tr> +</table> + +<?php show_menu_items('Home'); ?> +</body> +</html> diff --git a/info.php b/info.php new file mode 100644 index 00000000..147cebcd --- /dev/null +++ b/info.php @@ -0,0 +1 @@ +<?php phpinfo(); ?> diff --git a/install.php b/install.php new file mode 100644 index 00000000..45a9b96f --- /dev/null +++ b/install.php @@ -0,0 +1,132 @@ +<?php +/* + + Copyright (c) 2001 - 2005 ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// Set the Error level manualy... I'm to lazy to fix notices +error_reporting(E_ALL ^ E_NOTICE); + +require_once('lib/general.php'); +require_once('lib/ui.php'); +require_once('lib/Browser.php'); +require_once('lib/install.php'); +require_once('modules/lib.php'); +require_once('lib/debug.php'); +require_once('modules/class/user.php'); + +// Libglue Requires +require_once('libglue/auth.php'); +require_once('libglue/session.php'); +require_once('libglue/dbh.php'); + + +if ($_SERVER['HTTPS'] == 'on') { $http_type = "https://"; } +else { $http_type = "http://"; } + + +$prefix = dirname(__FILE__); +$configfile = "$prefix/config/ampache.cfg.php"; + +$conf_array = array('prefix' => $prefix,'font_size' => '12', 'bg_color1' => '#c0c0c0', 'font' => 'Verdana', 'error_color' => 'red'); +$conf_array['base_color1'] = "#a0a0a0"; +$conf_array['bg_color2'] = "#000000"; +conf($conf_array); + +/* First things first we must be sure that they actually still need to + install ampache +*/ +if (!install_check_status($configfile)) { + access_denied(); +} + +/* Clean up incomming variables */ +$action = scrub_in($_REQUEST['action']); +$web_path = scrub_in($_REQUEST['web_path']); +$username = scrub_in($_REQUEST['local_username']); +$password = scrub_in($_REQUEST['local_pass']); +$hostname = scrub_in($_REQUEST['local_host']); +$database = scrub_in($_REQUEST['local_db']); + +/* Catch the Current Action */ +switch ($action) { + + case 'create_db': + if (!install_insert_db($username,$password,$hostname,$database)) { + require_once('templates/show_install.inc'); + break; + } + + header ("Location: " . $_SERVER['PHP_SELF'] . "?action=show_create_config&local_db=$database&local_host=$hostname"); + + break; + case 'create_config': + $created_config = install_create_config($web_path,$username,$password,$hostname,$database); + + require_once('templates/show_install_config.inc'); + break; + case 'show_create_config': + + /* Attempt to Guess the Web_path */ + $web_path = dirname($_SERVER['PHP_SELF']); + $web_path = rtrim($web_path,"\/"); + + require_once('templates/show_install_config.inc'); + break; + case 'create_account': + if (!install_create_account($username,$password)) { + require_once('templates/show_install_account.inc.php'); + break; + } + $results = read_config($configfile, 0, 0); + if ($_SERVER['HTTPS'] == 'on') { $http_type = "https://"; } + else { $http_type = "http://"; } + + libglue_param($results['libglue']); + /* Setup Preferences */ + $temp_user = new User($username); + $temp_user->fix_preferences(); + $temp_user = new User(0); + $temp_user->fix_preferences(); + + + $web_path = $http_type . $_SERVER['HTTP_HOST'] . $results['conf']['web_path']; + + header ("Location: " . $web_path . "/login.php"); + + case 'show_create_account': + + $results = read_config($configfile, 0, 0); + + /* Make sure we've got a valid config file */ + if (!read_config_file($configfile) OR !check_config_values($results)) { + require_once('templates/show_install_config.inc'); + break; + } + + require_once('templates/show_install_account.inc.php'); + break; + default: + require_once('templates/show_install.inc'); + break; + +} // end action switch + + +?> diff --git a/lib/Browser.php b/lib/Browser.php new file mode 100644 index 00000000..cf97dc41 --- /dev/null +++ b/lib/Browser.php @@ -0,0 +1,1082 @@ +<?php +/** + * The Browser:: class provides capability information for the current + * web client. Browser identification is performed by examining the + * HTTP_USER_AGENT environmental variable provide by the web server. + * + * $Horde: framework/Browser/Browser.php,v 1.167 2005/03/02 16:05:15 jan Exp $ + * + * Copyright 1999-2005 Chuck Hagenbuch <chuck@horde.org> + * Copyright 1999-2005 Jon Parise <jon@horde.org> + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Chuck Hagenbuch <chuck@horde.org> + * @author Jon Parise <jon@horde.org> + * @since Horde 1.3 + * @package Horde_Browser + */ +class Browser { + + /** + * Major version number. + * + * @var integer $_majorVersion + */ + var $_majorVersion = 0; + + /** + * Minor version number. + * + * @var integer $_minorVersion + */ + var $_minorVersion = 0; + + /** + * Browser name. + * + * @var string $_browser + */ + var $_browser = ''; + + /** + * Full user agent string. + * + * @var string $_agent + */ + var $_agent = ''; + + /** + * Lower-case user agent string. + * + * @var string $_agent + */ + var $_lowerAgent = ''; + + /** + * HTTP_ACCEPT string + * + * @var string $_accept + */ + var $_accept = ''; + + /** + * Platform the browser is running on. + * + * @var string $_platform + */ + var $_platform = ''; + + /** + * Known robots. + * + * @var array $_robots + */ + var $_robots = array( + /* The most common ones. */ + 'Googlebot', + 'msnbot', + 'Slurp', + 'Yahoo', + /* The rest alphabetically. */ + 'Arachnoidea', + 'ArchitextSpider', + 'Ask Jeeves', + 'B-l-i-t-z-Bot', + 'ConveraCrawler', + 'ExtractorPro', + 'FAST-WebCrawler', + 'FDSE robot', + 'fido', + 'geckobot', + 'Gigabot', + 'Girafabot', + 'grub-client', + 'Gulliver', + 'ia_archiver', + 'InfoSeek', + 'KIT-Fireball', + 'LEIA', + 'Lycos_Spider', + 'Mediapartners-Google', + 'MuscatFerret', + 'NaverBot', + 'polybot', + 'Pompos', + 'Scooter', + 'Teoma', + 'TurnitinBot', + 'Ultraseek', + 'ViolaBot', + 'webbandit', + 'www.almaden.ibm.com/cs/crawler', + 'ZyBorg', + ); + + /** + * Is this a mobile browser? + * + * @var boolean $_mobile + */ + var $_mobile = false; + + /** + * Features. + * + * @var array $_features + */ + var $_features = array( + 'html' => true, + 'hdml' => false, + 'wml' => false, + 'images' => true, + 'iframes' => false, + 'frames' => true, + 'tables' => true, + 'java' => true, + 'javascript' => true, + 'dom' => false, + 'utf' => false, + 'rte' => false, + 'homepage' => false, + 'accesskey' => false, + 'optgroup' => false, + 'xmlhttpreq' => false, + 'cite' => false, + ); + + /** + * Quirks + * + * @var array $_quirks + */ + var $_quirks = array( + 'avoid_popup_windows' => false, + 'break_disposition_header' => false, + 'break_disposition_filename' => false, + 'broken_multipart_form' => false, + 'buggy_compression' => false, + 'cache_same_url' => false, + 'cache_ssl_downloads' => false, + 'double_linebreak_textarea' => false, + 'empty_file_input_value' => false, + 'must_cache_forms' => false, + 'no_filename_spaces' => false, + 'no_hidden_overflow_tables' => false, + 'ow_gui_1.3' => false, + 'png_transparency' => false, + 'scrollbar_in_way' => false, + 'scroll_tds' => false, + ); + + /** + * List of viewable image MIME subtypes. + * This list of viewable images works for IE and Netscape/Mozilla. + * + * @var array $_images + */ + var $_images = array('jpeg', 'gif', 'png', 'pjpeg', 'x-png', 'bmp'); + + /** + + /** + * Returns a reference to the global Browser object, only creating it + * if it doesn't already exist. + * + * This method must be invoked as: + * $browser = &Browser::singleton([$userAgent[, $accept]]); + * + * @access public + * + * @param optional string $userAgent The browser string to parse. + * @param optional string $accept The HTTP_ACCEPT settings to use. + * + * @return object Browser The Browser object. + */ + function &singleton($userAgent = null, $accept = null) + { + static $instances; + + if (!isset($instances)) { + $instances = array(); + } + + $signature = serialize(array($userAgent, $accept)); + if (empty($instances[$signature])) { + $instances[$signature] = new Browser($userAgent, $accept); + } + + return $instances[$signature]; + } + + /** + * Create a browser instance (Constructor). + * + * @access public + * + * @param optional string $userAgent The browser string to parse. + * @param optional string $accept The HTTP_ACCEPT settings to use. + */ + function Browser($userAgent = null, $accept = null) + { + $this->match($userAgent, $accept); + } + + /** + * Parses the user agent string and inititializes the object with + * all the known features and quirks for the given browser. + * + * @access public + * + * @param optional string $userAgent The browser string to parse. + * @param optional string $accept The HTTP_ACCEPT settings to use. + */ + function match($userAgent = null, $accept = null) + { + // Set our agent string. + if (is_null($userAgent)) { + if (isset($_SERVER['HTTP_USER_AGENT'])) { + $this->_agent = trim($_SERVER['HTTP_USER_AGENT']); + } + } else { + $this->_agent = $userAgent; + } + $this->_lowerAgent = strtolower($this->_agent); + + // Set our accept string. + if (is_null($accept)) { + if (isset($_SERVER['HTTP_ACCEPT'])) { + $this->_accept = strtolower(trim($_SERVER['HTTP_ACCEPT'])); + } + } else { + $this->_accept = strtolower($accept); + } + + // Check for UTF support. + if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) { + $this->setFeature('utf', strpos(strtolower($_SERVER['HTTP_ACCEPT_CHARSET']), 'utf') !== false); + } + + if (!empty($this->_agent)) { + $this->_setPlatform(); + + if (preg_match('|Opera[/ ]([0-9.]+)|', $this->_agent, $version)) { + $this->setBrowser('opera'); + list($this->_majorVersion, $this->_minorVersion) = explode('.', $version[1]); + $this->setFeature('javascript', true); + $this->setQuirk('no_filename_spaces'); + + switch ($this->_majorVersion) { + case 7: + $this->setFeature('dom'); + $this->setFeature('iframes'); + $this->setFeature('accesskey'); + $this->setFeature('optgroup'); + $this->setQuirk('double_linebreak_textarea'); + break; + } + } elseif (strpos($this->_lowerAgent, 'elaine/') !== false || + strpos($this->_lowerAgent, 'palmsource') !== false || + strpos($this->_lowerAgent, 'digital paths') !== false) { + $this->setBrowser('palm'); + $this->setFeature('images', false); + $this->setFeature('frames', false); + $this->setFeature('javascript', false); + $this->setQuirk('avoid_popup_windows'); + $this->_mobile = true; + } elseif ((preg_match('|MSIE ([0-9.]+)|', $this->_agent, $version)) || + (preg_match('|Internet Explorer/([0-9.]+)|', $this->_agent, $version))) { + + $this->setBrowser('msie'); + $this->setQuirk('cache_ssl_downloads'); + $this->setQuirk('cache_same_url'); + $this->setQuirk('break_disposition_filename'); + + if (strpos($version[1], '.') !== false) { + list($this->_majorVersion, $this->_minorVersion) = explode('.', $version[1]); + } else { + $this->_majorVersion = $version[1]; + $this->_minorVersion = 0; + } + + /* IE on Windows does not support alpha transparency in PNG + * images. */ + if (preg_match('/windows/i', $this->_agent)) { + $this->setQuirk('png_transparency'); + } + + /* IE 6 (pre-SP1) and 5.5 (pre-SP1) has buggy compression. + * The versions affected are as follows: + * 6.00.2462.0000 Internet Explorer 6 Public Preview (Beta) + * 6.00.2479.0006 Internet Explorer 6 Public Preview (Beta) + Refresh + * 6.00.2600.0000 Internet Explorer 6 (Windows XP) + * 5.50.3825.1300 Internet Explorer 5.5 Developer Preview (Beta) + * 5.50.4030.2400 Internet Explorer 5.5 & Internet Tools Beta + * 5.50.4134.0100 Internet Explorer 5.5 for Windows Me (4.90.3000) + * 5.50.4134.0600 Internet Explorer 5.5 + * 5.50.4308.2900 Internet Explorer 5.5 Advanced Security Privacy Beta + * + * See: + * ==== + * http://support.microsoft.com/kb/164539; + * http://support.microsoft.com/default.aspx?scid=kb;en-us;Q312496) + * http://support.microsoft.com/default.aspx?scid=kb;en-us;Q313712 + */ + $ie_vers = $this->getIEVersion(); + $buggy_list = array( + '6,00,2462,0000', '6,00,2479,0006', '6,00,2600,0000', + '5,50,3825,1300', '5,50,4030,2400', '5,50,4134,0100', + '5,50,4134,0600', '5,50,4308,2900' + ); + if (!is_null($ie_vers) && in_array($ie_vers, $buggy_list)) { + $this->setQuirk('buggy_compression'); + } + + /* Some Handhelds have their screen resolution in the + * user agent string, which we can use to look for + * mobile agents. */ + if (preg_match('/; (120x160|240x280|240x320)\)/', $this->_agent)) { + $this->_mobile = true; + } + + switch ($this->_majorVersion) { + case 6: + $this->setFeature('javascript', 1.4); + $this->setFeature('dom'); + $this->setFeature('iframes'); + $this->setFeature('utf'); + $this->setFeature('rte'); + $this->setFeature('homepage'); + $this->setFeature('accesskey'); + $this->setFeature('optgroup'); + $this->setFeature('xmlhttpreq'); + $this->setQuirk('scrollbar_in_way'); + $this->setQuirk('broken_multipart_form'); + break; + + case 5: + if ($this->getPlatform() == 'mac') { + $this->setFeature('javascript', 1.2); + $this->setFeature('optgroup'); + } else { + // MSIE 5 for Windows. + $this->setFeature('javascript', 1.4); + $this->setFeature('dom'); + $this->setFeature('xmlhttpreq'); + if ($this->_minorVersion >= 5) { + $this->setFeature('rte'); + } + } + $this->setFeature('iframes'); + $this->setFeature('utf'); + $this->setFeature('homepage'); + $this->setFeature('accesskey'); + if ($this->_minorVersion == 5) { + $this->setQuirk('break_disposition_header'); + $this->setQuirk('broken_multipart_form'); + } + break; + + case 4: + $this->setFeature('javascript', 1.2); + $this->setFeature('accesskey'); + if ($this->_minorVersion > 0) { + $this->setFeature('utf'); + } + break; + + case 3: + $this->setFeature('javascript', 1.1); + $this->setQuirk('avoid_popup_windows'); + break; + } + } elseif (preg_match('|ANTFresco/([0-9]+)|', $this->_agent, $version)) { + $this->setBrowser('fresco'); + $this->setFeature('javascript', 1.1); + $this->setQuirk('avoid_popup_windows'); + } elseif (strpos($this->_lowerAgent, 'avantgo') !== false) { + $this->setBrowser('avantgo'); + $this->_mobile = true; + } elseif (preg_match('|Konqueror/([0-9]+)|', $this->_agent, $version) || + preg_match('|Safari/([0-9]+)\.?([0-9]+)?|', $this->_agent, $version)) { + // Konqueror and Apple's Safari both use the KHTML + // rendering engine. + $this->setBrowser('konqueror'); + $this->setQuirk('empty_file_input_value'); + $this->setQuirk('no_hidden_overflow_tables'); + $this->_majorVersion = $version[1]; + if (isset($version[2])) { + $this->_minorVersion = $version[2]; + } + + if (strpos($this->_agent, 'Safari') !== false && + $this->_majorVersion >= 60) { + // Safari. + $this->setFeature('utf'); + $this->setFeature('javascript', 1.4); + $this->setFeature('dom'); + $this->setFeature('iframes'); + if ($this->_majorVersion > 125 || + ($this->_majorVersion == 125 && + $this->_minorVersion >= 1)) { + $this->setFeature('utf'); + $this->setFeature('accesskey'); + $this->setFeature('xmlhttpreq'); + } + } else { + // Konqueror. + $this->setFeature('javascript', 1.1); + switch ($this->_majorVersion) { + case 3: + $this->setFeature('dom'); + $this->setFeature('iframes'); + break; + } + } + } elseif (preg_match('|Mozilla/([0-9.]+)|', $this->_agent, $version)) { + $this->setBrowser('mozilla'); + $this->setQuirk('must_cache_forms'); + + list($this->_majorVersion, $this->_minorVersion) = explode('.', $version[1]); + switch ($this->_majorVersion) { + case 5: + if ($this->getPlatform() == 'win') { + $this->setQuirk('break_disposition_filename'); + } + $this->setFeature('javascript', 1.4); + $this->setFeature('dom'); + $this->setFeature('accesskey'); + $this->setFeature('optgroup'); + $this->setFeature('xmlhttpreq'); + $this->setFeature('cite'); + if (preg_match('|rv:(.*)\)|', $this->_agent, $revision)) { + if ($revision[1] >= 1) { + $this->setFeature('iframes'); + } + if ($revision[1] >= 1.3) { + $this->setFeature('rte'); + } + } + break; + + case 4: + $this->setFeature('javascript', 1.3); + $this->setQuirk('buggy_compression'); + break; + + case 3: + default: + $this->setFeature('javascript', 1); + $this->setQuirk('buggy_compression'); + break; + } + } elseif (preg_match('|Lynx/([0-9]+)|', $this->_agent, $version)) { + $this->setBrowser('lynx'); + $this->setFeature('images', false); + $this->setFeature('frames', false); + $this->setFeature('javascript', false); + $this->setQuirk('avoid_popup_windows'); + } elseif (preg_match('|Links \(([0-9]+)|', $this->_agent, $version)) { + $this->setBrowser('links'); + $this->setFeature('images', false); + $this->setFeature('frames', false); + $this->setFeature('javascript', false); + $this->setQuirk('avoid_popup_windows'); + } elseif (preg_match('|HotJava/([0-9]+)|', $this->_agent, $version)) { + $this->setBrowser('hotjava'); + $this->setFeature('javascript', false); + } elseif (strpos($this->_agent, 'UP/') !== false || + strpos($this->_agent, 'UP.B') !== false || + strpos($this->_agent, 'UP.L') !== false) { + $this->setBrowser('up'); + $this->setFeature('html', false); + $this->setFeature('javascript', false); + $this->setFeature('hdml'); + $this->setFeature('wml'); + + if (strpos($this->_agent, 'GUI') !== false && + strpos($this->_agent, 'UP.Link') !== false) { + /* The device accepts Openwave GUI extensions for + * WML 1.3. Non-UP.Link gateways sometimes have + * problems, so exclude them. */ + $this->setQuirk('ow_gui_1.3'); + } + $this->_mobile = true; + } elseif (strpos($this->_agent, 'Xiino/') !== false) { + $this->setBrowser('xiino'); + $this->setFeature('hdml'); + $this->setFeature('wml'); + $this->_mobile = true; + } elseif (strpos($this->_agent, 'Palmscape/') !== false) { + $this->setBrowser('palmscape'); + $this->setFeature('javascript', false); + $this->setFeature('hdml'); + $this->setFeature('wml'); + $this->_mobile = true; + } elseif (strpos($this->_agent, 'Nokia') !== false) { + $this->setBrowser('nokia'); + $this->setFeature('html', false); + $this->setFeature('wml'); + $this->setFeature('xhtml'); + $this->_mobile = true; + } elseif (strpos($this->_agent, 'Ericsson') !== false) { + $this->setBrowser('ericsson'); + $this->setFeature('html', false); + $this->setFeature('wml'); + $this->_mobile = true; + } elseif (strpos($this->_lowerAgent, 'wap') !== false) { + $this->setBrowser('wap'); + $this->setFeature('html', false); + $this->setFeature('javascript', false); + $this->setFeature('hdml'); + $this->setFeature('wml'); + $this->_mobile = true; + } elseif (strpos($this->_lowerAgent, 'docomo') !== false || + strpos($this->_lowerAgent, 'portalmmm') !== false) { + $this->setBrowser('imode'); + $this->setFeature('images', false); + $this->_mobile = true; + } elseif (strpos($this->_lowerAgent, 'j-') !== false) { + $this->setBrowser('mml'); + $this->_mobile = true; + } + } + } + + /** + * Match the platform of the browser. + * + * This is a pretty simplistic implementation, but it's intended + * to let us tell what line breaks to send, so it's good enough + * for its purpose. + * + * @access public + * + * @since Horde 2.2 + */ + function _setPlatform() + { + if (strpos($this->_lowerAgent, 'wind') !== false) { + $this->_platform = 'win'; + } elseif (strpos($this->_lowerAgent, 'mac') !== false) { + $this->_platform = 'mac'; + } else { + $this->_platform = 'unix'; + } + } + + /** + * Return the currently matched platform. + * + * @return string The user's platform. + * + * @since Horde 2.2 + */ + function getPlatform() + { + return $this->_platform; + } + + /** + * Sets the current browser. + * + * @access public + * + * @param string $browser The browser to set as current. + */ + function setBrowser($browser) + { + $this->_browser = $browser; + } + + /** + * Determine if the given browser is the same as the current. + * + * @access public + * + * @param string $browser The browser to check. + * + * @return boolean Is the given browser the same as the current? + */ + function isBrowser($browser) + { + return ($this->_browser === $browser); + } + + /** + * Do we consider the current browser to be a mobile device? + * + * @return boolean True if we do, false if we don't. + */ + function isMobile() + { + return $this->_mobile; + } + + /** + * Determines if the browser is a robot or not. + * + * @access public + * + * @return boolean True if browser is a known robot. + */ + function isRobot() + { + foreach ($this->_robots as $robot) { + if (strpos($this->_agent, $robot) !== false) { + return true; + } + } + return false; + } + + /** + * Retrieve the current browser. + * + * @access public + * + * @return string The current browser. + */ + function getBrowser() + { + return $this->_browser; + } + + /** + * Retrieve the current browser's major version. + * + * @access public + * + * @return integer The current browser's major version. + */ + function getMajor() + { + return $this->_majorVersion; + } + + /** + * Retrieve the current browser's minor version. + * + * @access public + * + * @return integer The current browser's minor version. + */ + function getMinor() + { + return $this->_minorVersion; + } + + /** + * Retrieve the current browser's version. + * + * @access public + * + * @return string The current browser's version. + */ + function getVersion() + { + return $this->_majorVersion . '.' . $this->_minorVersion; + } + + /** + * Return the full browser agent string. + * + * @access public + * + * @return string The browser agent string. + */ + function getAgentString() + { + return $this->_agent; + } + + /** + * Set unique behavior for the current browser. + * + * @access public + * + * @param string $quirk The behavior to set. + * @param optional string $value Special behavior parameter. + */ + function setQuirk($quirk, $value = true) + { + $this->_quirks[$quirk] = $value; + } + + /** + * Check unique behavior for the current browser. + * + * @access public + * + * @param string $quirk The behavior to check. + * + * @return boolean Does the browser have the behavior set? + */ + function hasQuirk($quirk) + { + return !empty($this->_quirks[$quirk]); + } + + /** + * Retreive unique behavior for the current browser. + * + * @access public + * + * @param string $quirk The behavior to retreive. + * + * @return string The value for the requested behavior. + */ + function getQuirk($quirk) + { + return isset($this->_quirks[$quirk]) + ? $this->_quirks[$quirk] + : null; + } + + /** + * Set capabilities for the current browser. + * + * @access public + * + * @param string $feature The capability to set. + * @param optional string $value Special capability parameter. + */ + function setFeature($feature, $value = true) + { + $this->_features[$feature] = $value; + } + + /** + * Check the current browser capabilities. + * + * @access public + * + * @param string $feature The capability to check. + * + * @return boolean Does the browser have the capability set? + */ + function hasFeature($feature) + { + return !empty($this->_features[$feature]); + } + + /** + * Retreive the current browser capability. + * + * @access public + * + * @param string $feature The capability to retreive. + * + * @return string The value of the requested capability. + */ + function getFeature($feature) + { + return isset($this->_features[$feature]) + ? $this->_features[$feature] + : null; + } + + /** + * Determine if we are using a secure (SSL) connection. + * + * @access public + * + * @return boolean True if using SSL, false if not. + */ + function usingSSLConnection() + { + return ((isset($_SERVER['HTTPS']) && + ($_SERVER['HTTPS'] == 'on')) || + getenv('SSL_PROTOCOL_VERSION')); + } + + /** + * Returns the server protocol in use on the current server. + * + * @access public + * + * @return string The HTTP server protocol version. + */ + function getHTTPProtocol() + { + if (isset($_SERVER['SERVER_PROTOCOL'])) { + if (($pos = strrpos($_SERVER['SERVER_PROTOCOL'], '/'))) { + return substr($_SERVER['SERVER_PROTOCOL'], $pos + 1); + } + } + + return null; + } + + /** + * Determine if files can be uploaded to the system. + * + * @access public + * + * @return integer If uploads allowed, returns the maximum size of the + * upload in bytes. Returns 0 if uploads are not + * allowed. + */ + function allowFileUploads() + { + if (ini_get('file_uploads')) { + if (($dir = ini_get('upload_tmp_dir')) && + !is_writable($dir)) { + return 0; + } + $size = ini_get('upload_max_filesize'); + switch (strtolower(substr($size, -1, 1))) { + case 'k': + $size = intval(floatval($size) * 1024); + break; + + case 'm': + $size = intval(floatval($size) * 1024 * 1024); + break; + + default: + $size = intval($size); + break; + } + return $size; + } else { + return 0; + } + } + + /** + * Determines if the file was uploaded or not. If not, will return the + * appropriate error message. + * + * @access public + * + * @param string $field The name of the field containing the + * uploaded file. + * @param optional string $name The file description string to use in the + * error message. Default: 'file'. + * + * @return mixed True on success, PEAR_Error on error. + */ + function wasFileUploaded($field, $name = null) + { + require_once 'PEAR.php'; + + if (is_null($name)) { + $name = _("file"); + } + + if (!($uploadSize = Browser::allowFileUploads())) { + return PEAR::raiseError(_("File uploads not supported.")); + } + + /* Get any index on the field name. */ + require_once 'Horde/Array.php'; + $index = Horde_Array::getArrayParts($field, $base, $keys); + + if ($index) { + /* Index present, fetch the error var to check. */ + $keys_path = array_merge(array($base, 'error'), $keys); + $error = Horde_Array::getElement($_FILES, $keys_path); + + /* Index present, fetch the tmp_name var to check. */ + $keys_path = array_merge(array($base, 'tmp_name'), $keys); + $tmp_name = Horde_Array::getElement($_FILES, $keys_path); + } else { + /* No index, simple set up of vars to check. */ + if (!isset($_FILES[$field])) { + return PEAR::raiseError(_("No file uploaded"), UPLOAD_ERR_NO_FILE); + } + $error = $_FILES[$field]['error']; + $tmp_name = $_FILES[$field]['tmp_name']; + } + + if (!isset($_FILES) || ($error == UPLOAD_ERR_NO_FILE)) { + return PEAR::raiseError(sprintf(_("There was a problem with the file upload: No %s was uploaded."), $name), UPLOAD_ERR_NO_FILE); + } elseif (($error == UPLOAD_ERR_OK) && is_uploaded_file($tmp_name)) { + return true; + } elseif (($error == UPLOAD_ERR_INI_SIZE) || + ($error == UPLOAD_ERR_FORM_SIZE)) { + return PEAR::raiseError(sprintf(_("There was a problem with the file upload: The %s was larger than the maximum allowed size (%d bytes)."), $name, $uploadSize), $error); + } elseif ($error == UPLOAD_ERR_PARTIAL) { + return PEAR::raiseError(sprintf(_("There was a problem with the file upload: The %s was only partially uploaded."), $name), $error); + } + } + + /** + * Returns the headers for a browser download. + * + * @access public + * + * @param optional string $filename The filename of the download. + * @param optional string $cType The content-type description of the + * file. + * @param optional boolean $inline True if inline, false if attachment. + * @param optional string $cLength The content-length of this file. + * + * @since Horde 2.2 + */ + function downloadHeaders($filename = 'unknown', $cType = null, + $inline = false, $cLength = null) + { + /* Remove linebreaks from file names. */ + $filename = str_replace(array("\r\n", "\r", "\n"), ' ', $filename); + + /* Some browsers don't like spaces in the filename. */ + if ($this->hasQuirk('no_filename_spaces')) { + $filename = strtr($filename, ' ', '_'); + } + + /* MSIE doesn't like multiple periods in the file name. Convert + all periods (except the last one) to underscores. */ + if ($this->isBrowser('msie')) { + if (($pos = strrpos($filename, '.'))) { + $filename = strtr(substr($filename, 0, $pos), '.', '_') . substr($filename, $pos); + } + } + + /* Content-Type/Content-Disposition Header. */ + if ($inline) { + if (!is_null($cType)) { + header('Content-Type: ' . trim($cType)); + } elseif ($this->isBrowser('msie')) { + header('Content-Type: application/x-msdownload'); + } else { + header('Content-Type: application/octet-stream'); + } + header('Content-Disposition: inline; filename="' . $filename . '"'); + } else { + if ($this->isBrowser('msie')) { + header('Content-Type: application/x-msdownload'); + } elseif (!is_null($cType)) { + header('Content-Type: ' . trim($cType)); + } else { + header('Content-Type: application/octet-stream'); + } + + if ($this->hasQuirk('break_disposition_header')) { + header('Content-Disposition: filename="' . $filename . '"'); + } else { + header('Content-Disposition: attachment; filename="' . $filename . '"'); + } + } + + /* Content-Length Header. Don't send Content-Length for + * HTTP/1.1 servers. */ + if (($this->getHTTPProtocol() != '1.1') && !is_null($cLength)) { + header('Content-Length: ' . $cLength); + } + + /* Overwrite Pragma: and other caching headers for IE. */ + if ($this->hasQuirk('cache_ssl_downloads')) { + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Pragma: public'); + } + } + + /** + * Determines if a browser can display a given MIME type. + * + * @access public + * + * @param string $mimetype The MIME type to check. + * + * @return boolean True if the browser can display the MIME type. + */ + function isViewable($mimetype) + { + $mimetype = strtolower($mimetype); + list($type, $subtype) = explode('/', $mimetype); + + if (!empty($this->_accept)) { + $wildcard_match = false; + + if (strpos($this->_accept, $mimetype) !== false) { + return true; + } + + if (strpos($this->_accept, '*/*') !== false) { + $wildcard_match = true; + if ($type != 'image') { + return true; + } + } + + /* image/jpeg and image/pjpeg *appear* to be the same + * entity, but Mozilla doesn't seem to want to accept the + * latter. For our purposes, we will treat them the + * same. */ + if ($this->isBrowser('mozilla') && + ($mimetype == 'image/pjpeg') && + (strpos($this->_accept, 'image/jpeg') !== false)) { + return true; + } + + if (!$wildcard_match) { + return false; + } + } + + if (!$this->hasFeature('images') || ($type != 'image')) { + return false; + } + + return (in_array($subtype, $this->_images)); + } + + /** + * Escape characters in javascript code if the browser requires it. + * %23, %26, and %2B (for IE) and %27 need to be escaped or else + * jscript will interpret it as a single quote, pound sign, or + * ampersand and refuse to work. + * + * @access public + * + * @param string $code The JS code to escape. + * + * @return string The escaped code. + */ + function escapeJSCode($code) + { + $from = $to = array(); + + if ($this->isBrowser('msie') || + ($this->isBrowser('mozilla') && ($this->getMajor() >= 5))) { + $from = array('%23', '%26', '%2B'); + $to = array(urlencode('%23'), urlencode('%26'), urlencode('%2B')); + } + $from[] = '%27'; + $to[] = '\%27'; + + return str_replace($from, $to, $code); + } + + /** + * Set the IE version in the session. + * + * @access public + * + * @param string $ver The IE Version string. + */ + function setIEVersion($ver) + { + $_SESSION['__browser'] = array( + 'ie_version' => $ver + ); + } + + /** + * Return the IE version stored in the session, if available. + * + * @access public + * + * @return mixed The IE Version string or null if no string is stored. + */ + function getIEVersion() + { + return isset($_SESSION['__browser']['ie_version']) ? $_SESSION['__browser']['ie_version'] : null; + } + +} diff --git a/lib/album.php b/lib/album.php new file mode 100644 index 00000000..7d518ff8 --- /dev/null +++ b/lib/album.php @@ -0,0 +1,31 @@ +<?php +/* + + This library handles album related functions.... wooo! + //FIXME: Remove this in favor of /modules/class/album +*/ + +/*! + @function get_albums + @discussion pass a sql statement, and it gets full album info and returns + an array of the goods.. can be set to format them as well +*/ +function get_albums($sql, $action=0) { + + $db_results = mysql_query($sql, dbh()); + while ($r = mysql_fetch_array($db_results)) { + $album = new Album($r[0]); + $album->format_album(); + $albums[] = $album; + } + + return $albums; + + +} // get_albums + + + + + +?> diff --git a/lib/archive.php b/lib/archive.php new file mode 100644 index 00000000..1a605f10 --- /dev/null +++ b/lib/archive.php @@ -0,0 +1,778 @@ +<?php
+/*--------------------------------------------------
+ | TAR/GZIP/BZIP2/ZIP ARCHIVE CLASSES 2.0
+ | By Devin Doucette
+ | Copyright (c) 2004 Devin Doucette
+ | Email: darksnoopy@shaw.ca
+ +--------------------------------------------------
+ | Email bugs/suggestions to darksnoopy@shaw.ca
+ +--------------------------------------------------
+ | This script has been created and released under
+ | the GNU GPL and is free to use and redistribute
+ | only if this copyright statement is not removed
+ +--------------------------------------------------*/
+
+class archive
+{
+ function archive($name)
+ {
+ $this->options = array(
+ 'basedir'=>".",
+ 'name'=>$name,
+ 'prepend'=>"",
+ 'inmemory'=>0,
+ 'overwrite'=>0,
+ 'recurse'=>1,
+ 'storepaths'=>1,
+ 'level'=>3,
+ 'method'=>1,
+ 'sfx'=>"",
+ 'type'=>"",
+ 'comment'=>""
+ );
+ $this->files = array();
+ $this->exclude = array();
+ $this->storeonly = array();
+ $this->error = array();
+ }
+
+ function set_options($options)
+ {
+ foreach($options as $key => $value)
+ {
+ $this->options[$key] = $value;
+ }
+ if(!empty($this->options['basedir']))
+ {
+ $this->options['basedir'] = str_replace("\\","/",$this->options['basedir']);
+ $this->options['basedir'] = preg_replace("/\/+/","/",$this->options['basedir']);
+ $this->options['basedir'] = preg_replace("/\/$/","",$this->options['basedir']);
+ }
+ if(!empty($this->options['name']))
+ {
+ $this->options['name'] = str_replace("\\","/",$this->options['name']);
+ $this->options['name'] = preg_replace("/\/+/","/",$this->options['name']);
+ }
+ if(!empty($this->options['prepend']))
+ {
+ $this->options['prepend'] = str_replace("\\","/",$this->options['prepend']);
+ $this->options['prepend'] = preg_replace("/^(\.*\/+)+/","",$this->options['prepend']);
+ $this->options['prepend'] = preg_replace("/\/+/","/",$this->options['prepend']);
+ $this->options['prepend'] = preg_replace("/\/$/","",$this->options['prepend']) . "/";
+ }
+ }
+
+ function create_archive()
+ {
+ $this->make_list();
+
+ if($this->options['inmemory'] == 0)
+ {
+ $pwd = getcwd();
+ chdir($this->options['basedir']);
+ if($this->options['overwrite'] == 0 && file_exists($this->options['name'] . ($this->options['type'] == "gzip" || $this->options['type'] == "bzip"? ".tmp" : "")))
+ {
+ $this->error[] = "File {$this->options['name']} already exists.";
+ chdir($pwd);
+ return 0;
+ }
+ else if($this->archive = @fopen($this->options['name'] . ($this->options['type'] == "gzip" || $this->options['type'] == "bzip"? ".tmp" : ""),"wb+"))
+ {
+ chdir($pwd);
+ }
+ else
+ {
+ $this->error[] = "Could not open {$this->options['name']} for writing.";
+ chdir($pwd);
+ return 0;
+ }
+ }
+ else
+ {
+ $this->archive = "";
+ }
+
+ switch($this->options['type'])
+ {
+ case "zip":
+ if(!$this->create_zip())
+ {
+ $this->error[] = "Could not create zip file.";
+ return 0;
+ }
+ break;
+ case "bzip":
+ if(!$this->create_tar())
+ {
+ $this->error[] = "Could not create tar file.";
+ return 0;
+ }
+ if(!$this->create_bzip())
+ {
+ $this->error[] = "Could not create bzip2 file.";
+ return 0;
+ }
+ break;
+ case "gzip":
+ if(!$this->create_tar())
+ {
+ $this->error[] = "Could not create tar file.";
+ return 0;
+ }
+ if(!$this->create_gzip())
+ {
+ $this->error[] = "Could not create gzip file.";
+ return 0;
+ }
+ break;
+ case "tar":
+ if(!$this->create_tar())
+ {
+ $this->error[] = "Could not create tar file.";
+ return 0;
+ }
+ }
+
+ if($this->options['inmemory'] == 0)
+ {
+ fclose($this->archive);
+ if($this->options['type'] == "gzip" || $this->options['type'] == "bzip")
+ {
+ unlink($this->options['basedir'] . "/" . $this->options['name'] . ".tmp");
+ }
+ }
+ }
+
+ function add_data($data)
+ {
+ if($this->options['inmemory'] == 0)
+ {
+ fwrite($this->archive,$data);
+ }
+ else
+ {
+ $this->archive .= $data;
+ }
+ }
+
+ function make_list()
+ {
+ if(!empty($this->exclude))
+ {
+ foreach($this->files as $key => $value)
+ {
+ foreach($this->exclude as $current)
+ {
+ if($value['name'] == $current['name'])
+ {
+ unset($this->files[$key]);
+ }
+ }
+ }
+ }
+ if(!empty($this->storeonly))
+ {
+ foreach($this->files as $key => $value)
+ {
+ foreach($this->storeonly as $current)
+ {
+ if($value['name'] == $current['name'])
+ {
+ $this->files[$key]['method'] = 0;
+ }
+ }
+ }
+ }
+ unset($this->exclude,$this->storeonly);
+ }
+
+ function add_files($list)
+ {
+ $temp = $this->list_files($list);
+ foreach($temp as $current)
+ {
+ $this->files[] = $current;
+ }
+ }
+
+ function exclude_files($list)
+ {
+ $temp = $this->list_files($list);
+ foreach($temp as $current)
+ {
+ $this->exclude[] = $current;
+ }
+ }
+
+ function store_files($list)
+ {
+ $temp = $this->list_files($list);
+ foreach($temp as $current)
+ {
+ $this->storeonly[] = $current;
+ }
+ }
+
+ function list_files($list)
+ {
+ if(!is_array($list))
+ {
+ $temp = $list;
+ $list = array($temp);
+ unset($temp);
+ }
+
+ $files = array();
+
+ $pwd = getcwd();
+ chdir($this->options['basedir']);
+
+ foreach($list as $current)
+ {
+ $current = str_replace("\\","/",$current);
+ $current = preg_replace("/\/+/","/",$current);
+ $current = preg_replace("/\/$/","",$current);
+ if(strstr($current,"*"))
+ {
+ $regex = preg_replace("/([\\\^\$\.\[\]\|\(\)\?\+\{\}\/])/","\\\\\\1",$current);
+ $regex = str_replace("*",".*",$regex);
+ $dir = strstr($current,"/")? substr($current,0,strrpos($current,"/")) : ".";
+ $temp = $this->parse_dir($dir);
+ foreach($temp as $current2)
+ {
+ if(preg_match("/^{$regex}$/i",$current2['name']))
+ {
+ $files[] = $current2;
+ }
+ }
+ unset($regex,$dir,$temp,$current);
+ }
+ else if(@is_dir($current))
+ {
+ $temp = $this->parse_dir($current);
+ foreach($temp as $file)
+ {
+ $files[] = $file;
+ }
+ unset($temp,$file);
+ }
+ else if(@file_exists($current))
+ {
+ $files[] = array('name'=>$current,'name2'=>$this->options['prepend'] .
+ preg_replace("/(\.+\/+)+/","",($this->options['storepaths'] == 0 && strstr($current,"/"))?
+ substr($current,strrpos($current,"/") + 1) : $current),'type'=>0,
+ 'ext'=>substr($current,strrpos($current,".")),'stat'=>stat($current));
+ }
+ }
+
+ chdir($pwd);
+
+ unset($current,$pwd);
+
+ usort($files,array("archive","sort_files"));
+
+ return $files;
+ }
+
+ function parse_dir($dirname)
+ {
+ if($this->options['storepaths'] == 1 && !preg_match("/^(\.+\/*)+$/",$dirname))
+ {
+ $files = array(array('name'=>$dirname,'name2'=>$this->options['prepend'] .
+ preg_replace("/(\.+\/+)+/","",($this->options['storepaths'] == 0 && strstr($dirname,"/"))?
+ substr($dirname,strrpos($dirname,"/") + 1) : $dirname),'type'=>5,'stat'=>stat($dirname)));
+ }
+ else
+ {
+ $files = array();
+ }
+ $dir = @opendir($dirname);
+
+ while($file = @readdir($dir))
+ {
+ if($file == "." || $file == "..")
+ {
+ continue;
+ }
+ else if(@is_dir($dirname."/".$file))
+ {
+ if(empty($this->options['recurse']))
+ {
+ continue;
+ }
+ $temp = $this->parse_dir($dirname."/".$file);
+ foreach($temp as $file2)
+ {
+ $files[] = $file2;
+ }
+ }
+ else if(@file_exists($dirname."/".$file))
+ {
+ $files[] = array('name'=>$dirname."/".$file,'name2'=>$this->options['prepend'] .
+ preg_replace("/(\.+\/+)+/","",($this->options['storepaths'] == 0 && strstr($dirname."/".$file,"/"))?
+ substr($dirname."/".$file,strrpos($dirname."/".$file,"/") + 1) : $dirname."/".$file),'type'=>0,
+ 'ext'=>substr($file,strrpos($file,".")),'stat'=>stat($dirname."/".$file));
+ }
+ }
+
+ @closedir($dir);
+
+ return $files;
+ }
+
+ function sort_files($a,$b)
+ {
+ if($a['type'] != $b['type'])
+ {
+ return $a['type'] > $b['type']? -1 : 1;
+ }
+ else if($a['type'] == 5)
+ {
+ return strcmp(strtolower($a['name']),strtolower($b['name']));
+ }
+ else
+ {
+ if($a['ext'] != $b['ext'])
+ {
+ return strcmp($a['ext'],$b['ext']);
+ }
+ else if($a['stat'][7] != $b['stat'][7])
+ {
+ return $a['stat'][7] > $b['stat'][7]? -1 : 1;
+ }
+ else
+ {
+ return strcmp(strtolower($a['name']),strtolower($b['name']));
+ }
+ }
+ return 0;
+ }
+
+ function download_file()
+ {
+ if($this->options['inmemory'] == 0)
+ {
+ $this->error[] = "Can only use download_file() if archive is in memory. Redirect to file otherwise, it is faster.";
+ return;
+ }
+ switch($this->options['type'])
+ {
+ case "zip":
+ header("Content-type:application/zip");
+ break;
+ case "bzip":
+ header("Content-type:application/x-compressed");
+ break;
+ case "gzip":
+ header("Content-type:application/x-compressed");
+ break;
+ case "tar":
+ header("Content-type:application/x-tar");
+ }
+ $header = "Content-disposition: attachment; filename=\"";
+ $header .= strstr($this->options['name'],"/")? substr($this->options['name'],strrpos($this->options['name'],"/") + 1) : $this->options['name'];
+ $header .= "\"";
+ header($header);
+ header("Content-length: " . strlen($this->archive));
+ header("Content-transfer-encoding: binary");
+ header("Pragma: no-cache");
+ header("Expires: 0");
+ print($this->archive);
+ }
+}
+
+class tar_file extends archive
+{
+ function tar_file($name)
+ {
+ $this->archive($name);
+ $this->options['type'] = "tar";
+ }
+
+ function create_tar()
+ {
+ $pwd = getcwd();
+ chdir($this->options['basedir']);
+
+ foreach($this->files as $current)
+ {
+ if($current['name'] == $this->options['name'])
+ {
+ continue;
+ }
+ if(strlen($current['name2']) > 99)
+ {
+ $path = substr($current['name2'],0,strpos($current['name2'],"/",strlen($current['name2']) - 100) + 1);
+ $current['name2'] = substr($current['name2'],strlen($path));
+ if(strlen($path) > 154 || strlen($current['name2']) > 99)
+ {
+ $this->error[] = "Could not add {$path}{$current['name2']} to archive because the filename is too long.";
+ continue;
+ }
+ }
+ $block = pack("a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12",$current['name2'],decoct($current['stat'][2]),
+ sprintf("%6s ",decoct($current['stat'][4])),sprintf("%6s ",decoct($current['stat'][5])),
+ sprintf("%11s ",decoct($current['stat'][7])),sprintf("%11s ",decoct($current['stat'][9])),
+ " ",$current['type'],"","ustar","00","Unknown","Unknown","","",!empty($path)? $path : "","");
+
+ $checksum = 0;
+ for($i = 0; $i < 512; $i++)
+ {
+ $checksum += ord(substr($block,$i,1));
+ }
+ $checksum = pack("a8",sprintf("%6s ",decoct($checksum)));
+ $block = substr_replace($block,$checksum,148,8);
+
+ if($current['stat'][7] == 0)
+ {
+ $this->add_data($block);
+ }
+ else if($fp = @fopen($current['name'],"rb"))
+ {
+ $this->add_data($block);
+ while($temp = fread($fp,1048576))
+ {
+ $this->add_data($temp);
+ }
+ if($current['stat'][7] % 512 > 0)
+ {
+ $temp = "";
+ for($i = 0; $i < 512 - $current['stat'][7] % 512; $i++)
+ {
+ $temp .= "\0";
+ }
+ $this->add_data($temp);
+ }
+ fclose($fp);
+ }
+ else
+ {
+ $this->error[] = "Could not open file {$current['name']} for reading. It was not added.";
+ }
+ }
+
+ $this->add_data(pack("a512",""));
+
+ chdir($pwd);
+
+ return 1;
+ }
+
+ function extract_files()
+ {
+ $pwd = getcwd();
+ chdir($this->options['basedir']);
+
+ if($fp = $this->open_archive())
+ {
+ if($this->options['inmemory'] == 1)
+ {
+ $this->files = array();
+ }
+
+ while($block = fread($fp,512))
+ {
+ $temp = unpack("a100name/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1type/a100temp/a6magic/a2temp/a32temp/a32temp/a8temp/a8temp/a155prefix/a12temp",$block);
+ $file = array(
+ 'name'=>$temp['prefix'] . $temp['name'],
+ 'stat'=>array(
+ 2=>$temp['mode'],
+ 4=>octdec($temp['uid']),
+ 5=>octdec($temp['gid']),
+ 7=>octdec($temp['size']),
+ 9=>octdec($temp['mtime']),
+ ),
+ 'checksum'=>octdec($temp['checksum']),
+ 'type'=>$temp['type'],
+ 'magic'=>$temp['magic'],
+ );
+ if($file['checksum'] == 0x00000000)
+ {
+ break;
+ }
+ else if($file['magic'] != "ustar")
+ {
+ $this->error[] = "This script does not support extracting this type of tar file.";
+ break;
+ }
+ $block = substr_replace($block," ",148,8);
+ $checksum = 0;
+ for($i = 0; $i < 512; $i++)
+ {
+ $checksum += ord(substr($block,$i,1));
+ }
+ if($file['checksum'] != $checksum)
+ {
+ $this->error[] = "Could not extract from {$this->options['name']}, it is corrupt.";
+ }
+
+ if($this->options['inmemory'] == 1)
+ {
+ $file['data'] = fread($fp,$file['stat'][7]);
+ fread($fp,(512 - $file['stat'][7] % 512) == 512? 0 : (512 - $file['stat'][7] % 512));
+ unset($file['checksum'],$file['magic']);
+ $this->files[] = $file;
+ }
+ else
+ {
+ if($file['type'] == 5)
+ {
+ if(!is_dir($file['name']))
+ {
+ mkdir($file['name'],$file['stat'][2]);
+ chown($file['name'],$file['stat'][4]);
+ chgrp($file['name'],$file['stat'][5]);
+ }
+ }
+ else if($this->options['overwrite'] == 0 && file_exists($file['name']))
+ {
+ $this->error[] = "{$file['name']} already exists.";
+ }
+ else if($new = @fopen($file['name'],"wb"))
+ {
+ fwrite($new,fread($fp,$file['stat'][7]));
+ fread($fp,(512 - $file['stat'][7] % 512) == 512? 0 : (512 - $file['stat'][7] % 512));
+ fclose($new);
+ chmod($file['name'],$file['stat'][2]);
+ chown($file['name'],$file['stat'][4]);
+ chgrp($file['name'],$file['stat'][5]);
+ }
+ else
+ {
+ $this->error[] = "Could not open {$file['name']} for writing.";
+ }
+ }
+ unset($file);
+ }
+ }
+ else
+ {
+ $this->error[] = "Could not open file {$this->options['name']}";
+ }
+
+ chdir($pwd);
+ }
+
+ function open_archive()
+ {
+ return @fopen($this->options['name'],"rb");
+ }
+}
+
+class gzip_file extends tar_file
+{
+ function gzip_file($name)
+ {
+ $this->tar_file($name);
+ $this->options['type'] = "gzip";
+ }
+
+ function create_gzip()
+ {
+ if($this->options['inmemory'] == 0)
+ {
+ $pwd = getcwd();
+ chdir($this->options['basedir']);
+ if($fp = gzopen($this->options['name'],"wb{$this->options['level']}"))
+ {
+ fseek($this->archive,0);
+ while($temp = fread($this->archive,1048576))
+ {
+ gzwrite($fp,$temp);
+ }
+ gzclose($fp);
+ chdir($pwd);
+ }
+ else
+ {
+ $this->error[] = "Could not open {$this->options['name']} for writing.";
+ chdir($pwd);
+ return 0;
+ }
+ }
+ else
+ {
+ $this->archive = gzencode($this->archive,$this->options['level']);
+ }
+
+ return 1;
+ }
+
+ function open_archive()
+ {
+ return @gzopen($this->options['name'],"rb");
+ }
+}
+
+class bzip_file extends tar_file
+{
+ function bzip_file($name)
+ {
+ $this->tar_file($name);
+ $this->options['type'] = "bzip";
+ }
+
+ function create_bzip()
+ {
+ if($this->options['inmemory'] == 0)
+ {
+ $pwd = getcwd();
+ chdir($this->options['basedir']);
+ if($fp = bzopen($this->options['name'],"wb"))
+ {
+ fseek($this->archive,0);
+ while($temp = fread($this->archive,1048576))
+ {
+ bzwrite($fp,$temp);
+ }
+ bzclose($fp);
+ chdir($pwd);
+ }
+ else
+ {
+ $this->error[] = "Could not open {$this->options['name']} for writing.";
+ chdir($pwd);
+ return 0;
+ }
+ }
+ else
+ {
+ $this->archive = bzcompress($this->archive,$this->options['level']);
+ }
+
+ return 1;
+ }
+
+ function open_archive()
+ {
+ return @bzopen($this->options['name'],"rb");
+ }
+}
+
+class zip_file extends archive
+{
+ function zip_file($name)
+ {
+ $this->archive($name);
+ $this->options['type'] = "zip";
+ }
+
+ function create_zip()
+ {
+ $files = 0;
+ $offset = 0;
+ $central = "";
+
+ if(!empty($this->options['sfx']))
+ {
+ if($fp = @fopen($this->options['sfx'],"rb"))
+ {
+ $temp = fread($fp,filesize($this->options['sfx']));
+ fclose($fp);
+ $this->add_data($temp);
+ $offset += strlen($temp);
+ unset($temp);
+ }
+ else
+ {
+ $this->error[] = "Could not open sfx module from {$this->options['sfx']}.";
+ }
+ }
+
+ $pwd = getcwd();
+ chdir($this->options['basedir']);
+
+ foreach($this->files as $current)
+ {
+ if($current['name'] == $this->options['name'])
+ {
+ continue;
+ }
+
+ $translate = array('Ç'=>pack("C",128),'ü'=>pack("C",129),'é'=>pack("C",130),'â'=>pack("C",131),'ä'=>pack("C",132),
+ 'à'=>pack("C",133),'å'=>pack("C",134),'ç'=>pack("C",135),'ê'=>pack("C",136),'ë'=>pack("C",137),
+ 'è'=>pack("C",138),'ï'=>pack("C",139),'î'=>pack("C",140),'ì'=>pack("C",141),'Ä'=>pack("C",142),
+ 'Å'=>pack("C",143),'É'=>pack("C",144),'æ'=>pack("C",145),'Æ'=>pack("C",146),'ô'=>pack("C",147),
+ 'ö'=>pack("C",148),'ò'=>pack("C",149),'û'=>pack("C",150),'ù'=>pack("C",151),'Ö'=>pack("C",153),
+ 'Ü'=>pack("C",154),'£'=>pack("C",156),'¥'=>pack("C",157),'ƒ'=>pack("C",159),'á'=>pack("C",160),
+ 'í'=>pack("C",161),'ó'=>pack("C",162),'ú'=>pack("C",163),'ñ'=>pack("C",164),'Ñ'=>pack("C",165));
+ $current['name2'] = strtr($current['name2'],$translate);
+
+ $timedate = explode(" ",date("Y n j G i s",$current['stat'][9]));
+ $timedate = ($timedate[0] - 1980 << 25) | ($timedate[1] << 21) | ($timedate[2] << 16) |
+ ($timedate[3] << 11) | ($timedate[4] << 5) | ($timedate[5]);
+
+ $block = pack("VvvvV",0x04034b50,0x000A,0x0000,(isset($current['method']) || $this->options['method'] == 0)? 0x0000 : 0x0008,$timedate);
+
+ if($current['stat'][7] == 0 && $current['type'] == 5)
+ {
+ $block .= pack("VVVvv",0x00000000,0x00000000,0x00000000,strlen($current['name2']) + 1,0x0000);
+ $block .= $current['name2'] . "/";
+ $this->add_data($block);
+ $central .= pack("VvvvvVVVVvvvvvVV",0x02014b50,0x0014,$this->options['method'] == 0? 0x0000 : 0x000A,0x0000,
+ (isset($current['method']) || $this->options['method'] == 0)? 0x0000 : 0x0008,$timedate,
+ 0x00000000,0x00000000,0x00000000,strlen($current['name2']) + 1,0x0000,0x0000,0x0000,0x0000,$current['type'] == 5? 0x00000010 : 0x00000000,$offset);
+ $central .= $current['name2'] . "/";
+ $files++;
+ $offset += (31 + strlen($current['name2']));
+ }
+ else if($current['stat'][7] == 0)
+ {
+ $block .= pack("VVVvv",0x00000000,0x00000000,0x00000000,strlen($current['name2']),0x0000);
+ $block .= $current['name2'];
+ $this->add_data($block);
+ $central .= pack("VvvvvVVVVvvvvvVV",0x02014b50,0x0014,$this->options['method'] == 0? 0x0000 : 0x000A,0x0000,
+ (isset($current['method']) || $this->options['method'] == 0)? 0x0000 : 0x0008,$timedate,
+ 0x00000000,0x00000000,0x00000000,strlen($current['name2']),0x0000,0x0000,0x0000,0x0000,$current['type'] == 5? 0x00000010 : 0x00000000,$offset);
+ $central .= $current['name2'];
+ $files++;
+ $offset += (30 + strlen($current['name2']));
+ }
+ else if($fp = @fopen($current['name'],"rb"))
+ {
+ $temp = fread($fp,$current['stat'][7]);
+ fclose($fp);
+ $crc32 = crc32($temp);
+ if(!isset($current['method']) && $this->options['method'] == 1)
+ {
+ $temp = gzcompress($temp,$this->options['level']);
+ $size = strlen($temp) - 6;
+ $temp = substr($temp,2,$size);
+ }
+ else
+ {
+ $size = strlen($temp);
+ }
+ $block .= pack("VVVvv",$crc32,$size,$current['stat'][7],strlen($current['name2']),0x0000);
+ $block .= $current['name2'];
+ $this->add_data($block);
+ $this->add_data($temp);
+ unset($temp);
+ $central .= pack("VvvvvVVVVvvvvvVV",0x02014b50,0x0014,$this->options['method'] == 0? 0x0000 : 0x000A,0x0000,
+ (isset($current['method']) || $this->options['method'] == 0)? 0x0000 : 0x0008,$timedate,
+ $crc32,$size,$current['stat'][7],strlen($current['name2']),0x0000,0x0000,0x0000,0x0000,0x00000000,$offset);
+ $central .= $current['name2'];
+ $files++;
+ $offset += (30 + strlen($current['name2']) + $size);
+ }
+ else
+ {
+ $this->error[] = "Could not open file {$current['name']} for reading. It was not added.";
+ }
+ }
+
+ $this->add_data($central);
+
+ $this->add_data(pack("VvvvvVVv",0x06054b50,0x0000,0x0000,$files,$files,strlen($central),$offset,
+ !empty($this->options['comment'])? strlen($this->options['comment']) : 0x0000));
+
+ if(!empty($this->options['comment']))
+ {
+ $this->add_data($this->options['comment']);
+ }
+
+ chdir($pwd);
+
+ return 1;
+ }
+} ?>
\ No newline at end of file diff --git a/lib/artist.php b/lib/artist.php new file mode 100644 index 00000000..5ccd200a --- /dev/null +++ b/lib/artist.php @@ -0,0 +1,122 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + + This library handles all artist mojo + +*/ + +/*! + @function get_artists + @discussion run a search, takes string,field,type and returns an array + of results of the correct type (song, album, artist) +*/ +function get_artists($sql, $action=0) { + + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_array($db_results)) { + $artist_info = get_artist_info($r['id']); + if ($action ==='format') { $artist = format_artist($artist_info); } + else { $artist = $artist_info; } + $artists[] = $artist; + } // end while + + return $artists; + +} // get_artists + +/*! + @function format_artist + @discussion this function takes an array of artist + information and reformats the relevent values + so they can be displayed in a table for example + it changes the title into a full link. +*/ +function format_artist($artist) { + + $web_path = conf('web_path'); + $artist['name'] = "<a href=\"$web_path/artists.php?action=show&artist=" . $artist['id'] . "\">" . $artist['prefix'] . " " . $artist['name'] . "</a>"; + + return $artist; + +} // format_artist + +/*! + @function show_artists + @discussion takes a match and accounts for the possiblity of a view + then displays _many_ artists +*/ +function show_artists ($match = '') { + + global $settings; + $dbh = dbh(); + + $view = new View(); + $view->import_session_view(); + + // Check for the view object... + if ($_REQUEST['keep_view']) { + $view->initialize(); + } + + // If there isn't a view object we need to create a new one.. + else { + if ( isset($match) && $match != '' ) { + $query = "SELECT id,name FROM artist " . + " WHERE name LIKE '$match%' "; + } + else { + $query = "SELECT id FROM artist "; + } + + $db_results = mysql_query($query, $dbh); + $total_items = mysql_num_rows($db_results); + if ($_REQUEST['match'] === "Show_all") { + $offset_limit = 999999; + } + else { + $offset_limit = $_SESSION['userdata']['offset_limit']; + } + $view = new View($query,'artists.php','name',$total_items,$offset_limit); + } // end if creating view object + + if (is_array($match)) { + $artists = $match; + $_SESSION['view_script'] = false; + } + + $db_results = mysql_query($view->sql, $dbh); + while ($r = @mysql_fetch_array($db_results)) { + $artist_info = get_artist_info($r[0]); + $artist = format_artist($artist_info); + // Only Add this artist if there is information to go along with it + if ($artist_info) { + $artists[] = $artist; + } + } + + if (count($artists)) { + require ( conf('prefix') . "/templates/show_artists.inc"); + } + +} // show_artists + + + + +?> diff --git a/lib/batch.php b/lib/batch.php new file mode 100644 index 00000000..a3a3aba3 --- /dev/null +++ b/lib/batch.php @@ -0,0 +1,63 @@ +<?php +/* + +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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +/* + @function get_song_files + @discussion tmakes array of song ids and returns + array of path to actual files + @param $song_ids an array of song ids whose filenames you need +*/ +function get_song_files( $song_ids ) { + global $user; + $song_files = array(); + foreach( $song_ids as $song_id ) { + $song = new Song( $song_id ); + /* Don't archive disabled songs */ + if ($song->status != 'disabled') { + $user->update_stats( $song_id ); + $total_size += sprintf("%.2f",($song->size/1048576));; + array_push( $song_files, $song->file ); + } // if song isn't disabled + } + return array($song_files,$total_size); +} //get_song_files + + +/*! + @function send_zip + @discussion takes array of full paths to songs + zips them and sends them + @param $song_files array of full paths to songs to zip + create w/ call to get_song_files +*/ +function send_zip( $name, $song_files ) { + require_once(conf('prefix') . '/lib/archive.php' ); + $arc = new zip_file( $name . ".zip" ); + $options = array( + 'inmemory' => 1, // create archive in memory + 'storepaths' => 0, // only store file name, not full path + 'level' => 0 // no compression + ); + $arc->set_options( $options ); + $arc->add_files( $song_files ); + $arc->create_archive(); + $arc->download_file(); +} +?> diff --git a/lib/debug.php b/lib/debug.php new file mode 100644 index 00000000..c554daf7 --- /dev/null +++ b/lib/debug.php @@ -0,0 +1,361 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +/* + @header Debug Library + This library is loaded when somehow our mojo has + been lost, it contains functions for checking sql + connections, web paths etc.. +*/ + +/*! + @function read_config_file + @discussion checks to see if the config + file is readable, overkill I know.. + @param level 0 is readable, 1 detailed info +*/ +function read_config_file($file,$level=0) { + + $fp = @fopen($file, 'r'); + + if (!$level) { + return is_resource($fp); + } + + +} // read_config_file + +/*! + @function check_database + @discussion checks the local mysql db + and make sure life is good +*/ +function check_database($host,$username,$pass,$db,$level=0) { + + $dbh = @mysql_connect($host, $username, $pass); + + if (!is_resource($dbh)) { + $error['error_state'] = true; + $error['mysql_error'] = mysql_errno() . ": " . mysql_error() . "\n"; + } + if (!$host || !$username || !$pass) { + $error['error_state'] = true; + $error['mysql_error'] .= "<br />HOST:$host<br />User:$username<br />Pass:$pass<br />"; + } +print_r($error); + if ($error['error_state']) { return false; } + + + return $dbh; + +} // check_database + +/*! + @function check_database_inserted + @discussion checks to make sure that you + have inserted the database and that the user + you are using has access to it +*/ +function check_database_inserted($dbh,$db_name) { + + + if (!@mysql_select_db($db_name,$dbh)) { + return false; + } + + $sql = "DESCRIBE session"; + $db_results = @mysql_query($sql, $dbh); + if (!@mysql_num_rows($db_results)) { + return false; + } + + return true; + +} // check_database_inserted + +/*! + @function check_php_ver + @discussion checks the php version and makes + sure that it's good enough +*/ +function check_php_ver($level=0) { + + if (strcmp('4.1.2',phpversion()) > 0) { + $error['error_state'] = true; + $error['php_ver'] = phpversion(); + } + + if ($error['error_state']) { return false; } + + return true; + +} // check_php_ver + +/*! + @function check_php_mysql + @discussion checks for mysql support +*/ +function check_php_mysql() { + + if (!function_exists('mysql_query')) { + $error['error_state'] = true; + $error['php_mysql'] = false; + } + + if ($error['error_state']) { return false; } + + return true; + +} // check_php_mysql + +/*! + @function check_php_session + @discussion checks to make sure the needed functions + for sessions exist +*/ +function check_php_session() { + + if (!function_exists('session_set_save_handler')) { + $error['error_state'] = true; + $error['php_session'] = false; + } + + if ($error['error_state']) { return false; } + + return true; + +} // check_php_session + +/*! + @function check_php_iconv + @discussion checks to see if you have iconv installed +*/ +function check_php_iconv() { + + if (!function_exists('iconv')) { + $error['error_state'] = true; + $error['php_iconv'] = false; + } + + if ($error['error_state']) { return false; } + + return true; + +} // check_php_iconv + +/*! + @function check_config_values() + @discussion checks to make sure that they have at + least set the needed variables +*/ +function check_config_values($conf) { + + if (!$conf['libglue']['local_host']) { + return false; + } + if (!$conf['libglue']['local_db']) { + return false; + } + if (!$conf['libglue']['local_username']) { + return false; + } + if (!$conf['libglue']['local_pass']) { + return false; + } + if (!$conf['libglue']['local_length']) { + return false; + } + + return true; + +} // check_config_values + +/*! + @function show_compare_config + @discussion shows the difference between ampache.cfg + and ampache.cfg.dst +*/ +function show_compare_config($prefix) { + + + // Live Config File + $live_config = $prefix . "/config/ampache.cfg.php"; + + // Generic Config File + $generic_config = $prefix . "/config/ampache.cfg.dist"; + +} // show_compare_config + + +/*! + @function debug_read_config + @discussion this is the same as the read config function + except it will pull config values with a # before them + (basicly adding a #config="value" check) and not + ever dieing on a config file error +*/ +function debug_read_config($config_file,$debug) { + + $fp = @fopen($config_file,'r'); + if(!is_resource($fp)) return false; + $file_data = fread($fp,filesize($config_file)); + fclose($fp); + + // explode the var by \n's + $data = explode("\n",$file_data); + if($debug) echo "<pre>"; + $count = 0; + + foreach($data as $value) { + $count++; + + $value = trim($value); + + if (preg_match("/^\[([A-Za-z]+)\]$/",$value,$matches)) { + // If we have previous data put it into $results... + if (isset($config_name) && isset(${$config_name}) && count(${$config_name})) { + $results[$config_name] = ${$config_name}; + } + + $config_name = $matches[1]; + + } // if it is a [section] name + + + elseif (isset($config_name)) { + + // if it's not a comment + if (preg_match("/^#?([\w\d]+)\s+=\s+[\"]{1}(.*?)[\"]{1}$/",$value,$matches) + || preg_match("/^#?([\w\d]+)\s+=\s+[\']{1}(.*?)[\']{1}$/", $value, $matches) + || preg_match("/^#?([\w\d]+)\s+=\s+[\'\"]{0}(.*)[\'\"]{0}$/",$value,$matches)) { + + if (isset(${$config_name}[$matches[1]]) && is_array(${$config_name}[$matches[1]]) && isset($matches[2]) ) { + if($debug) echo "Adding value <strong>$matches[2]</strong> to existing key <strong>$matches[1]</strong>\n"; + array_push(${$config_name}[$matches[1]], $matches[2]); + } + + elseif (isset(${$config_name}[$matches[1]]) && isset($matches[2]) ) { + if($debug) echo "Adding value <strong>$matches[2]</strong> to existing key $matches[1]</strong>\n"; + ${$config_name}[$matches[1]] = array(${$config_name}[$matches[1]],$matches[2]); + } + + elseif ($matches[2] !== "") { + if($debug) echo "Adding value <strong>$matches[2]</strong> for key <strong>$matches[1]</strong>\n"; + ${$config_name}[$matches[1]] = $matches[2]; + } + + // if there is something there and it's not a comment + elseif ($value{0} !== "#" AND strlen(trim($value)) > 0 AND !$test AND strlen($matches[2]) > 0) { + echo "Error Invalid Config Entry --> Line:$count"; return false; + } // elseif it's not a comment and there is something there + + else { + if($debug) echo "Key <strong>$matches[1]</strong> defined, but no value set\n"; + } + } // end if it's not a comment + + } // elseif no config_name + + elseif (preg_match("/^#?([\w\d]+)\s+=\s+[\"]{1}(.*?)[\"]{1}$/",$value,$matches) + || preg_match("/^#?([\w\d]+)\s+=\s+[\']{1}(.*?)[\']{1}$/", $value, $matches) + || preg_match("/^#?([\w\d]+)\s+=\s+[\'\"]{0}(.*)[\'\"]{0}$/",$value,$matches)) { + + + if (is_array($results[$matches[1]]) && isset($matches[2]) ) { + if($debug) echo "Adding value <strong>$matches[2]</strong> to existing key <strong>$matches[1]</strong>\n"; + array_push($results[$matches[1]], $matches[2]); + } + + elseif (isset($results[$matches[1]]) && isset($matches[2]) ) { + if($debug) echo "Adding value <strong>$matches[2]</strong> to existing key $matches[1]</strong>\n"; + $results[$matches[1]] = array($results[$matches[1]],$matches[2]); + } + + elseif ($matches[2] !== "") { + if($debug) echo "Adding value <strong>$matches[2]</strong> for key <strong>$matches[1]</strong>\n"; + $results[$matches[1]] = $matches[2]; + } + + // if there is something there and it's not a comment + elseif ($value{0} !== "#" AND strlen(trim($value)) > 0 AND !$test AND strlen($matches[2]) > 0) { + echo "Error Invalid Config Entry --> Line:$count"; return false; + } // elseif it's not a comment and there is something there + + else { + if($debug) echo "Key <strong>$matches[1]</strong> defined, but no value set\n"; + } + + } // end else + + } // foreach + + if (isset($config_name) && isset(${$config_name}) && count(${$config_name})) { + $results[$config_name] = ${$config_name}; + } + + if($debug) echo "</pre>"; + + return $results; + +} // debug_read_config + +/*! + @function debug_compare_configs + @discussion this takes two config files, and then compares + the results and returns an array of the values + that are missing from the first one passed +*/ +function debug_compare_configs($config,$dist_config) { + + + + /* Get the results from the two difference configs including #'d values */ + $results = debug_read_config($config,0); + $dist_results = debug_read_config($dist_config,0); + + $missing = array(); + if (!count($dist_results['conf'])) { $dist_results['conf'] = array(); } + if (!count($dist_results['libglue'])) { $dist_results['libglue'] = array(); } + + foreach ($dist_results['conf'] as $key=>$value) { + + if (!isset($results['conf'][$key])) { + $missing['conf'][$key] = $value; + } + + } // end foreach conf + + foreach ($dist_results['libglue'] as $key=>$value) { + + if (!isset($results['libglue'][$key])) { + $missing['libglue'][$key] = $value; + } + + } // end foreach libglue + + return $missing; + +} // debug_compare_configs + + +?> diff --git a/lib/duplicates.php b/lib/duplicates.php new file mode 100644 index 00000000..94f3deda --- /dev/null +++ b/lib/duplicates.php @@ -0,0 +1,117 @@ +<?php +/*! + @header Contains the functions for handling duplicate songs +*/ + + +/*! + @function get_duplicate_songs + @discussion +*/ +function get_duplicate_songs($search_type) { + $sql = "SELECT song.id as song,artist.name,album.name,title,count(title) as ctitle". + " FROM song,artist,album ". + " WHERE song.artist=artist.id AND song.album=album.id AND song.title<>'' ". + " GROUP BY title"; + if ($search_type=="artist_title"||$search_type=="artist_album_title") + $sql = $sql.",artist"; + if ($search_type=="artist_album_title") + $sql = $sql.",album"; + $sql = $sql." HAVING count(title) > 1"; + $sql = $sql." ORDER BY ctitle"; + + //echo $sql."<BR>"; + + $result = mysql_query($sql, dbh()); + + $arr = array(); + + while ($flag = mysql_fetch_array($result)) { + $arr[] = $flag; + } // end while + return $arr; +} // get_duplicate_songs + +/*! + @function get_duplicate_info + @discussion +*/ +function get_duplicate_info($song,$search_type) { + $artist = get_artist_name($song->artist); + $sql = "SELECT song.id as songid,song.title as song,file,bitrate,size,time,album.name AS album,album.id as albumid, artist.name AS artist,artist.id as artistid". + " FROM song,artist,album ". + " WHERE song.artist=artist.id AND song.album=album.id ". + " AND song.title= '".str_replace("'","''",$song->title)."'"; + + if ($search_type=="artist_title"||$search_type=="artist_album_title") + $sql = $sql." AND artist.id = '".$song->artist."'"; + if ($search_type=="artist_album_title") + $sql = $sql." AND album.id = '".$song->album."'"; + + $result = mysql_query($sql, dbh()); + + $arr = array(); + + while ($flag = mysql_fetch_array($result)) { + $arr[] = $flag; + } // end while + return $arr; + +} // get_duplicate_info + +/*! + @function show_duplicate_songs + @discussion +*/ +function show_duplicate_songs($flags,$search_type) { + require_once(conf('prefix').'/templates/list_duplicates.inc'); +} // show_duplicate_songs + +/*! + @function show_duplicate_searchbox + @discussion +*/ +function show_duplicate_searchbox($search_type) { +?> +<br /> +<form name="songs" action="<?php echo conf('web_path'); ?>/admin/duplicates.php" method="post" enctype="multipart/form-data" > +<table class="border" cellspacing="0" cellpadding="3" border="0" width="450px"> + <tr class="table-header"> + <td colspan="2"><b><?php echo _("Find Duplicates"); ?></b></td> + </tr> + <tr class="even"> + <td><?php echo _("Search Type"); ?>:</td> + <td> + <? + + if ($search_type=="title") + $checked = "checked=\"checked\""; + else + $checked = ""; + echo "<input type=\"radio\" name=\"search_type\" value=\"title\" ".$checked." >" . _("Title") . "<br />"; + + if ($search_type=="artist_title") + $checked = "checked=\"checked\""; + else + $checked = ""; + echo "<input type=\"radio\" name=\"search_type\" value=\"artist_title\" ".$checked." >" . _("Artist and Title") . "<br />"; + if ($search_type=="artist_album_title"OR $search_type=="") + $checked = "checked=\"checked\""; + else + $checked = ""; + echo "<input type=\"radio\" name=\"search_type\" value=\"artist_album_title\"".$checked." >" . _("Artist, Album and Title") . "<br />"; + ?> + </td> + </tr> + <tr class="odd"> + <td></td> + <td> + <input type="hidden" name="action" value="search"> + <input type="submit" value="<?php echo _("Search"); ?>" /> + </td> + </tr> +</table> +<br> +<? +} // show_duplicate_searchbox +?> diff --git a/lib/flag.php b/lib/flag.php new file mode 100644 index 00000000..01b3ba9f --- /dev/null +++ b/lib/flag.php @@ -0,0 +1,348 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +// +// + +function add_to_edit_queue($flags=0) +{ + $oldflags = 0; + if(empty($flags)) $flags = 0; + if($_SESSION['edit_queue']) + { + $oldflags = $_SESSION['edit_queue']; + if(!is_array($oldflags)) $oldflags = array($oldflags); + } + + if(!is_array($flags)) + { + if($flags !== 0) $flags = array($flags); + } + + if(is_array($flags)) + { + if(is_array($oldflags)) $new_array = array_merge($flags, $oldflags); + else $new_array = $flags; + } + elseif (is_array($oldflags)) $new_array = $oldflags; + + if(count($new_array)) + { + $_SESSION['edit_queue'] = $new_array; + return count($new_array); + } + else + { + unset($_SESSION['edit_queue']); + return 0; + } +} + +function show_edit_flagged($flag=0) +{ + if(empty($flag)||$flag === 0) + { + $flag = array_pop($_SESSION['edit_queue']); + } + $flaginfo = get_flag($flag); + if($flaginfo['type'] === 'badid3') + { + show_edit_badid3($flaginfo['song'],$flag); + } + else + { + echo "I don't know how to edit already edited songs yet: $flag.<br />"; + } +} + +function show_edit_badid3($songid,$flagid) +{ + $song = get_song_info($songid); + require(conf('prefix')."/templates/song_edit.inc"); +} + +function get_flag($id) +{ + if(!is_array($id)) $id = array($id); + $results = array(); + $newid = array_pop($id); + $sql = "SELECT flagged.id,user.username,type,song,date,comment" . + " FROM flagged,user WHERE flagged.user = user.id AND (flagged.song = '$newid'"; + foreach($id as $num) + { + $sql .= " OR flagged.song = '$num'"; + } + $sql .= ")"; + $result = mysql_query($sql, dbh()); + while ($row = mysql_fetch_array($result)) + { + $results[] = $row; + } + if(count($results) == 1) return $results[0]; + else return $results; +} + + +function get_flagged_songs($user = 0) +{ + $sql = "SELECT flagged.id,user.username,type,song,date,comment" . + " FROM flagged,user WHERE flagged.user = user.id AND flagged.type <> 'notify' AND flagged.type <> 'done'"; + + // If the user is not an admin, they can only see songs they've flagged + if($user) + { + if($_SESSION['userdata']['access'] === 'admin') + { + $sql .= " AND user.id = '$user'"; + } + else + { + $sql .= " AND user.id = '".$_SESSION['userdata']['id']."'"; + } + } + + $sql .= " ORDER BY date"; + $result = mysql_query($sql, dbh()); + + $arr = array(); + + while ($flag = mysql_fetch_array($result)) + { + $arr[] = $flag; + } + return $arr; +} + +function show_flagged_songs($flags) +{ + require_once(conf('prefix').'/templates/list_flagged.inc'); +} + +function accept_new_tags($flags) +{ + if(!is_array($flags)) $flags = array($flags); + foreach($flags as $flag) + { + copy_updated_tag($flag); + } + set_flag_value($flags, 'setid3'); +} + + +function reject_new_tags($flags) +{ + if(!is_array($flags)) $flags = array($flags); + $oldflags = $flags; + $flag = array_pop($flags); + $sql = "DELETE FROM flagged_songs WHERE song = '$flag'"; + + foreach($flags as $flag) + { + $sql .= " OR song = '$flag'"; + } + $result = mysql_query($sql, dbh()); + $user = $_SESSION['userdata']['username']; + set_flag_value($oldflags, 'notify', "Tag changes rejected by $user"); +} + +function set_flag_value($flags, $val, $comment = '') +{ + if(!is_array($flags)) $flags = array($flags); + $user = $_SESSION['userdata']['id']; +/* $flagid = array_pop($flags);*/ + $dbh = dbh(); + foreach($flags as $flagid) + { + $sql = "REPLACE INTO flagged (type,song,comment,user,date)". + " VALUES ('$val','$flagid','$comment','$user','".time()."')"; + $result = mysql_query($sql, $dbh); + } + return $result; +} + +function copy_updated_tag($flag) +{ + $flagdata = get_flag($flag); + $sql = "SELECT * FROM flagged_song WHERE song = '".$flagdata['song']."'"; + $result = mysql_query($sql, dbh()); + $newtag = mysql_fetch_array($result); + + if($newtag['new_artist']) + { + $newtag['artist'] = insert_artist($newtag['new_artist']); + } + if($newtag['new_album']) + { + $newtag['album'] = insert_album($newtag['new_album']); + } + + $sql = "UPDATE song SET ". + "title = '".$newtag['title']."',". + "artist = '".$newtag['artist']."',". + "album = '".$newtag['album']."',". + "track = '".$newtag['track']."',". + "genre = '".$newtag['genre']."',". + "year = '".$newtag['year']."' ". + "WHERE song.id = '".$newtag['song']."'"; + $result = mysql_query($sql, dbh()); + if($result) + { + $sql2 = "DELETE FROM flagged_song WHERE song='".$flagdata['song']."'"; + $result2 = mysql_query($sql2, dbh()); + } + return ($result && $result2); + +} + +function update_flags($songs) +{ + $accepted = array(); + $rejected = array(); + $newflags = array(); + foreach($songs as $song) + { + $accept = scrub_in($_REQUEST[$song.'_accept']); + if($accept === 'accept') $accepted[] = $song; + elseif ($accept === 'reject') $rejected[] = $song; + else + { + $newflag = scrub_in($_REQUEST[$song.'_newflag']); + $newflags[$song] = $newflag; + } + } + + if(count($accepted)) + { + accept_new_tags($accepted); + } + if(count($rejected)) + { + reject_new_tags($rejected); + } + if(count($newflags)) + { + foreach($newflags as $flag=>$type) + { + set_flag_value($flag, $type); + } + } + +} + + +function update_song_info($song) +{ + $user = $_SESSION['userdata']; + + $title = scrub_in($_REQUEST['title']); + $track = scrub_in($_REQUEST['track']); + $genre = scrub_in($_REQUEST['genre']); + $year = scrub_in($_REQUEST['year']); + + if(isset($_REQUEST['update_id3'])) + $update_id3 = 1; + + if(isset($_REQUEST['new_artist']) && $_REQUEST['new_artist'] !== '') + { + $create_artist = 1; + $artist = scrub_in($_REQUEST['new_artist']); + } + else + $artist = scrub_in($_REQUEST['artist']); + + if(isset($_REQUEST['new_album']) && $_REQUEST['new_album'] !== '') + { + $create_album = 1; + $album = scrub_in($_REQUEST['new_album']); + } + else + $album = scrub_in($_REQUEST['album']); + + if(is_array($_REQUEST['genre'])) { + $genre = $genre[0]; + } + + if($user['access'] == 'admin') + // Update the file directly + { + if($create_artist) + { + $artist = insert_artist($artist); + } + if($create_album) + { + $album = insert_album($album); + } + // Escape data (prevent " or ' snafu's) + $title = sql_escape($title); + $artist = sql_escape($artist); + $album = sql_escape($album); + $genre = sql_escape($genre); + $year = sql_escape($year); + + $sql = "UPDATE song SET" . + " title = '$title'," . + " track = '$track'," . + " genre = '$genre'," . + " year = '$year'," . + " artist = '$artist',". + " album = '$album'," . + " update_time = '".time()."'" . + " WHERE id = '$song' LIMIT 1"; + $result = mysql_query($sql, dbh() ); + if($result && $update_id3 ) + { + //Add to flagged table so we can fix the id3 tags + $date = time(); + $sql = "REPLACE INTO flagged SET " . + " type = 'setid3', song = '$song', date = '$date', user = '".$user['id']."'"; + $result = mysql_query($sql, dbh()); + } + } + + else + // Stick in the flagged_songs table to be updated by an admin + { + if($create_artist) $artist_field = 'new_artist'; + else $artist_field = 'artist'; + + if($create_album) $album_field = 'new_album'; + else $album_field = 'album'; + + $sql = "INSERT INTO flagged_song(song,title,track,genre,year,$artist_field,$album_field,update_time) " . + "VALUES ('$song','$title','$track','$genre','$year','$artist','$album','".time()."')"; + $result = mysql_query($sql, dbh() ); + + if($result && $update_id3 ) + { + //Add to flagged table so we can fix the id3 tags + $date = time(); + $sql = "REPLACE INTO flagged SET " . + " type = 'newid3', song = '$song', date = '$date', user = '".$user['id']."'"; + $result = mysql_query($sql, dbh()); + } + echo "Thanks for helping to keep the catalog up to date. Someone will review your changes, and you will be notified on the main page when they're approved."; + + } +} + diff --git a/lib/general.php b/lib/general.php new file mode 100644 index 00000000..1184db45 --- /dev/null +++ b/lib/general.php @@ -0,0 +1,554 @@ +<?php +/* + @header General Library + This is the general library that contains misc functions + that doesn't have a home elsewhere +*/ + +/*! + @function sql_escape + @discussion this takes a sql statement + and properly escapes it before a query is run + against it. +*/ +function sql_escape($sql,$dbh=0) { + + if (!is_resource($dbh)) { + $dbh = dbh(); + } + + if (function_exists('mysql_real_escape_string')) { + $sql = mysql_real_escape_string($sql,$dbh); + } + else { + $sql = mysql_escape_string($sql); + } + + return $sql; + +} // sql_escape + +/*! + @function ip2int + @discussion turns a dotted quad ip into an + int +*/ +function ip2int($ip) { + + $a=explode(".",$ip); + return $a[0]*256*256*256+$a[1]*256*256+$a[2]*256+$a[3]; + +} // ip2int + +/*! + @function int2ip + @discussion turns a int into a dotted quad +*/ +function int2ip($i) { + $d[0]=(int)($i/256/256/256); + $d[1]=(int)(($i-$d[0]*256*256*256)/256/256); + $d[2]=(int)(($i-$d[0]*256*256*256-$d[1]*256*256)/256); + $d[3]=$i-$d[0]*256*256*256-$d[1]*256*256-$d[2]*256; + return "$d[0].$d[1].$d[2].$d[3]"; +} // int2ip + +/*! + @function show_template + @discussion show a template from the /templates directory, automaticly appends .inc + to the passed filename + @param $template Name of Template +*/ +function show_template($template) { + + /* Check for a 'Theme' template */ + if (is_readable(conf('prefix') . conf('theme_path') . "/templates/$template".".inc")) { + require (conf('prefix') . conf('theme_path') . "/templates/$template".".inc"); + } + else { + require (conf('prefix') . "/templates/$template".".inc"); + } + +} // show_template + + +/*! + @function read_config + @discussion reads the config file for ampache +*/ +function read_config($config_file, $debug=0, $test=0) { + + $fp = @fopen($config_file,'r'); + if(!is_resource($fp)) return false; + $file_data = fread($fp,filesize($config_file)); + fclose($fp); + + // explode the var by \n's + $data = explode("\n",$file_data); + if($debug) echo "<pre>"; + + $count = 0; + + foreach($data as $value) { + $count++; + + $value = trim($value); + + if (preg_match("/^\[([A-Za-z]+)\]$/",$value,$matches)) { + // If we have previous data put it into $results... + if (isset($config_name) && isset(${$config_name}) && count(${$config_name})) { + $results[$config_name] = ${$config_name}; + } + + $config_name = $matches[1]; + + } // if it is a [section] name + + + elseif (isset($config_name)) { + + // if it's not a comment + if (preg_match("/^([\w\d]+)\s+=\s+[\"]{1}(.*?)[\"]{1}$/",$value,$matches) + || preg_match("/^([\w\d]+)\s+=\s+[\']{1}(.*?)[\']{1}$/", $value, $matches) + || preg_match("/^([\w\d]+)\s+=\s+[\'\"]{0}(.*)[\'\"]{0}$/",$value,$matches)) { + + if (isset(${$config_name}[$matches[1]]) && is_array(${$config_name}[$matches[1]]) && isset($matches[2]) ) { + if($debug) echo "Adding value <strong>$matches[2]</strong> to existing key <strong>$matches[1]</strong>\n"; + array_push(${$config_name}[$matches[1]], $matches[2]); + } + + elseif (isset(${$config_name}[$matches[1]]) && isset($matches[2]) ) { + if($debug) echo "Adding value <strong>$matches[2]</strong> to existing key $matches[1]</strong>\n"; + ${$config_name}[$matches[1]] = array(${$config_name}[$matches[1]],$matches[2]); + } + + elseif ($matches[2] !== "") { + if($debug) echo "Adding value <strong>$matches[2]</strong> for key <strong>$matches[1]</strong>\n"; + ${$config_name}[$matches[1]] = $matches[2]; + } + + // if there is something there and it's not a comment + elseif ($value{0} !== "#" AND strlen(trim($value)) > 0 AND !$test AND strlen($matches[2]) > 0) { + echo "Error Invalid Config Entry --> Line:$count"; return false; + } // elseif it's not a comment and there is something there + + else { + if($debug) echo "Key <strong>$matches[1]</strong> defined, but no value set\n"; + } + } // end if it's not a comment + + } // elseif no config_name + + + elseif (preg_match("/^([\w\d]+)\s+=\s+[\"]{1}(.*?)[\"]{1}$/",$value,$matches) + || preg_match("/^([\w\d]+)\s+=\s+[\']{1}(.*?)[\']{1}$/", $value, $matches) + || preg_match("/^([\w\d]+)\s+=\s+[\'\"]{0}(.*)[\'\"]{0}$/",$value,$matches)) { + + + if (is_array($results[$matches[1]]) && isset($matches[2]) ) { + if($debug) echo "Adding value <strong>$matches[2]</strong> to existing key <strong>$matches[1]</strong>\n"; + array_push($results[$matches[1]], $matches[2]); + } + + elseif (isset($results[$matches[1]]) && isset($matches[2]) ) { + if($debug) echo "Adding value <strong>$matches[2]</strong> to existing key $matches[1]</strong>\n"; + $results[$matches[1]] = array($results[$matches[1]],$matches[2]); + } + + elseif ($matches[2] !== "") { + if($debug) echo "Adding value <strong>$matches[2]</strong> for key <strong>$matches[1]</strong>\n"; + $results[$matches[1]] = $matches[2]; + } + + // if there is something there and it's not a comment + elseif ($value{0} !== "#" AND strlen(trim($value)) > 0 AND !$test AND strlen($matches[2]) > 0) { + echo "Error Invalid Config Entry --> Line:$count"; return false; + } // elseif it's not a comment and there is something there + + else { + if($debug) echo "Key <strong>$matches[1]</strong> defined, but no value set\n"; + } + + } // end else + + } // foreach + + if (isset($config_name) && isset(${$config_name}) && count(${$config_name})) { + $results[$config_name] = ${$config_name}; + } + + if($debug) echo "</pre>"; + + return $results; + + +} // read_config + +/* + * Conf function by Robert Hopson + * call it with a $parm name to retrieve + * a var, pass it a array to set them + * to reset a var pass the array plus + * Clobber! replaces global $conf; +*/ +function conf($param,$clobber=0) +{ + static $params = array(); + + if(is_array($param)) + //meaning we are setting values + { + foreach ($param as $key=>$val) + { + if(!$clobber && isset($params[$key])) + { + echo "Error: attempting to clobber $key = $val\n"; + exit(); + } + $params[$key] = $val; + } + return true; + } + else + //meaning we are trying to retrieve a parameter + { + if($params[$param]) return $params[$param]; + else return; + } +} //conf + +function libglue_param($param,$clobber=0) +{ + static $params = array(); + if(is_array($param)) + //meaning we are setting values + { + foreach ($param as $key=>$val) + { + if(!$clobber && isset($params[$key])) + { + echo "Error: attempting to clobber $key = $val\n"; + exit(); + } + $params[$key] = $val; + } + return true; + } + else + //meaning we are trying to retrieve a parameter + { + if(isset($params[$param])) return $params[$param]; + else return false; + } +} + +function error_results($param,$clobber=0) +{ + static $params = array(); + + if(is_array($param)) + //meaning we are setting values + { + foreach ($param as $key=>$val) + { + if(!$clobber && isset($params[$key])) + { + echo "Error: attempting to clobber $key = $val\n"; + exit(); + } + $params[$key] = $val; + } + return true; + } + else + //meaning we are trying to retrieve a parameter + { + if($params[$param]) return $params[$param]; + else return; + } +} //error_results + + +/*! + @function dbh + @discussion retrieves the DBH +*/ +function dbh() { return check_sess_db('local'); } + +/*! + @function fix_preferences + @discussion cleans up the preferences +*/ +function fix_preferences($results) { + + foreach ($results as $key=>$data) { + if (strcasecmp($data, "yes") == "0") { $data = 1; } + if (strcasecmp($data,"true") == "0") { $data = 1; } + if (strcasecmp($data,"enabled") == "0") { $data = 1; } + if (strcasecmp($data,"disabled") == "0") { $data = 0; } + if (strcasecmp($data,"false") == "0") { $data = 0; } + if (strcasecmp($data,"no") == "0") { $data = 0; } + $results[$key] = $data; + } + + return $results; + +} // fix_preferences + +/*! + @function session_exists + @discussion checks to make sure they've specified a + valid session +*/ +function session_exists($sid) { + + $sql = "SELECT * FROM session WHERE id = '$sid'"; + $db_results = mysql_query($sql, dbh()); + + if (!mysql_num_rows($db_results)) { + return false; + } + + return true; + +} // session_exists + +/*! + @function extend_session + @discussion just update the expire time +*/ +function extend_session($sid) { + + $new_time = time() + conf('local_length'); + + if ($_COOKIE['amp_longsess'] == '1') { $new_time = time() + 86400*364; } + + $sql = "UPDATE session SET expire='$new_time' WHERE id='$sid'"; + $db_results = mysql_query($sql, dbh()); + +} // extend_session + +/*! + @function get_tag_type + @discussion finds out what tag the audioinfo + results returned +*/ +function get_tag_type($results) { + + // Check and see if we are dealing with an ogg + // If so order will be a little different + if ($results['ogg']) { + $order[0] = 'ogg'; + } // end if ogg + elseif ($results['rm']) { + $order[0] = 'rm'; + } + elseif ($results['flac']) { + $order[0] = 'flac'; + } + elseif ($results['asf']) { + $order[0] = 'asf'; + } + elseif ($results['m4a']) { + $order[0] = 'm4a'; + } + elseif ($results['mpc']) { + $order[0] = 'mpc'; + } + else { + $order = conf('id3tag_order'); + } // end else + + if (!is_array($order)) { + $order = array($order); + } + + // set the $key to the first found tag style (according to their prefs) + foreach($order as $key) { + if ($results[$key]) { + break; + } + } + + return $key; + +} // get_tag_type + + +/*! + @function clean_tag_info + @discussion cleans up the tag information +*/ +function clean_tag_info($results,$key,$filename) { + + $info = array(); + + $clean_array = array("\n","\t","\r","\0"); + $wipe_array = array("","","",""); + + $info['file'] = $filename; + $info['title'] = stripslashes(trim($results[$key]['title'])); + $info['year'] = intval($results[$key]['year']); + $info['comment'] = sql_escape(str_replace($clean_array,$wipe_array,$results[$key]['comment'])); + $info['bitrate'] = intval($results['avg_bit_rate']); + $info['rate'] = intval($results['sample_rate']); + $info['mode'] = $results['bitrate_mode']; + $info['size'] = filesize($filename); + $info['time'] = intval($results['playing_time']); + $info['track'] = intval($results[$key]['track']); + + /* These are used to generate the correct ID's later */ + $info['artist'] = trim($results[$key]['artist']); + $info['album'] = trim($results[$key]['album']); + $info['genre'] = trim($results[$key]['genre']); + + return $info; + +} // clean_tag_info + +/*! + @function scrub_in() + @discussion Run on inputs, stuff that might get stuck in our db +*/ +function scrub_in($str) { + + if (!is_array($str)) { + return stripslashes( htmlspecialchars( strip_tags($str) ) ); + } + else { + $ret = array(); + foreach($str as $string) $ret[] = scrub_in($string); + return $ret; + } +} // scrub_in + +/*! + @function batch_ok() + @discussion return boolean if user can batch download +*/ +function batch_ok( ) { + global $user; + // i check this before showing any link + // should make it easy to tie to a new pref if you choose to add it + if (conf('allow_zip_download')) { + return( $user->prefs['download'] ); + } // if allowed zip downloads + + return false; + +} // batch_ok + +/*! + @function set_memory_limit + @discussion this function attempts to change the + php memory limit using init_set but it will + never reduce it +*/ +function set_memory_limit($new_limit) { + + /* Check their PHP Vars to make sure we're cool here */ + // Up the memory + $current_memory = ini_get('memory_limit'); + $current_memory = substr($current_memory,0,strlen($current_memory)-1); + if ($current_memory < $new_limit) { + $php_memory = $new_limit . "M"; + ini_set (memory_limit, "$php_memory"); + unset($php_memory); + } + +} // set_memory_limit + +/*! + @function get_random_songs + @discussion Returns a random set of songs/albums or artists + matchlist is an array of the WHERE mojo and options + defines special unplayed,album,artist,limit info +*/ +function get_random_songs( $options, $matchlist) { + + $dbh = dbh(); + + /* Define the options */ + $limit = $options['limit']; + $unplayed = $options['unplayed']; + + /* If they've passed -1 as limit then don't get everything */ + if ($options['limit'] == "-1") { unset($options['limit']); } + else { $options['limit'] = "LIMIT " . $limit; } + + + $where = "1=1 "; + if(is_array($matchlist)) + foreach ($matchlist as $type => $value) { + if (is_array($value)) { + foreach ($value as $v) { + $v = sql_escape($v); + if ($v != $value[0]) { $where .= " OR $type='$v' "; } + else { $where .= " AND ( $type='$v'"; } + } + $where .= " ) "; + } + else { + $value = sql_escape($value); + $where .= " AND $type='$value' "; + } + } + + + + if ($options['full_album'] == 1) { + $query = "SELECT album.id FROM song,album WHERE song.album=album.id AND $where GROUP BY song.album ORDER BY RAND() " . $options['limit']; + $db_results = mysql_query($query, $dbh); + while ($data = mysql_fetch_row($db_results)) { + $albums_where .= " OR song.album=" . $data[0]; + } + $albums_where = ltrim($albums_where," OR"); + $query = "SELECT song.id FROM song WHERE $albums_where ORDER BY song.track ASC"; + } + elseif ($options['full_artist'] == 1) { + $query = "SELECT artist.id FROM song,artist WHERE song.artist=artist.id AND $where GROUP BY song.artist ORDER BY RAND() " . $options['limit']; + $db_results = mysql_query($query, $dbh); + while ($data = mysql_fetch_row($db_results)) { + $artists_where .= " OR song.artist=" . $data[0]; + } + $artists_where = ltrim($artists_where," OR"); + $query = "SELECT song.id FROM song WHERE $artists_where ORDER BY RAND()"; + } + elseif ($options['unplayed'] == 1) { + $uid = $_SESSION['userdata']['id']; + $query = "SELECT song.id FROM song LEFT JOIN object_count ON song.id = object_count.object_id " . + "WHERE ($where) AND ((object_count.object_type='song' AND userid = '$uid') OR object_count.count IS NULL ) " . + "ORDER BY CASE WHEN object_count.count IS NULL THEN RAND() WHEN object_count.count > 4 THEN RAND()*RAND()*object_count.count " . + "ELSE RAND()*object_count.count END " . $options['limit']; + } // If unplayed + else { + $query = "SELECT id FROM song WHERE $where ORDER BY RAND() " . $options['limit']; + } + $db_result = mysql_query($query, $dbh); + + $songs = array(); + + while ( $r = mysql_fetch_array($db_result) ) { + $songs[] = $r[0]; + } + + return ($songs); + +} // get_random_songs + +/*! + @function cleanup_and_exit + @discussion used specificly for the play/index.php file + this functions nukes now playing and then exits +*/ +function cleanup_and_exit($playing_id) { + + /* Clear now playing */ + // 900 = 15 min + $expire = time() - 900; + $sql = "DELETE FROM now_playing WHERE id='$lastid' OR start_time < $expire"; + $db_results = mysql_query($sql, dbh()); + exit(); + +} // cleanup_and_exit + +?> diff --git a/lib/gettext.php b/lib/gettext.php new file mode 100644 index 00000000..064173d6 --- /dev/null +++ b/lib/gettext.php @@ -0,0 +1,36 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @function load_gettext + @discussion sets the local +*/ +function load_gettext() { + /* If we have gettext */ + if (function_exists('bindtextdomain')) { + bindtextdomain('messages', conf('prefix') . "/locale/"); + textdomain('messages'); + putenv("LANG=" . conf('lang')); + setlocale(LC_ALL, conf('lang')); + } + +} // load_gettext + + +?> diff --git a/lib/install.php b/lib/install.php new file mode 100644 index 00000000..c337b1e8 --- /dev/null +++ b/lib/install.php @@ -0,0 +1,238 @@ +<? +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @header Install docuement + @discussion this document contains the functions needed to see if + ampache needs to be installed +*/ + +/*! + @function split_sql + @discussion splits up a standard SQL dump file into distinct + sql queryies +*/ +function split_sql($sql) { + $sql = trim($sql); + $sql = ereg_replace("\n#[^\n]*\n", "\n", $sql); + $buffer = array(); + $ret = array(); + $in_string = false; + for($i=0; $i<strlen($sql)-1; $i++) { + if($sql[$i] == ";" && !$in_string) { + $ret[] = substr($sql, 0, $i); + $sql = substr($sql, $i + 1); + $i = 0; + } + if($in_string && ($sql[$i] == $in_string) && $buffer[1] != "\\") { + $in_string = false; + } + elseif(!$in_string && ($sql[$i] == '"' || $sql[$i] == "'") && (!isset($buffer[0]) || $buffer[0] != "\\")) { + $in_string = $sql[$i]; + } + if(isset($buffer[1])) { + $buffer[0] = $buffer[1]; + } + $buffer[1] = $sql[$i]; + } + if(!empty($sql)) { + $ret[] = $sql; + } + return($ret); +} // split_sql + +/*! + @function install_check_status() + @discussion this function checks to see if we actually + still need to install ampache. This function is + very important, we don't want to reinstall over top + of an existing install +*/ +function install_check_status($configfile) { + + /* + Check and see if the config file exists + if it does they can't use the web interface + to install ampache. + */ + if (!file_exists($configfile)) { + return true; + } + + /* + Check and see if they've got _any_ account + if they don't then they're cool + */ + $results = read_config($GLOBALS['configfile'], 0, 0); + $dbh = check_database($results['libglue']['local_host'],$results['libglue']['local_username'],$results['libglue']['local_pass'],$results['libglue']['local_db']); + + if (is_resource($dbh)) { + mysql_select_db($results['libglue']['local_db'],$dbh); + $sql = "SELECT * FROM user"; + $db_results = @mysql_query($sql, $dbh); + if (!@mysql_num_rows($db_results)) { + return true; + } + } + + + /* Defaut to no */ + return false; + +} // install_check_status + +/*! + @function install_insert_db() + @discussion this function inserts the database + using the username/pass/host provided + and reading the .sql file +*/ +function install_insert_db($username,$password,$hostname,$database) { + + /* Attempt to make DB connection */ + $dbh = @mysql_pconnect($hostname,$username,$password); + + + /* Check/Create Database as needed */ + $db_selected = @mysql_select_db($database, $dbh); + if (!$db_selected) { + $sql = "CREATE DATABASE `" . $database . "`"; + if (!$db_results = @mysql_query($sql, $dbh)) { + return false; + } + @mysql_select_db($database, $dbh); + } // if db can't be selected + + + /* Attempt to insert database */ + $query = fread(fopen("sql/ampache.sql", "r"), filesize("sql/ampache.sql")); + $pieces = split_sql($query); + for ($i=0; $i<count($pieces); $i++) { + $pieces[$i] = trim($pieces[$i]); + if(!empty($pieces[$i]) && $pieces[$i] != "#") { + //FIXME: This is for a DB prefix when we get around to it +// $pieces[$i] = str_replace( "#__", $DBPrefix, $pieces[$i]); + if (!$result = mysql_query ($pieces[$i])) { + $errors[] = array ( mysql_error(), $pieces[$i] ); + } // end if + } // end if + } // end for + + return true; + +} // install_insert_db + +/*! + @function install_create_config() + @discussion attempts to write out the config file + if it can't write it out it will prompt the + user to download the config file. +*/ +function install_create_config($web_path,$username,$password,$hostname,$database) { + + /* + First Test The Variables they've given us to make + sure that they actually work! + */ + // Connect to the DB + if(!$dbh = @mysql_pconnect($hostname,$username,$password)) { + return false; + } + if (!$db_selected = @mysql_select_db($database, $dbh)) { + return false; + } + + + /* Read in the .dist file and spit out the .cfg */ + $dist_handle = @fopen("config/ampache.cfg.php.dist",'r'); + $dist_data = @fread($dist_handle,filesize("config/ampache.cfg.php.dist")); + fclose($dist_handle); + + $dist_array = explode("\n",$dist_data); + + // Rather then write it out right away, let's build the string + // incase we can't write to the FS and have to make it a download + + foreach ($dist_array as $row) { + + if (preg_match("/^#?web_path\s*=/",$row)) { + $row = "web_path = \"$web_path\""; + } + elseif (preg_match("/^#?local_db\s*=/",$row)) { + $row = "local_db = \"$database\""; + } + elseif (preg_match("/^#?local_host\s*=/",$row)) { + $row = "local_host = \"$hostname\""; + } + elseif (preg_match("/^#?local_username\s*=/",$row)) { + $row = "local_username = \"$username\""; + } + elseif (preg_match("/^#?local_pass\s*=/",$row)) { + $row = "local_pass = \"$password\""; + } + + $config_data .= $row . "\n"; + + } // foreach row in config file + + /* Attempt to Write out File */ + if (!$config_handle = @fopen("config/ampache.cfg.php",'w')) { + $browser = new Browser(); + $browser->downloadHeaders("ampache.cfg.php","text/plain",false,filesize("config/ampache.cfg.php.dist")); + + echo $config_data; + exit(); + + } + if (!@fwrite($config_handle,$config_data)) { + return false; + } + + return true; + +} // install_create_config + +/*! + @function install_create_account + @discussion this creates your initial account +*/ +function install_create_account($username,$password) { + + $results = read_config($GLOBALS['configfile'], 0, 0); + $dbh = check_database($results['libglue']['local_host'],$results['libglue']['local_username'],$results['libglue']['local_pass'],$results['libglue']['local_db']); + + @mysql_select_db($results['libglue']['local_db'],$dbh); + + $username = sql_escape($username,$dbh); + $password = sql_escape($password,$dbh); + + $sql = "INSERT INTO user (`username`,`password`,`offset_limit`,`access`) VALUES ('$username',PASSWORD('$password'),'50','admin')"; + $db_results = mysql_query($sql, $dbh); + + $insert_id = mysql_insert_id($dbh); + + if (!$insert_id) { return false; } + + return true; + +} // install_create_account + +?> diff --git a/lib/log.php b/lib/log.php new file mode 100644 index 00000000..7a3d8faf --- /dev/null +++ b/lib/log.php @@ -0,0 +1,77 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @function log_event + @discussion logs an event either to a database + or to a defined log file based on config options +*/ +function log_event($username='Unknown',$event_name,$event_description,$log_name='ampache') { + + /* Set it up here to make sure it's _always_ the same */ + $log_time = time(); + + set_time_limit(0); + + $log_filename = conf('log_path') . "/$log_name." . date("Ymd",$log_time) . ".log"; + $log_line = date("Y-m-d H:i:s",$log_time) . " { $username } ( $event_name ) - $event_description \n"; + + + error_log($log_line, 3, $log_filename) or die("Error: Unable to write to log ($log_filename)"); + +} // log_event + +/*! + @function ampache_error_handler + @discussion an error handler for ampache that traps + as many errors as it can and logs em +*/ +function ampache_error_handler($errno, $errstr, $errfile, $errline) { + + switch ($errno) { + case '2': + case '128': + case '8': + case '32': + return true; + break; + case '1': + $error_name = "Fatal run-time Error"; + break; + case '4': + $error_name = "Parse Error"; + break; + case '16': + $error_name = "Fatal Core Error"; + break; + case '64': + $error_name = "Zend run-time Error"; + break; + default: + $error_name = "Error"; + break; + } // end switch + + + $log_line = "[$errstr] $error_name on line $errline in $errfile"; + log_event($_SESSION['userdata']['username'],'error',$log_line,'ampache-error'); + +} // ampache_error_handler + +?> diff --git a/lib/mpd.php b/lib/mpd.php new file mode 100644 index 00000000..166bfe5b --- /dev/null +++ b/lib/mpd.php @@ -0,0 +1,75 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @function addToPlaylist() + @discussion adds a bunch of songs to the mpd playlist + this takes a mpd object, and an array of songs +*/ +function addToPlaylist( $myMpd, $song_ids=array()) { + + foreach( $song_ids as $song_id ) { + + /* There are two ways to do this, filename or URL */ + if (conf('mpd_method') == 'url') { + // We just need to generate a standard stream URL and pass that + $song = new Song($song_id); + $sess_id = session_id(); + if ($song->type == ".flac") { $song->type = ".ogg"; } + if ($GLOBALS['user']->prefs['play_type'] == 'downsample') { + $ds = $GLOBALS['user']->prefs['sample_rate']; + } + $song_url = conf('web_path') . "/play/index.php?song=$song_id&uid=" . $GLOBALS['user']->id . "&sid=$sess_id&ds=$ds&name=." . $song->type; + if (is_null( $myMpd->PlAdd($song_url) ) ) { + $log_line = _("Error") . ": " . _("Could not add") . ": " . $song_url . " : " . $myMpd->errStr; + echo "<font class=\"error\">$log_line</font><br />\n"; + log_event($GLOBALS['user']->username,'add',$log_line); + } // if it's null + } // if we want urls + else { + $song = new Song( $song_id ); + $song_filename = $song->get_rel_path(); + if( is_null( $myMpd->PLAdd( $song_filename ) ) ) { + $log_line = _("Error") . ": " . _("Could not add") . ": " . $song_filename . " : " . $myMpd->errStr; + echo "<font class=\"error\">$log_line</font><br />\n"; + log_event($_SESSION['userdata']['username'],'add',$log_line); + } // end if it's null + // We still need to count if they use the file method + else { + $GLOBALS['user']->update_stats( $song_id ); + } // end else + + } // end else not url method + } // end foreach + +} // addToPlaylist + +/*! + @function show_mpd_control + @discussion shows the mpd controls +*/ +function show_mpd_control() { + + $_REQUEST['action'] = 'show_control'; + require_once ('amp-mpd.php'); + + +} // show_mpd_control + +?> diff --git a/lib/perl/Local/Ampache/Ampache.pm b/lib/perl/Local/Ampache/Ampache.pm new file mode 100755 index 00000000..3bceb7e6 --- /dev/null +++ b/lib/perl/Local/Ampache/Ampache.pm @@ -0,0 +1,237 @@ +#!/usr/bin/perl -w + +# Find and file away MP3's. Run multiple times and will +# ignore addition of duplicates in db (based on MD5 hash +# of full file path. + +package Local::Ampache; +#use File::Find; +use DBI; +#use strict; +use Data::Dumper; +use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %ampache); +require Exporter; + +@ISA = qw(Exporter AutoLoader); +@EXPORT = qw( + +); + +my $TRUE = 1; +my $FALSE = 0; +$VERSION = ''; + + +my %ampache = (); + + +sub new { + my ($class, $path) = @_; + + + open(CONFIG, "< $path/config/ampache.cfg") + or die "Could not find $path/config/ampache.cfg. Is it readable by me?\n"; + + my %config = (); + + while (<CONFIG>) { + next if ($_ =~ /^#.*/); + + if ( $_ =~ /(.*?)\s+=\s+(.*)/ ) { + $config{$1} = $2; + } + } + + my $name = $config{'local_db'}; + + my $self = + { + _name => $config{'local_db'}, + _database => $config{'local_db'}, + _sth_cache => {}, + _connect => { + dbd => 'mysql', + host => $config{'local_host'}, + port => '3306', + username => $config{'local_username'}, + password => $config{'local_pass'} + }, + _dbh => '', + _path => $path, + _config => \%config, + _debug => $FALSE + }; + + $VERSION = $config{'VERSION'}; + + $Local::Ampache::ampache{$name} = bless ($self, $class); + + $self->{_dbh} = $self->dbh( $name ); + + return $self; + +} # End New Ampache Module + +sub DESTROY { + my ($self) = @_; + + foreach my $sth (values %{$self->{_sth_cache}}) { + if (defined($sth)) { $sth->finish(); } + } + + if (defined($self->{_dbh}) and $self->{_dbh} ne "") { + $self->{_dbh}->disconnect(); + } +} + +sub get +{ + my ($class, $name) = @_; + + if (not $Local::Ampache::ampache{$name}) { + $Local::Ampache::ampache{$name} = Local::Ampache->new($name); + } + return bless $Local::Ampache::ampache{$name}, $class; +} + +sub dbh +{ + my ($self, $database) = @_; + my $dbh = ''; + + if($self->{_dbh} ) + { + return $self->{_dbh}; + } + else + { + my $connect_string = [ sprintf("dbi:%s:database=%s;host=%s;port=%s", + $self->{_connect}{dbd}, + $self->{_database}, + $self->{_connect}{host}, + $self->{_connect}{port}), + $self->{_connect}{username}, + $self->{_connect}{password} ]; + $dbh = DBI->connect( @{$connect_string}, + {PrintError => 0, + RaiseError => 0, + AutoCommit => 1}); + + if ( !$dbh ) + { + die "Failed to connect to database. Exiting."; + } + } + + return $dbh; +} + +sub prepare_sth_cache { + my ($self, $sql) = @_; + + # the call to dbh() forces a connection if one has dropped + my $dbh = $self->dbh(); + return $dbh->prepare($sql); +} + +sub get_table_where +{ + my ($self, $name, $where,$select) = @_; + if (!$select) { $select = "*"; } + my ($sql, $sth); + my $dbh = $self->dbh(); + $sql = qq{SELECT $select FROM $name $where}; + $sth = $dbh->prepare($sql); + $sth->execute(); + + my @table = (); + while ( my $ary = $sth->fetchrow_hashref() ) + { + push(@table, $ary); + } + return (@table); +} + +sub get_catalog_option +{ + my ($self, $catalog, $field) = @_; + if(!$self->{_catalog}{$catalog}) { + print "Loading catalog settings\n"; + my ($sql, $sth); + $sql = qq{SELECT * FROM catalog WHERE path = '$catalog'}; + my $dbh = $self->dbh(); + $sth = $dbh->prepare($sql); + $sth->execute(); + $self->{_catalog}{$catalog} = $sth->fetchrow_hashref(); + } + return $self->{_catalog}->{$catalog}->{$field}; +} + +sub change_flags +{ + my ($self, $song, $oldflag, $newflag) = @_; + my ($sql, $sth); + my $dbh = $self->dbh(); + $sql = "UPDATE flagged SET type = '$newflag' WHERE song = '".$song->{'id'}."' AND type = '$oldflag'"; + $sth = $dbh->prepare($sql); + $sth->execute(); +} + + sub update_song +{ + my ($self, $filename, $song) = @_; + my ($sql, $sth); + my $dbh = $self->dbh(); + $filename =~ s/'/\\'/g; + $filename =~ s/"/\\"/g; + $filename =~ s/\Q%\E//g; + $sql = "UPDATE song SET file = '$filename' WHERE id = '".$song->{'id'}."'"; + $sth = $dbh->prepare($sql); + $sth->execute(); +} + +sub get_song_info +{ + my ($self, $song) = @_; + my ($sql, $sth); + my $dbh = $self->dbh(); + if ( not $self->{_sth_cache}{get_song_info}) + { + $self->{_sth_cache}{get_song_info} = $self->prepare_sth_cache( + qq{SELECT catalog.path AS catalog,song.file,song.id,song.title,song.track,song.year,song.comment,album.name AS album, artist.name AS artist,genre FROM song,album,artist,catalog WHERE song.id = ? AND album.id = song.album AND artist.id = song.artist AND song.catalog = catalog.id}); + + } + $sth = $self->{_sth_cache}{get_song_info}; + $sth->execute($song); + + my @table = (); + while ( my $ary = $sth->fetchrow_hashref() ) + { + push(@table, $ary); + } + return (@table); +} + +#sub get_song_info +#{ +# my ($self, $song) = @_; +# +# my ($sql, $sth); +# my $dbh = $self->dbh(); +# if ( not $self->{_sth_cache}{song_info}{$song} ) +# { +# $sql = qq{SELECT * FROM song WHERE id = $song}; +# $sth = $dbh->prepare($sql); +# $self->{_sth_cache}{song_info}{$song} = $sth; +# } +# +# $sth = $self->{_sth_cache}{song_info}{$song}; +# $sth->execute(); +# +# my @song_info = $sth->fetchrow_hashref(); +# return (@song_info); +#} + + +1; +__END__ diff --git a/lib/perl/Local/Ampache/Makefile.PL b/lib/perl/Local/Ampache/Makefile.PL new file mode 100755 index 00000000..a51e5d54 --- /dev/null +++ b/lib/perl/Local/Ampache/Makefile.PL @@ -0,0 +1,7 @@ +use ExtUtils::MakeMaker; +# See lib/ExtUtils/MakeMaker.pm for details of how to influence +# the contents of the Makefile that is written. +WriteMakefile( + 'NAME' => 'Local::Ampache', + 'VERSION_FROM' => 'Ampache.pm', # finds $VERSION +); diff --git a/lib/preferences.php b/lib/preferences.php new file mode 100644 index 00000000..b6c6e0c9 --- /dev/null +++ b/lib/preferences.php @@ -0,0 +1,281 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @header Preferences Library + @discussion This contains all of the functions needed for the preferences +*/ + +/*! + @function get_site_preferences + @discussion gets all of the preferences for this Ampache site +*/ +function get_site_preferences() { + + $results = array(); + + $sql = "SELECT preferences.name, preferences.type, user_preference.value, preferences.description FROM preferences,user_preference " . + " WHERE preferences.id=user_preference.preference AND user_preference.user = '0' ORDER BY `type`,`name`"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $results[] = $r; + } + + return $results; + +} // get_site_preferences + +/*! + @function set_site_preferences + @discussion sets the conf() function with the current site preferences from the db +*/ +function set_site_preferences() { + + $results = array(); + + $sql = "SELECT preferences.name,user_preference.value FROM preferences,user_preference WHERE user='0' AND user_preference.preference=preferences.id"; + $db_results = @mysql_query($sql, dbh()); + + while ($r = @mysql_fetch_object($db_results)) { + $results[$r->name] = $r->value; + } // db results + + if (strlen($results['theme_name']) > 0) { + $results['theme_path'] = "/themes/" . $results['theme_name']; + } + + conf($results,1); + +} // set_site_preferences + +/*! + @function clean_preference_name + @discussion s/_/ /g & upper case first +*/ +function clean_preference_name($name) { + + $name = str_replace("_"," ",$name); + $name = ucwords($name); + + return $name; + +} // clean_preference_name + +/*! + @function update_preferences + @discussion grabs the current keys that should be added + and then runs throught $_REQUEST looking for those + values and updates them for this user +*/ +function update_preferences($pref_id=0) { + + $pref_user = new User(0,$pref_id); + + /* Get current keys */ + $sql = "SELECT id,name,type FROM preferences"; + if ($pref_id != '0') { $sql .= " WHERE type='user'"; } + $db_results = mysql_query($sql, dbh()); + + // Collect the current possible keys + while ($r = mysql_fetch_object($db_results)) { + $results[] = array('id' => $r->id, 'name' => $r->name,'type' => $r->type); + } + + foreach ($results as $data) { + /* Get the Value from POST/GET var called $data */ + //FIXME: Do this right.... + $type = $data['type']; + $name = $data['name']; + $apply_to_all = "check_" . $data['name']; + $id = $data['id']; + $value = sql_escape(scrub_in($_REQUEST[$name])); + + if (has_preference_access($name) AND isset($_REQUEST[$name])) { + $sql = "UPDATE user_preference SET `value`='$value' WHERE preference='$id' AND user='$pref_id'"; + $db_results = mysql_query($sql, dbh()); + + /* Check to see if this is a theme, and if so run the theme updater */ + if ($name == "theme_name" AND $pref_user->prefs['theme_name'] != $_REQUEST[$name]) { + set_theme_colors($value,$pref_id); + } // run theme updater + + } // if access + + if ($GLOBALS['user']->has_access(100) AND $_REQUEST[$apply_to_all] =='1') { + $sql = "UPDATE user_preference SET `value`='$value' WHERE preference='$id'"; + $db_results = mysql_query($sql, dbh()); + } + } // end foreach preferences + + +} // update_preferences + +/*! + @function has_preference_access + @discussion makes sure that the user has sufficient + rights to actually set this preference, handle + as allow all, deny X + //FIXME: + // This is no longer needed, we just need to check against preferences.level +*/ +function has_preference_access($name) { + global $user; + + if (conf('demo_mode')) { + return false; + } + + switch($name) { + + case 'download': + case 'upload': + case 'quarantine': + case 'upload_dir': + case 'sample_rate': + case 'direct_link': + $level = 100; + break; + default: + $level = 1; + break; + } // end switch key + if ($user->has_access($level)) { + return true; + } + + return false; + +} // has_preference_access + + +/*! + @function create_preference_input + @discussion takes the key and then creates + the correct type of input for updating it +*/ +function create_preference_input($name,$value) { + + $len = strlen($value); + if ($len <= 1) { $len = 8; } + + if (!has_preference_access($name)) { + if ($value == '1') { + echo "Enabled"; + } + elseif ($value == '0') { + echo "Disabled"; + } + else { + echo $value; + } + return; + } // if we don't have access to it + + switch($name) { + + case 'download': + case 'quarantine': + case 'upload': + case 'access_list': + case 'lock_songs': + case 'xml_rpc': + case 'force_http_play': + case 'no_symlinks': + case 'use_auth': + case 'access_control': + case 'demo_mode': + case 'direct_link': + if ($value == '1') { $is_true = "selected=\"selected\""; } + else { $is_false = "selected=\"selected\""; } + echo "<select name=\"$name\">\n"; + echo "\t<option value=\"1\" $is_true>" . _("Enable") . "</option>\n"; + echo "\t<option value=\"0\" $is_false>" . _("Disable") . "</option>\n"; + echo "</select>\n"; + break; + case 'play_type': + if ($value == 'local_play') { $is_local = "selected=\"selected\""; } + elseif ($value == 'icecast2') { $is_ice = "selected=\"selected\""; } + elseif ($value == 'downsample') { $is_down = "selected=\"selected\""; } + elseif ($value == 'mpd') { $is_mpd = "selected=\"selected\""; } + elseif ($value == 'slim') { $is_slim = "selected=\"selected\""; } + else { $is_stream = "selected=\"selected\""; } + echo "<select name=\"$name\">\n"; + if (conf('allow_local_playback')) { + echo "\t<option value=\"local_play\" $is_local>" . _("Local") . "</option>\n"; + } + if (conf('allow_stream_playback')) { + echo "\t<option value=\"stream\" $is_stream>" . _("Stream") . "</option>\n"; + } + if (conf('allow_icecast_playback')) { + echo "\t<option value=\"icecast2\" $is_ice>" . _("IceCast") . "</option>\n"; + } + if (conf('allow_downsample_playback')) { + echo "\t<option value=\"downsample\" $is_down>" . _("Downsample") . "</option>\n"; + } + if (conf('allow_mpd_playback')) { + echo "\t<option value=\"mpd\" $is_mpd>" . _("Music Player Daemon") . "</option>\n"; + } + if (conf('allow_slim_playback')) { + echo "\t<option value=\"slim\" $is_slim>" . _("SlimServer") . "</option>\n"; + } + + echo "</select>\n"; + break; + case 'playlist_type': + $var_name = $value . "_type"; + ${$var_name} = "selected=\"selected\""; + echo "<select name=\"$name\">\n"; + echo "\t<option value=\"m3u\" $m3u_type>" . _("M3U") . "</option>\n"; + echo "\t<option value=\"simple_m3u\" $simple_m3u_type>" . _("Simple M3U") . "</option>\n"; + echo "\t<option value=\"pls\" $pls_type>" . _("PLS") . "</option>\n"; + echo "\t<option value=\"asx\" $asx_type>" . _("Asx") . "</option>\n"; + echo "</select>\n"; + break; + case 'lang': + $var_name = $value . "_lang"; + ${$var_name} = "selected=\"selected\""; + echo "<select name=\"$name\">\n"; + echo "\t<option value=\"en_US\" $en_US_lang>" . _("English") . "</option>\n"; + echo "\t<option value=\"de_DE\" $de_DE_lang>" . _("German") . "</option>\n"; + echo "\t<option value=\"fr_FR\" $fr_FR_lang>" . _("French") . "</option>\n"; + echo "\t<option value=\"tr_TR\" $tr_TR_lang>" . _("Turkish") . "</option>\n"; + echo "</select>\n"; + break; + case 'theme_name': + $themes = get_themes(); + echo "<select name=\"$name\">\n"; + foreach ($themes as $theme) { + $is_selected = ""; + if ($value == $theme['path']) { $is_selected = "selected=\"selected\""; } + echo "\t<option value=\"" . $theme['path'] . "\" $is_selected>" . $theme['name'] . "</option>\n"; + } // foreach themes + echo "</select>\n"; + break; + default: + echo "<input type=\"text\" size=\"$len\" name=\"$name\" value=\"$value\" />"; + break; + + } + +} // create_preference_input + +?> diff --git a/lib/rss.php b/lib/rss.php new file mode 100644 index 00000000..efb6cba9 --- /dev/null +++ b/lib/rss.php @@ -0,0 +1,64 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @function show_now_playingRSS + @discussion creates a RSS fead for the now + playing information +*/ +function show_now_playingRSS () { + + $dbh = dbh(); + $web_path = conf('web_path'); + $rss_main_title = conf('rss_main_title'); + $rss_main_description = conf('rss_main_description'); + $rss_main_copyright = conf('rss_main_copyright'); + $rss_main_language = conf('rss_main_language'); + $rss_song_description = conf('rss_song_description'); + + $sql = "SELECT * FROM now_playing ORDER BY start_time DESC"; + + $db_result = mysql_query($sql, $dbh); + $today = date("d-m-Y"); + + echo "<rss version=\"0.91\">"; + echo "<channel>\n<title>$rss_main_title</title>\n"; + echo "<link>$web_path</link>\n<description>$rss_main_description</description>\n"; + echo "<copyright>$rss_main_copyright</copyright>"; + echo "<pubDate>$today</pubDate>\n<language>$rss_main_language</language>\n"; + + while ($r = mysql_fetch_object($db_result)) { + $song = new Song($r->song_id); + $song->format_song(); + $user = get_user_byid($r->user_id); + if (is_object($song)) { + $artist = $song->f_artist; + $album = $song->get_album_name(); + $text = "$artist - $song->f_title"; + echo "<item> "; + echo " <title><![CDATA[$text]]></title> "; + echo " <link>$web_path/albums.php?action=show&album=$song->album</link>"; + echo " <description>$rss_song_description</description>"; + echo " <pubDate>$today</pubDate>"; + echo "</item>"; + } + } + + echo "</channel>\n</rss>"; +} // show_now_playingRSS +?> diff --git a/lib/search.php b/lib/search.php new file mode 100644 index 00000000..54192750 --- /dev/null +++ b/lib/search.php @@ -0,0 +1,185 @@ +<?php +/* + + Copyright (c) 2004 ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + This library handles all the searching! + +*/ + +/*! + @function run_search + @discussion run a search, takes string,field,type and returns an array + of results of the correct type (song, album, artist) +*/ +function run_search($string,$field,$type) { + + // Clear this so it doesn't try any fanzy view mojo on us + unset($_SESSION['view_script']); + + // Escape input string + $string = sql_escape($string); + + // Switch on the field --> type and setup sql statement + switch ($field === 0 ? '': $field) { + case 'artist': + if ($type === 'fuzzy') { + $sql = "SELECT id FROM artist WHERE name LIKE '%$string%'"; + } + else { + $sql = "SELECT id FROM artist WHERE name ='$string'"; + } + $artists = get_artists($sql, 'format'); + if ($artists) { + show_artists($artists); + } + else { + echo "<div class=\"error\" align=\"center\">" . _("No Results Found") . "</div>"; + } + break; + + case 'album': + if ($type === 'fuzzy') { + $sql = "SELECT id FROM album WHERE name LIKE '%$string%'"; + } + else { + $sql = "SELECT id FROM album WHERE name='$string'"; + } + $albums = get_albums($sql); + if (count($albums)) { + show_albums($albums); + } + else { + echo "<div class=\"error\" align=\"center\">" . _("No Results Found") . "</div>"; + } + break; + + case 'song_title': + if ($type === 'fuzzy') { + $sql = "SELECT id FROM song WHERE title LIKE '%$string%'"; + } + else { + $sql = "SELECT id FROM song WHERE title = '$string'"; + } + $song_ids = get_songs($sql, 'format'); + if ($song_ids) { + show_songs($song_ids); + } + else { + echo "<div class=\"error\" align=\"center\">" . _("No Results Found") . "</div>"; + } + break; + + case 'song_genre': + if ($type === 'fuzzy') { + $sql = "SELECT song.id FROM song,genre WHERE song.genre=genre.id AND genre.name LIKE '%$string%'"; + } + else { + $sql = "SELECT song.id FROM song,genre WHERE song.genre=genre.id AND genre.name='$string'"; + } + $song_ids = get_songs($sql, 'format'); + if ($song_ids) { + show_songs($song_ids); + } + else { + echo "<div class=\"error\" align=\"center\">" . _("No Results Found") . "</div>"; + } + break; + + case 'song_year': + if ($type === 'fuzzy') { + $sql = "SELECT song.id FROM song WHERE song.year LIKE '%$string%'"; + } + else { + $sql = "SELECT song.id FROM song WHERE song.year='$string'"; + } + $song_ids = get_songs($sql, 'format'); + if ($song_ids) { + show_songs($song_ids); + } + else { + echo "<div class=\"error\" align=\"center\">" . _("No Results Found") . "</div>"; + } + break; + + case 'song_length': + case 'song_bitrate': + if ($type === 'fuzzy') { + $sql = "SELECT song.id FROM song WHERE song.bitrate LIKE '%$string%'"; + } + else { + $sql = "SELECT song.id FROM song WHERE song.bitrate='$string'"; + } + $song_ids = get_songs($sql, 'format'); + if ($song_ids) { + show_songs($song_ids); + } + else { + echo "<div class=\"error\" align=\"center\">" . _("No Results Found") . "</div>"; + } + break; + + case 'song_min_bitrate': + $string = $string * 1000; + $sql = "SELECT song.id FROM song WHERE song.bitrate >= '$string'"; + $song_ids = get_songs($sql, 'format'); + if ($song_ids) { + show_songs($song_ids); + } + else { + echo "<div class=\"error\" align=\"center\">" . _("No Results Found") . "</div>"; + } + break; + + case 'song_comment': + if ($type === 'fuzzy') { + $sql = "SELECT song.id FROM song WHERE song.comment LIKE '%$string%'"; + } + else { + $sql = "SELECT song.id FROM song WHERE song.comment='$string'"; + } + $song_ids = get_songs($sql, 'format'); + if ($song_ids) { + show_songs($song_ids); + } + else { + echo "<div class=\"error\" align=\"center\">" . _("No Results Found") . "</div>"; + } + break; + + case 'song_filename': + if ($type === 'fuzzy') { + $sql = "SELECT song.id FROM song WHERE song.file LIKE '%$string%'"; + } + else { + $sql = "SELECT song.id FROM song WHERE song.file='$string'"; + } + $song_ids = get_songs($sql, 'format'); + if ($song_ids) { + show_songs($song_ids); + } + else { + echo "<div class=\"error\" align=\"center\">" . _("No Results Found") . "</div>"; + } + break; + + } // end switch + +} // run_search + +?> diff --git a/lib/song.php b/lib/song.php new file mode 100644 index 00000000..0e847ad9 --- /dev/null +++ b/lib/song.php @@ -0,0 +1,57 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/* + @header Song Library + @discussion This library handles song related functions.... woohoo! + This library is defunt, please try use the song class if possible + +*/ + +/*! + @function get_songs + @discussion pass a sql statement, and it gets full song info and returns + an array of the goods.. can be set to format them as well +*/ +function get_songs($sql, $action=0) { + + $db_results = mysql_query($sql, dbh()); + while ($r = mysql_fetch_array($db_results)) { +// $song_info = get_songinfo($r['id']); +// if ($action === 'format') { $song = format_song($song_info); } +// else { $song = $song_info; } + $results[] = $r['id']; + } + + return $results; + + +} // get_albums + +/*! + @function format_song + @discussion takes a song array and makes it html friendly +*/ +function format_song($song) { + + return $song; + +} // format_song + + +?> diff --git a/lib/themes.php b/lib/themes.php new file mode 100644 index 00000000..ca4a92c7 --- /dev/null +++ b/lib/themes.php @@ -0,0 +1,124 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All Rights Reserved + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @function get_themes() + @discussion this looks in /themes and pulls all of the + theme.cfg.php files it can find and returns an + array of the results +*/ +function get_themes() { + + /* Open the themes dir and start reading it */ + $handle = @opendir(conf('prefix') . "/themes"); + + if (!is_resource($handle)) { + if (conf('debug')) { log_event($_SESSION['userdata']['username'],'theme',"Error unable to open Themes Directory"); } + } + + $results = array(); + + while ($file = readdir($handle)) { + + $full_file = conf('prefix') . "/themes/" . $file; + /* See if it's a directory */ + if (is_dir($full_file) AND substr($file,0,1) != ".") { + $config_file = $full_file . "/theme.cfg.php"; + /* Open the theme.cfg.php file */ + $r = read_config($config_file); + $r['path'] = $file; + $results[] = $r; + } + + } // end while directory + + return $results; + +} // get_themes + +/*! + @function get_theme + @discussion get a single theme and read the config file + then return the results +*/ +function get_theme($name) { + + $config_file = conf('prefix') . "/themes/" . $name . "/theme.cfg.php"; + $results = read_config($config_file); + $results['path'] = $name; + return $results; + +} // get_theme + +/*! + @function set_theme + @discussion Resets all of the colors for this theme +*/ +function set_theme_colors($theme_name,$user_id) { + + /* We assume if we've made it this far we've got the right to do it + This could be dangerous but eah! + */ + $theme = get_theme($theme_name); + if (!count($theme['color'])) { return false; } + + foreach ($theme['color'] as $key=>$color) { + + $sql = "SELECT id FROM preferences WHERE name='" . sql_escape($key) . "'"; + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_array($db_results); + + $sql = "UPDATE user_preference SET `value`='" . sql_escape($color) . "' WHERE `user`='" . $user_id . "' AND " . + " preference='" . $results[0] . "'"; + $db_results = mysql_query($sql, dbh()); + + } // theme colors + +} // set_theme_colors + +/*! + @function set_theme + @discussion this sets the needed vars for the theme +*/ +function set_theme() { + + if (strlen(conf('theme_name')) > 0) { + $theme_path = "/themes/" . conf('theme_name'); + conf(array('theme_path'=>$theme_path),1); + } + +} // set_theme + +/*! + @function get_theme_author + @discussion returns the author of this theme +*/ +function get_theme_author($theme_name) { + + $theme_path = conf('prefix') . "/themes/" . conf('theme_name') . "/theme.cfg.php"; + $results = read_config($theme_path); + + return $results['author']; + +} // get_theme_author +?> diff --git a/lib/ui.php b/lib/ui.php new file mode 100644 index 00000000..bd1bb882 --- /dev/null +++ b/lib/ui.php @@ -0,0 +1,437 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @header UI Function Library + This contains functions that are generic, and display information + things like a confirmation box, etc and so forth +*/ + +/*! + @function show_confirmation + @discussion shows a confirmation of an action + @param $next_url Where to go next + @param $title The Title of the message + @param $text The details of the message +*/ +function show_confirmation($title,$text,$next_url) { + + if (substr_count($next_url,conf('web_path'))) { + $path = $next_url; + } + else { + $path = conf('web_path') . "/$next_url"; + } + + require (conf('prefix') . "/templates/show_confirmation.inc.php"); + +} // show_confirmation + +/*! + @function set_preferences + @discussion legacy function... + //FIXME: Remove References +*/ +function set_preferences() { + + get_preferences(); + return true; + +} // set_preferences + +/*! + @function get_preferences + @discussion reads this users preferences +*/ +function get_preferences($username=0) { + + /* Get System Preferences first */ + $sql = "SELECT preferences.name,user_preference.value FROM preferences,user_preference WHERE user_preference.user='0' " . + " AND user_preference.preference = preferences.id AND preferences.type='system'"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $results[$r->name] = $r->value; + } // end while sys prefs + + conf($results, 1); + + unset($results); + + if (!$username) { $username = $_SESSION['userdata']['username']; } + + $user = new User($username); + + $sql = "SELECT preferences.name,user_preference.value FROM preferences,user_preference WHERE user_preference.user='$user->id'" . + " AND user_preference.preference=preferences.id"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $results[$r->name] = $r->value; + } + + unset($results['user'], $results['id']); + + conf($results, 1); + +} // get_preferences + +/*! + @function flip_class + @discussion takes an array of 2 class names + and flips them back and forth and + then echo's out [0] +*/ +function flip_class($array=0) { + + static $classes = array(); + + if ($array) { + $classes = $array; + } + else { + $classes = array_reverse($classes); + return $classes[0]; + } + +} // flip_class + +/*! + @function clear_now_playing + @discussion Clears the now playing information incase something has + gotten stuck in there +*/ +function clear_now_playing() { + + $sql = "DELETE FROM now_playing"; + $db_results = mysql_query($sql, dbh()); + + return true; + +} // clear_now_playing + +/*! + @function show_tool_box + @discussion shows the toolbox +*/ +function show_tool_box ($title, $items) { + + include(conf('prefix') . "/templates/tool_box.inc"); + +}// show_tool_box + +/*! + @function show_box + @discussion shows a generic box +*/ +function show_box($title,$items) { + + include(conf('prefix') . "/templates/show_box.inc"); + +} // show_box + +/*! + @function show_menu_items + @discussion shows menu items +*/ +function show_menu_items ($high) { + + include(conf('prefix') . "/templates/menu.inc"); + +} // show_menu_items + +/*! + @function _ + @discussion checks to see if the alias _ is defined + if it isn't it defines it as a simple return +*/ +if (!function_exists('_')) { + function _($string) { + + return $string; + + } // _ +} // if _ isn't defined + +/*! + @function show_playlist_menu + @discussion playlist functions +*/ +function show_playlist_menu () { + + echo "<br /><span class=\"header2\">" . _("Playlist Actions") . ": <a href=\"" . conf('web_path') . "/playlist.php?action=new\">" . _("New") ."</a> | "; + echo "<a href=\"" . conf('web_path') . "/playlist.php\"> " . _("View All") . "</a> | "; + echo "<a href=\"" . conf('web_path') . "/playlist.php?action=show_import_playlist\"> " . _("Import") . "</a>"; + echo "</span><br /><br />"; + +} // show_playlist_menu + +/*! + @function show_admin_menu + @discussion shows the admin menu +*/ +function show_admin_menu ($admin_highlight) { + include(conf('prefix') . "/templates/admin_menu.inc"); +} // show_admin_menu + +/*! + @function access_denied + @discussion throws an error if they try to do something + that they aren't allowed to +*/ +function access_denied() { + + show_template('style'); + show_footer(); + echo "<br /><br /><br />"; + echo "<div class=\"fatalerror\">Error Access Denied</div>\n"; + show_footer(); + exit(); + +} // access_denied + +/*! + @function show_users + @discussion shows all users (admin function) +*/ +function show_users () { + + $dbh = dbh(); + + // Setup the View Ojbect + $view = new View(); + $view->import_session_view(); + + // if we are returning + if ($_REQUEST['keep_view']) { + $view->initialize(); + } + // If we aren't keeping the view then initlize it + else { + $sql = "SELECT username FROM user"; + $db_results = mysql_query($sql, $dbh); + $total_items = mysql_num_rows($db_results); + if ($match != "Show_all") { $offset_limit = $_SESSION['userdata']['offset_limit']; } + $view = new View($sql, 'admin/users.php','fullname',$total_items,$offset_limit); + } + + $db_result = mysql_query($view->sql, $dbh); + + require(conf('prefix') . "/templates/show_users.inc"); + +} // show_users() + + +/*! + @function return_referer + @discussion returns the script part of the + referer address passed by the web browser + this is not %100 accurate +*/ +function return_referer() { + + $web_path = substr(conf('web_path'),0,strlen(conf('web_path'))-1-strlen($_SERVER['SERVER_PORT'])) . "/"; + $next = str_replace($web_path,"",$_SERVER['HTTP_REFERER']); + + // If there is more than one :// we know it's fudged + // and just return the index + if (substr_count($next,"://") > 1) { + return "index.php"; + } + + return $next; + +} // return_referer + +/*! + @function show_alphabet_list + @discussion shows the A-Z,0-9 lists for + albums and artist pages +*/ +function show_alphabet_list ($type,$script="artist.php",$selected="false") { + + $list = array(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,1,2,3,4,5,6,7,8,9,"0"); + + $style_name = "style_" . strtolower($selected); + ${$style_name} = "style=\"font-weight:bold;\""; + + echo "<div class=\"alphabet\">"; + foreach ($list as $l) { + $style_name = "style_" . strtolower($l); + echo "<a href=\"". conf('web_path') ."/$script?action=match&match=$l\" " . ${$style_name} . ">$l</a> | \n"; + } + + echo " <a href=\"". conf('web_path') ."/$script?action=match&match=Browse\" $style_browse>" . _("Browse") . "</a> | \n"; + if ($script == "albums.php") { + echo " <a href=\"". conf('web_path') ."/$script?action=match&match=Show_missing_art\" $style_show_missing_art>" . _("Show w/o art") . "</a> | \n"; + } // if we are on the albums page + + echo " <a href=\"". conf('web_path') ."/$script?action=match&match=Show_all\" $style_show_all>" . _("Show all") . "</a>"; + + echo "</div>\n"; +} // show_alphabet_list + +/*! + @function show_local_control + @discussion shows the controls + for localplay +*/ +function show_local_control () { + + require_once(conf('prefix') . "/templates/show_localplay.inc"); + +} // show_local_control + +/*! + @function truncate_with_ellipse + @discussion truncates a text file to specified length by adding + thre dots (ellipse) to the end + (Thx Nedko Arnaudov) +*/ +function truncate_with_ellipse($text, $max=27) { + + /* If we want it to be shorter than three, just throw it back */ + if ($max > 3) { + /* Make sure the functions exist before doing the iconv mojo */ + if (function_exists('iconv') && function_exists('iconv_substr') && function_exists('iconv_strlen')) { + if (iconv_strlen($text, conf('site_charset')) > $max) { + $text = iconv_substr($text, 0, $max-3, conf('site_charset')); + $text .= iconv("ISO-8859-1", conf('site_charset'), "..."); + } + } + /* Do normal substr if we don't have iconv */ + else { + if (strlen($text) > $max) { + $text = substr($text,0,$max-3)."..."; + } + } // else no iconv + } // else greater than 3 + + return $text; +} // truncate_with_ellipse + +/*! + @function show_footer + @discussion shows the footer of the page +*/ +function show_footer() { + $class = "table-header"; + echo "<br /><br /><br /><div class=\"$class\" style=\"border: solid thin black;\"> </div>"; +} // show_footer + +/*! + @function show_now_playing + @discussion shows the now playing template +*/ +function show_now_playing() { + + $dbh = dbh(); + $web_path = conf('web_path'); + $results = get_now_playing(); + require (conf('prefix') . "/templates/show_now_playing.inc"); + +} // show_now_playing + +/*! + @function show_user_registration + @discussion this function is called for a new user + registration + @author Terry +*/ +//function show_user_registration ($id, $username, $fullname, $email, $access, $type, $error) { +//FIXME: See above +function show_user_registration ($values=array()) { + + require (conf('prefix') . "/templates/show_user_registration.inc.php"); + +} // show_user_registration + +/*! + @function show_edit_profile + @discussion shows a single user profile for editing +*/ +function show_edit_profile($username) { + + $this_user = new User($username); + + require (conf('prefix') . "/templates/show_user.inc.php"); + +} // show_edit_profile + +/*! + @function show_playlist + @discussion this shows the current playlist +*/ +function show_playlist($playlist_id) { + + /* Create the Playlist */ + $playlist = new Playlist($playlist_id); + $song_ids = $playlist->get_songs(); + + if (count($song_ids) > 0) { + show_songs($song_ids, $playlist->id); + } + else { + echo "<p>" . _("No songs in this playlist.") . "</p>\n"; + } + +} // show_playlist + +/*! + @function show_play_selected + @discussion this shows the playselected/add to playlist + box, which includes a little javascript +*/ +function show_play_selected() { + + require (conf('prefix') . "/templates/show_play_selected.inc.php"); + +} // show_play_selected + +/*! + @function get_now_playing + @discussion gets the now playing information +*/ +function get_now_playing() { + + $sql = "SELECT song_id,user_id FROM now_playing ORDER BY start_time DESC"; + $db_results = mysql_query($sql, dbh()); + while ($r = mysql_fetch_assoc($db_results)) { + $song = new Song($r['song_id']); + $song->format_song(); + $np_user = new User(0,$r['user_id']); + $results[] = array('song'=>$song,'user'=>$np_user); + } // end while + return $results; + +} // get_now_playing + +/*! + @function show_clear + @discussion this is a hack because of the float mojo +*/ +function show_clear() { + + echo "\n<div style=\"clear:both;\"> </div>\n"; + +} // show_clear + +?> diff --git a/lib/xmlrpc.php b/lib/xmlrpc.php new file mode 100644 index 00000000..7b810dd6 --- /dev/null +++ b/lib/xmlrpc.php @@ -0,0 +1,143 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header XML-RPC Library + @discussion If you want an honest answer NFC. Copy and paste baby! +*/ + +/*! + @function remote_server_query + @discussion don't ask don't tell +*/ +function remote_server_query($m) { + + $result = array(); + + // we only want to send the local entries + $sql = "SELECT name FROM catalog WHERE catalog_type='local'"; + $db_result = mysql_query($sql, dbh()); + + while ( $i = mysql_fetch_row($db_result) ) { + $result[] = $i; + } + + + set_time_limit(0); + $encoded_array = new xmlrpcval($result); + log_event($_SESSION['userdata']['username'],'xml-rpc_encoded',$encoded_array); + return new xmlrpcresp($encoded_array); + return $result; + +} // remote_server_query + +/*! + @function remote_song_query + @discussion return all local songs and their + information +*/ +function remote_song_query() { + + + //FIXME: We should add catalog level access control + + // Get me a list of all local catalogs + $sql = "SELECT catalog.id FROM catalog WHERE catalog_type='local'"; + $db_results = mysql_query($sql, dbh()); + + $results = array(); + //FIXME: enabled --> smallint(1) T/F boolean mojo + $sql = "SELECT song.id FROM song WHERE song.status='enabled' AND"; + + // Get the catalogs and build the query! + while ($r = mysql_fetch_object($db_results)) { + if (preg_match("/catalog/",$sql)) { + $sql .= " OR song.catalog='$r->id'"; + } + else { + $sql .= " song.catalog='$r->id'"; + } + } // build query + + $db_results = mysql_query($sql, dbh()); + + // Recurse through the songs and build a results + // array that is base64_encoded + while ($r = mysql_fetch_object($db_results)) { + + $song = new Song($r->id); + $song->album = $song->get_album_name(); + $song->artist = $song->get_artist_name(); + $song->genre = $song->get_genre_name(); + + // Format the output + $output = ''; + $output = $song->artist . "::" . $song->album . "::" . $song->title . "::" . $song->comment . + "::" . $song->year . "::" . $song->bitrate . "::" . $song->rate . "::" . $song->mode . + "::" . $song->size . "::" . $song->time . "::" . $song->track . "::" . $song->genre . "::" . $r->id; + $output = base64_encode($output); + $results[] = $output; + + // Prevent Timeout + set_time_limit(0); + + } // while songs + + set_time_limit(0); + $encoded_array = old_xmlrpc_encode($results); + return new xmlrpcresp($encoded_array); + +} // remote_song_query + +/*! + @function remote_server_denied + @discussion Access Denied Sucka! +*/ +function remote_server_denied() { + + $result = array(); + + $result['access_denied'] = "Access Denied: Sorry, but " . $_SERVER['REMOTE_ADDR'] . " does not have access to " . + "this server's catalog. Please make sure that you have been added to this server's access list.\n"; + + $encoded_array = old_xmlrpc_encode($result); + return new xmlrpcresp($encoded_array); + +} // remote_server_deniee + + + + + + + + + + + + + + + + + +?> diff --git a/libglue/README b/libglue/README new file mode 100644 index 00000000..b847586b --- /dev/null +++ b/libglue/README @@ -0,0 +1,393 @@ +libglue - 8/17/03 + +libglue provides a set of libraries for use with applications +developed here at Oregon State University. + +This set of libraries includes: +- mysql session handling, +- MySQL/LDAP/'shared' authentication methods +- a database handler + +Contents: +1 Authentication Methods + 1.1 LDAP Authentication + 1.2 MySQL Authentication + 1.3 Shared Authentication +2 Database schemas + 2.1 For Session management + 2.2 For LDAP Authentication + 2.3 For MySQL Authentication + 2.4 For Shared Authentication +3 The Config file + 3.1 Formatting + 3.2 Subsections + 3.3 Arrays + 3.3 Retrieving options +4 Session management +5 Libglue in action + +6 Help, FAQs + + +1 Authentication Methods +-------------------------------------------------------------------------------- + Libglue currently supports 3 authentication methods: LDAP, MySQL, and + 'Shared.' It can support any combination of these concurrently, by falling + through in the order you specify (see Section 3.3). + + 1.1 LDAP Authentication + ------------------------------------------------------------------------------ + To use LDAP authentication, you must have LDAP support for PHP. + See http://php.net/manual/en/ref.ldap.php for how to configure php with LDAP. + + You must provide credentials for your LDAP server in the config file. + Anonymous LDAP authentication is possible, but not with libglue today. + + libglue has two functions for ldap authentication, both in 'LIBGLUE/auth.php': + + mixed get_ldap_user($username [,$fields]) + + object auth_ldap($username,$password) + + 'auth_ldap' is intended for internal use, while 'get_ldap_user' is a utility + for app developers to use. Both have similar layouts: + + - connect to ldap service + - bind to ldap with credential from config file + - search for '$username' in the space specified in the config file + - attempt to bind with supplied user credentials (auth_ldap only) + + 'get_ldap_user' returns an array of fields on success (if '$fields' is + not specified, it will return all the information for the specified user), + and an error string on failure. + + 'auth_ldap' returns an 'auth_response' object, with success indicated by + the value of auth_response->success. (This class is defined in 'auth.php'). + + 'auth_ldap' is typically only going to be called from a login script. + 'get_ldap_user' could be used when granting access to a new user. + + Config Options: + ldap_host + ldap_auth_dn + ldap_user_dn + ldap_filter + ldap_pass + ldap_fields + ldap_version + + + 1.2 MySQL Authentication + ------------------------------------------------------------------------------ + MySQL Authentication (like all of libglue) requires MySQL support in PHP. + It defaults to using the MySQL PASSWORD() function to check passwords, but + can also use PHP crypt() for compatability with other applications + (pam_mysql for example). + + MySQL Authentication assumes the local database specified in the config file + is being used. It is possible to support a different database, but that + begins to duplicate functionality from Share Authentication (Section 1.3). + + Config Options: + mysql_host + mysql_db + mysql_username + mysql_passwd + mysql_table + mysql_usercol + mysql_passcol + mysql_other + mysql_fields + + 'mysql_other' is an optional clause appended to the query. + Ex: mysql_other = "access = 'admin'" + + 'mysql_fields' is a comma separated list of the fields to return from + 'mysql_table' + Ex: mysql_fields = 'id,username,email,homedir' + + 1.3 Shared Authentication + ------------------------------------------------------------------------------ + Because libglue uses a mysql database to store session info, it is possible + to share session information between applications, creating a "Single Sign + On" (SSO) framework for web applications. libglue supports this out of + the box, with the following assumptions: + 1) The initial authentication is being handled elsewhere + 2) "SSO" session data is stored in a mysql database. + 3) The SSO database uses the schema described in Section 2.3. + + libglue keeps track of the 'type' of authentication each session uses, so + can still use LDAP or MySQL authentication when also using SSO. + + Config options: + sso_host + sso_db + sso_username + sso_pass + sso_table + sso_sid + sso_usercol + sso_expirecol + sso_length + sso_dbh_name + + +2 Database schemas +-------------------------------------------------------------------------------- + + Below are sample schemas for use with libglue. Mandatory fields are + indicated with a '*'. Unless stated otherwise, field NAMES can be set in + the config file. + + 2.1 For Session Management + ------------------------------------------------------------------------------ + CREATE TABLE session_data ( + * id varchar(32) NOT NULL default '', + * username varchar(16) NOT NULL default '', + * expire int(10) unsigned NOT NULL default '0', + * type enum('sso','mysql','ldap') NOT NULL default 'sso', + * data text, + PRIMARY KEY (id)) + + This session table should work for any type of authentication you do. + 'id' is an md5sum by default (as generated by php) but you can make + something else up if you've got the spare entropy. 'Type' obviously + only applies if you're using more than 1 type of authentication, + but the code assumes that it's present. + + 'data' is the field where serialized php data (from $_SESSION) is stored. + If you overflow this field, weird things may happen. + + + 2.2 For LDAP Authentication + ------------------------------------------------------------------------------ + + Basic LDAP authentication with libglue doesn't require a mysql database, + only session management. However, you will most likely need to store + some user information locally, in which case the table definition in + Section 2.3 is a good starting point. + + 2.3 For MySQL Authentication + ------------------------------------------------------------------------------ + + CREATE TABLE user ( + * id int(11) NOT NULL default '0', + * username varchar(255) NOT NULL default '', + * password varchar(255) default NULL, + fullname varchar(255) NOT NULL default '', + email varchar(255) default NULL, + status enum('disabled','enabled','dev') NOT NULL default 'enabled', + expire date default NULL, + phone varchar(255) NOT NULL default '', + PRIMARY KEY (id), + UNIQUE KEY username (username), + UNIQUE KEY id (id) + } + + Feel free to add columns to this table and then specify them in + 'mysql_fields' to make them part of your session data. + + 2.3 For Shared Authentication + ------------------------------------------------------------------------------ + + If you need to store user data locally, see the definition in Section 2.3. + + +3 The Config file +-------------------------------------------------------------------------------- + + 3.1 Formatting + ------------------------------------------------------------------------------ + The libglue config file is a lot like the smb.cnf file if you've ever used + samba (it's really easy to parse). Options are specified like + + option = value + + 'option' is a letter followed by any number of letters or numbers. + The spaces between 'option' and 'value' are optional. + 'value' may be single quoted, double quoted, or not quoted at all. + semicolons at the end of the line are ignored. + '#' is the single-line comment character. + + 3.2 Subsections + ------------------------------------------------------------------------------ + The config file parser can generate subsections in the config: + + > [libglue] + > option1 = value; + > option2 = 'value'; + > option3 = "value" + > [conf] + > option1 = value; + > otheroption = othervalue; + > [other] + > foo = "bar"; + + The parser then returns: + array( + 'libglue' => ('option1' => 'value', + 'option2' => 'value', + 'option3' => 'value'), + 'conf' => ('option1' => 'value', + 'otheroption' => 'othervalue'), + 'other' => ('foo' => 'bar') + ); + + 3.3 Arrays + ------------------------------------------------------------------------------ + You can create arrays of values in the config file by declaring an option + multiple times: + + [libglue] + ldap_fields = 'cn' + ldap_fields = 'homedirectory' + ldap_fields = 'uidnumber' + ldap_fields = 'uid' + ldap_fields = 'osuuid' + + would return the following: + array( + 'libglue' => ('ldap_fields' => ( + 0 => 'cn', + 1 => 'homedirectory', + 2 => 'uidnumber', + 3 => 'uid', + 4 => 'osuuid') + ) + ) + + + 3.3 Retrieving options + ------------------------------------------------------------------------------ + LIBGLUE/config.php defines two functions, conf() and libglue_param() for + retrieving values from the config file. See "Libglue in action" below. + + +4 Session Data and Management +-------------------------------------------------------------------------------- + + Libglue should relieve some of the burden of session management from your app + once a user is authenticated. The config file has the following parameters, + + user_data_name - Name of the array to store authentication session data in. + Ex: + user_data_name = 'user' + + Libglue then puts all the account information it retrieves + into $_SESSION['user'] + + user_id - fieldname for userid, + stored in $_SESSION[user_data_name][user_id] + user_username - fieldname for username, + stored in $_SESSION[user_data_name][user_username] + + Then for each of your authentication methods: + + ldap_uidfield = 'uidnumber' + ldap_usernamefield = 'uid' + mysql_uidfield = 'id' + mysql_usernamefield = 'username' + + Note that in this case, 'sso' isn't really an authentication method + (the info has to be looked up either in ldap or mysql). + + What this lets you do is ignore account type in your application, since + every session will have the same field names. + + +5 Libglue in action +-------------------------------------------------------------------------------- + + Libglue assumes there are three basic types of files in your application: + 1) Login/Authentication page + 2) Restricted pages + 3) Logout/Cleanup page + + Example login and logout pages are in the LIBGLUE/examples directory. + + For (2), you'll be calling the same code over and over. It is a good idea + to create an init file to take care of these common tasks in your application. + In each file, you'll do a + + > $restricted = 1; + > require_once('/path/to/init.php'); + + right off the bat; libglue needs to run before anything else so it can send + HTTP headers for cookies and redirection if necessary. + + Here is a sample init.php: + + + <?php + //config defines readconfig(), libglue_param(), and conf() + require_once("/data/libglue/config.php"); + // 1st parameter is the path to the config file + // 2nd parameter is DEBUG (1 or 0) + // '$config' will hold the parsed config data + $config = read_config("/data/app/conf/config.conf",0); + + // Register subsection 'libglue' in libglue_param() + libglue_param($config['libglue']); + + //Register subsection 'app' in conf() + conf($config['app']); + //Require the rest of the libglue code: + // Authentication methods: + require_once('/data/libglue/auth.php'); + // Session handling code: + require_once('/data/libglue/session.php'); + // Common database handle: + require_once('/data/libglue/dbh.php'); + + // This is optional, if you have some pages where session data and + // authentication aren't relevant. Otherwise just do check_session(). + if($restricted === 1) check_session(); + ?> + + libglue_param() and conf() make use of static member variables and + tests on paramter types to register/retrieve config data. When passed an + array, these functions assume you're registering config options. If given + a string, as in: + + $database_name = libglue_param('local_db'); + + the function will look for the key 'local_db' in the values that have been + previously registered with it. This is a little bit hokey, but objects + don't yet support static members in php so it's about the best we can do. + + + Session management is just taken care of; anything you put in $_SESSION in + your app is serialized, stored in the db when the page is done redering, + and retrieved on page load. + + + Database handle management is nice with libglue; if you've defined + 'dbh' in the config file, you can call dbh() to use that database handle again: + + $dbh = dbh(); + mysql_query($sql, $dbh); + + or + + mysql_query($sql, dbh()); + + +6 Help, FAQs +-------------------------------------------------------------------------------- + + Libglue is distributed at: + + http://oss.oregonstate.edu/libglue/ + + as it becomes more mature and widely used, this page may include more help + documentation. + + For now, feel free to email + + cws-prog@lists.orst.edu + + with any questions or bug reports. + +-- Central Web Services, + Oregon State University + diff --git a/libglue/auth.php b/libglue/auth.php new file mode 100644 index 00000000..0ef41e8c --- /dev/null +++ b/libglue/auth.php @@ -0,0 +1,399 @@ +<? +/* ------------------- CVS INFO ---------------------- + * + * $Source: /data/cvsroot/ampache/libglue/auth.php,v $ + * last modified by $Author: vollmerk $ at $Date: 2003/11/27 10:19:28 $ + * + * Libglue, a free php library for handling authentication + * and session management. + * + * Written and distributed by Oregon State University. + * http://oss.oregonstate.edu/libglue + * + * --------------------------------------------------- + */ + +// +// Attempt to authenticate using the services in +// auth_methods, and returns an auth_config object +// which describes the results of the authentication +// attempt +function authenticate($username, $password) +{ + // First thing to do is check for the gone fishing file: + $stopfile = libglue_param('stop_auth'); + if ( file_exists($stopfile) ) + { + echo "We should tell the users here that no one can log in.\n"; + exit(); + } + + $methods = libglue_param('auth_methods'); + if(!is_array($methods)) + { + $auth = call_user_func("auth_$methods",$username,$password); + } + else + { + foreach($methods as $method) + { + $auth = call_user_func("auth_$method", $username,$password); + if($auth['success'] == 1) break; + } + } + return $auth; +} + +function get_ldap_user ($username,$fields=0) +{ + $auth = array(); + + $auth_dn = libglue_param('ldap_auth_dn'); + $user_dn = libglue_param('ldap_user_dn'); + $filter = libglue_param('ldap_filter'); + $host = libglue_param('ldap_host'); + $pass = libglue_param('ldap_pass'); + $ldapfields = libglue_param('ldap_fields'); + $protocol = libglue_param('ldap_version'); + + // can we even connect? + if ( $ldap_link = @ldap_connect( $host ) ) + { + + //Snazzy new protocol stuff + if(!empty($protocol)) ldap_set_option($ldap_link, + LDAP_OPT_PROTOCOL_VERSION, + $protocol); + + // now try and bind with system credentials for searching. + if ( @ldap_bind($ldap_link, $filter."".$auth_dn, $pass) ) + { + // now search and retrieve our user data + $ldap_uid = libglue_param('ldap_uidfield'); + $ldap_username = libglue_param('ldap_usernamefield'); + + //force uid and username to be part of the query + if(!in_array($ldap_uid,$ldapfields)) $ldapfields[] = $ldap_uid; + if(!in_array($ldap_username,$ldapfields)) $ldapfields[] = $ldap_username; + + $sr = ldap_search($ldap_link, $user_dn, "(".$filter."".$username.")", $ldapfields, 0, 1); +/* $sr = @ldap_search($ldap_link, $user_dn, "(".$filter."".$username.")");*/ + + //info will contain a 1-element array with our user's info + $info = ldap_get_entries($ldap_link, $sr); + + foreach($ldapfields as $field) + { + $auth[$field] = $info[0][$field][0]; + } + $sess_username = libglue_param('user_username'); + $sess_id = libglue_param('user_id'); + $auth[$sess_username] = $username; + $auth[$sess_id] = $info[0][$ldap_uid][0]; + } + + // + // Here means we couldn't use the service. + // So it's most likely config related. + // Check the username and password? + // + else + { $auth['error'] = libglue_param('bad_auth_cred'); } + } + + // + // This most often will mean we can't reach the server. + // Perhaps it's down, or we mistyped the address. + // + else + { $auth['error'] = libglue_param('connect_error'); } + + // Done with the link, give it back + ldap_close($ldap_link); + + $auth_methods = libglue_param('auth_methods'); + if(!is_array($auth_methods)) $auth_methods = array($auth_methods); + if(in_array('sso',$auth_methods,TRUE)) $auth['type'] = 'sso'; + else $auth['type'] = 'ldap'; + return $auth; +} + +function get_mysql_user ($username,$fields=null) +{ + $auth = array(); + $dbh = dbh(); + $user_table = libglue_param('mysql_table'); + $mysql_uid = libglue_param('mysql_uidfield'); + $mysql_username = libglue_param('mysql_usernamefield'); + $mysql_fields = libglue_param('mysql_fields'); + $sql = "SELECT "; + if(is_null($fields)) $sql .= " * "; + else + { + if(!is_array($fields)) $fields = array($fields); + foreach($fields as $field) + { + $sql .= "$field,"; + } + $sql = substr($sql, 0, strlen($sql)-1); + } + + $sql .= " FROM $user_table WHERE $mysql_username = '$username'"; + $result = mysql_query($sql, $dbh); + + foreach($ldapfields as $field) + { + $auth[$field] = $info[0][$field][0]; + } + $sess_username = libglue_param('user_username'); + $sess_id = libglue_param('user_id'); + $auth[$sess_username] = $username; + $auth[$sess_id] = $info[0][$ldap_uid][0]; + + $auth['type'] = 'mysql'; + return $auth; +} + + +function auth_ldap ($username, $password) +{ + $auth = array(); + $auth['success'] = 0; // don't want to keep setting this + $auth_dn = libglue_param('ldap_auth_dn'); + $user_dn = libglue_param('ldap_user_dn'); + $filter = libglue_param('ldap_filter'); + $host = libglue_param('ldap_host'); + $pass = libglue_param('ldap_pass'); + $ldapfields = libglue_param('ldap_fields'); + // Did we get fed proper variables? + if(!$username || !$password) + { + $auth['error'] = libglue_param('empty_field'); + // I'm not a huge fan of returning here, + // but why force more logic? + return $auth; + } + + // can we even connect? + if ( $ldap_link = @ldap_connect( $host ) ) + { + // now try and bind with system credentials for searching. + if ( @ldap_bind($ldap_link, $filter."".$auth_dn, $pass) ) + { + // now search and retrieve our user data + $ldap_uid = libglue_param('ldap_uidfield'); + $ldap_username = libglue_param('ldap_usernamefield'); + + //force uid and username to be part of the query + if(!in_array($ldap_uid,$ldapfields)) $ldapfields[] = $ldap_uid; + if(!in_array($ldap_username,$ldapfields)) $ldapfields[] = $ldap_username; + + $sr = ldap_search($ldap_link, $user_dn, "(".$filter."".$username.")", $ldapfields, 0, 1); + //info will contain a 1-element array with our user's info + $info = @ldap_get_entries($ldap_link, $sr); + + // + // The real authentication: + // binding here with the user's credentials + // + //if ( ldap_bind($ldap_link, $user_dn, $password) ) { + if ( ($info["count"] == 1) && (@ldap_bind($ldap_link, + $info[0]['dn'], + $password) ) ) + { + $auth['info'] = array(); + foreach($ldapfields as $field) + { + $auth['info'][$field] = $info[0][$field][0]; + } + $sess_username = libglue_param('user_username'); + $sess_id = libglue_param('user_id'); + $auth['info'][$sess_username] = $username; + $auth['info'][$sess_id] = $info[0][$ldap_uid][0]; + $auth['success'] = 1; + } + else + { + // show the error here, better than anything I can come up with + // most likely bad username or password + // We'll handle two cases, where the username doesn't exist, + // and where more than 1 exists separately in case we + // decide to do some logging or something fancy someday + if($info["count"] == 0) + { + $auth['error'] = libglue_param('login_failed'); + } + else + { + // We could return the error here + // EXCEPT that we want the error message to be the same + // for a bad password as a bad username + // $auth->error = ldap_error($ldap_link); + $auth['error'] = libglue_param('login_failed'); + } + } + } + + // + // Here means we couldn't use the service. + // So it's most likely config related. + // Check the username and password? + // + else + { + $auth['error'] = libglue_param('bad_auth_cred'); + } + } + + // + // This most often will mean we can't reach the server. + // Perhaps it's down, or we mistyped the address. + // + else + { + $auth['error'] = libglue_param('connect_error'); + } + + // Done with the link, give it back + ldap_close($ldap_link); + $auth['type'] = 'ldap'; + return $auth; +} + +/* + * MySQL authentication. + * returns true/false depending on whether the user was authenticated + * successfully + * The crypt settings below assume the php crypt() function created the passwords. + * But hopson updated it to use mysql PASSWORD() instead + */ + +function auth_mysql($username, $password) { + + $auth = array(); + $auth['success'] = 0; + + // Did we get fed proper variables? + if(!$username or !$password) { + $auth['error'] = 'Empty username/password'; + return $auth; + } + + // + // Retrieve config parameters set in config.php + // + $dbhost = libglue_param('mysql_host'); + $dbuser = libglue_param('mysql_user'); + $dbpass = libglue_param('mysql_pass'); + $dbname = libglue_param('mysql_db'); + $passfield = libglue_param('mysql_passcol'); + $table = libglue_param('mysql_table'); + $usercol = libglue_param('mysql_usercol'); + $other = libglue_param('mysql_other'); + $fields = libglue_param('mysql_fields'); + + + $mysql_uidfield = libglue_param('mysql_uidfield'); + $mysql_usernamefield = libglue_param('mysql_usernamefield'); + + if(!preg_match("/$mysql_uidfield/",$fields)) $fields .= ",$mysql_uidfield"; + if(!preg_match("/$mysql_usernamefield/",$fields)) $fields .= ",$mysql_usernamefield"; + + if($other == '') $other = '1=1'; + + if ($mysql_link = @mysql_connect($dbhost,$dbuser,$dbpass)) + { + // + // now retrieve the stored password to use as salt + // for password checking + // + $sql = "SELECT $passfield FROM $table" . + " WHERE $usercol = '$username' " . + " AND $other LIMIT 1"; + @mysql_select_db($dbname, $mysql_link); + $result = @mysql_query($sql, $mysql_link); + $row = @mysql_fetch_array($result); + + $password_check_sql = "PASSWORD('$password')"; + + $sql = "SELECT version()"; + $db_results = @mysql_query($sql, $mysql_link); + $version = @mysql_fetch_array($db_results); + + $mysql_version = substr(preg_replace("/(\d+)\.(\d+)\.(\d+).*/","$1$2$3",$version[0]),0,3); + + if ($mysql_version > "409" AND substr($row[0],0,1) !== "*") { + $password_check_sql = "OLD_PASSWORD('$password')"; + } + + $sql = "SELECT $fields FROM $table" . + " WHERE $usercol = '$username'" . + " AND $passfield = $password_check_sql" . + " AND $other LIMIT 1"; + $rs = @mysql_query($sql, $mysql_link); + //This should only fail on a badly formed query. + if(!$rs) + { + $auth['error'] = @mysql_error(); + } + + // + // Retrieved the right info, set auth->success and info. + // + if (@mysql_num_rows($rs) == 1) + { + // username and password are successful + $row = mysql_fetch_array($rs); + $sess_username = libglue_param('user_username'); + $sess_id = libglue_param('user_id'); + $auth[$info][$sess_username] = $row[$mysql_usernamefield]; + $auth[$info][$sess_id] = $row[$mysql_uidfield]; + $auth[$info] = $row; + $auth['info'] = $row; + $auth['success'] = 1; + } + + // + // We didn't find anything matching. No user, bad password, ? + // + else + { + $auth['error'] = libglue_param('login_failed'); + } + } + + // + // Couldn't connect to database at all. + // + else + { + $auth['error'] = libglue_param('bad_auth_cred'); + } + + $auth['type'] = 'mysql'; + return $auth; + +} // auth_mysql + + +function auth_sso ($username, $password) +{ + $auth = new auth_response(); + $auth->success = 0; + $auth->error = "SSO Authentication failed."; + return $auth; +} + +// This is the auth_response class that will be returned during +// and authentication - this allows us to set some variables +// by the session for later lookup +class auth_response { + var $username; + var $userid; + var $error; + var $success; + var $info; +} + + +?> diff --git a/libglue/config.php b/libglue/config.php new file mode 100644 index 00000000..c1ca07a8 --- /dev/null +++ b/libglue/config.php @@ -0,0 +1,173 @@ +<?php + +function read_config($config_file, $debug = 0) { + $fp = fopen($config_file,'r'); + if(!is_resource($fp)) die("Can't open config file $config_file"); + $file_data = fread($fp,filesize($config_file)); + fclose($fp); + + // explode the var by \n's + $data = explode("\n",$file_data); + if($debug) echo "<pre>"; + + $count = 0; + $config_name = ''; + foreach($data as $value) + { + $count++; + if (preg_match("/^\[([A-Za-z]+)\]$/",$value,$matches)) + { + // If we have previous data put it into $results... + if (!empty($config_name) && count(${$config_name})) $results[$config_name] = ${$config_name}; + $config_name = $matches[1]; + } // if it is a [section] name + + elseif ($config_name) + { + // if it's not a comment + if (preg_match("/^(\w[\w\d]*)\s*=\s*\"{1}(.*?)\"{1};*$/",$value,$matches) + || preg_match("/^(\w[\w\d]*)\s*=\s*\'{1}(.*?)\'{1};*$/", $value, $matches) + || preg_match("/^(\w[\w\d]*)\s*=\s*[\'\"]{0}(.*)[\'\"]{0};*$/",$value,$matches)) + { + if (isset(${$config_name}[$matches[1]]) && is_array(${$config_name}[$matches[1]]) && isset($matches[2]) ) + { + if($debug) + echo "Adding value <strong>$matches[2]</strong> to existing key <strong>$matches[1]</strong>\n"; + array_push(${$config_name}[$matches[1]], $matches[2]); + } + elseif (isset(${$config_name}[$matches[1]]) && isset($matches[2]) ) + { + if($debug) + echo "Adding value <strong>$matches[2]</strong> to existing key $matches[1]</strong>\n"; + ${$config_name}[$matches[1]] = array(${$config_name}[$matches[1]],$matches[2]); + } + elseif ($matches[2] !== "") + { + if($debug) + echo "Adding value <strong>$matches[2]</strong> for key <strong>$matches[1]</strong>\n"; + ${$config_name}[$matches[1]] = $matches[2]; + } + + // if there is something there and it's not a comment + elseif ($value{0} !== "#" AND strlen(trim($value)) > 0) + { + echo "Error Invalid Config Entry --> Line:$count"; die; + } // else if it's not a comment and there is something there + + else + { + if($debug) + echo "Key <strong>$matches[1]</strong> defined, but no value set\n"; + } + } // end if it's not a comment + + } // else if no config_name + + + elseif (preg_match("/^([\w\d]+)\s+=\s+[\"]{1}(.*?)[\"]{1}$/",$value,$matches) + || preg_match("/^([\w\d]+)\s+=\s+[\']{1}(.*?)[\']{1}$/", $value, $matches) + || preg_match("/^([\w\d]+)\s+=\s+[\'\"]{0}(.*)[\'\"]{0}$/",$value,$matches)) + { + if (is_array($results[$matches[1]]) && isset($matches[2]) ) + { + if($debug) + echo "Adding value <strong>$matches[2]</strong> to existing key <strong>$matches[1]</strong>\n"; + array_push($results[$matches[1]], $matches[2]); + } + elseif (isset($results[$matches[1]]) && isset($matches[2]) ) + { + if($debug) + echo "Adding value <strong>$matches[2]</strong> to existing key $matches[1]</strong>\n"; + $results[$matches[1]] = array($results[$matches[1]],$matches[2]); + } + elseif ($matches[2] !== "") + { + if($debug) + echo "Adding value <strong>$matches[2]</strong> for key <strong>$matches[1]</strong>\n"; + $results[$matches[1]] = $matches[2]; + } + + // if there is something there and it's not a comment + elseif ($value{0} !== "#" AND strlen(trim($value)) > 0) + { + echo "Error Invalid Config Entry --> Line:$count"; die; + } // else if it's not a comment and there is something there + + else + { + if($debug) + echo "Key <strong>$matches[1]</strong> defined, but no value set\n"; + } + + } // end else + + } // foreach + + if (count(${$config_name})) + { + $results[$config_name] = ${$config_name}; + } + + if($debug) echo "</pre>"; + + return $results; + +} // end read_config + +function libglue_param($param,$clobber=0) +{ + static $params = array(); + if(is_array($param)) + //meaning we are setting values + { + foreach ($param as $key=>$val) + { + if(!$clobber && isset($params[$key])) + { + echo "Error: attempting to clobber $key = $val\n"; + exit(); + } + $params[$key] = $val; + } + return true; + } + else + //meaning we are trying to retrieve a parameter + { + if(isset($params[$param])) return $params[$param]; + else return false; + } +} + +function conf($param,$clobber=0) +{ + static $params = array(); + if(is_array($param)) + //meaning we are setting values + { + foreach ($param as $key=>$val) + { + if(!$clobber && isset($params[$key])) + { + echo "Error: attempting to clobber $key = $val\n"; + exit(); + } + $params[$key] = $val; + } + return true; + } + else + //meaning we are trying to retrieve a parameter + { + if(isset($params[$param])) return $params[$param]; + else return false; + } +} + +function dbh($str='') +{ + if($str !== '') $dbh = libglue_param(libglue_param($str)); + else $dbh = libglue_param(libglue_param('dbh')); + if(!is_resource($dbh)) die("Bad database handle: $dbh"); + else return $dbh; +} diff --git a/libglue/dbh.php b/libglue/dbh.php new file mode 100644 index 00000000..71d04b9c --- /dev/null +++ b/libglue/dbh.php @@ -0,0 +1,53 @@ +<? +/* + * ---------------------------- CVS INFO -------------------------------- + * + * $Source: /data/cvsroot/ampache/libglue/dbh.php,v $ + * last modified by $Author: vollmerk $ at $Date: 2003/11/24 05:53:13 $ + * + * Libglue, a free php library for handling authentication + * and session management. + * + * Written and distributed by Oregon State University. + * http://oss.oregonstate.edu/libglue + * + * ----------------------------------------------------------------------- + */ + +/*---------------------------------------------------------------------- + + For complete information on this toolkit see the README located in this + directory. + + This is the database handler class. This will setup and return a + database handle for use in your application. Simply pass it a + username and password. If an error occurs you'll be presented with + a verbose reason for the error. +----------------------------------------------------------------------*/ + +function setup_sess_db($name, $host, $db, $username, $password) +{ + $dbh = @mysql_connect($host, $username, $password) or header("Location:" . conf('web_path') . "/test.php"); + if ( !is_resource($dbh) ) + { + echo "Unable to connect to \"". $host ."\" in order to \n" . + "use the \"". $db ."\" database with account \"".$username." : ".$password. + "\"\n . Perhaps the database is not " . + "running, \nor perhaps the admin needs to change a few variables in\n ". + "the config files in order to point to the correct database.\n"; + echo "Details: " . + mysql_errno() . ": " . + mysql_error() . "\n"; + die(); + } + + else + { + @mysql_select_db($db, $dbh) or header("Location:" . conf('web_path') . "/test.php"); + libglue_param(array($name=>$dbh)); + } + + return $dbh; +} + +?> diff --git a/libglue/libdb.php b/libglue/libdb.php new file mode 100644 index 00000000..00e8a9b2 --- /dev/null +++ b/libglue/libdb.php @@ -0,0 +1,95 @@ +<?php +// +// PHP itself sort of supports the behavior defined here, +// but I don't trust it, and I think it's better to do +// application-level database abstraction. +// + +function db_connect($host='localhost',$user=null,$password=null) +{ + static $dbh = null; + // If we haven't already connected, do so + // The first call must include this info + // Subsequent calls that provide this info may bork db_query() below if you're not careful, + // but until I can have static class variables, I'm not going to make an object + // out of this mojo. + if(!empty($host) && isset($user) && isset($password)) $dbh = @mysql_connect($host,$user,$password); + + // If we've already connected successfully, we're good + if(is_resource($dbh)){ return $dbh; } + // On a failed connection, let's just die? + else die("Unable to create database connection in db_connect()"); +} + +function db_makeinsert($vars, $table) +{ + static $tables = array(); + $dbh = db_connect(); + if(!isset($tables[$table])) $tables[$table] = db_describe($table); + $fields = $tables[$table]; + + foreach($fields as $field) + { + //only addslashes if magic quotes is off + if(get_magic_quotes_gpc) $vars[$field['Field']] = stripslashes($vars[$field['Field']]); + addslashes($vars[$field['Field']]); + + if(isset($vars[$field['Field']])) + { + + $q1 = isset($q1)? $q1.','.$field['Field']:'INSERT INTO '.$table.'('.$field['Field']; + $q2 = isset($q2)? $q2.",\"".$field[$var['Field']]."\"":" VALUES(\"".$vars[$field['Field']]."\""; + } + } + $q1.=')'; + $q2.=')'; + $query = $q1.$q2; + return $query; +} + + +function db_select($database, $dbh=null) +{ + if(is_resource($dbh)) @mysql_select_db($database); + else @mysql_select_db($database, db_connect()); +} + +function db_describe($thingy) +{ + $descriptions = array(); + foreach( (explode(',',$thingy)) as $field) + { + db_query("DESCRIBE $field"); + while($row = db_fetch()){ $descriptions[] = $row; } + } + return $descriptions; +} + +function db_query($qry=null, $dbh=null) +{ + static $result = null; + if(!is_resource($dbh)) $dbh = db_connect(); + if(is_null($qry)) + { + if(is_resource($result)) return $result; + else return false; + } + else + { + $result = @mysql_query($qry, $dbh); + return $result; + } +} + +function db_fetch($result=null) +{ + if(!is_resource($result)) return @mysql_fetch_array(db_query()); + else return @mysql_fetch_array($result); +} + +function db_scrub($var,$htmlok=false) +{ + if(!get_magic_quotes_gpc()) $var = addslashes($var); + return $var; +} + diff --git a/libglue/session.php b/libglue/session.php new file mode 100644 index 00000000..aef10c60 --- /dev/null +++ b/libglue/session.php @@ -0,0 +1,417 @@ +<? +/* ------------------- CVS INFO ---------------------- + * + * $Source: /data/cvsroot/ampache/libglue/session.php,v $ + * last modified by $Author: vollmerk $ at $Date: 2003/11/24 05:53:13 $ + * + * Libglue, a free php library for handling authentication + * and session management. + * + * Written and distributed by Oregon State University. + * http://oss.oregonstate.edu/libglue + * + * --------------------------------------------------- + */ + + +function check_sess_db($dbtype = 'local') +{ + if($dbtype === 'sso') + { + $dbh = libglue_param(libglue_param('sso_dbh_name')); + if(is_resource($dbh)) return $dbh; + $dbh_name = libglue_param('sso_dbh_name'); + $host = libglue_param('sso_host'); + $db = libglue_param('sso_db'); + $user = libglue_param('sso_username'); + $pass = libglue_param('sso_pass'); + $name = libglue_param('sso_dbh_name'); + } + elseif($dbtype === 'local') + { + $dbh = libglue_param(libglue_param('local_dbh_name')); + if(is_resource($dbh)) return $dbh; + $dbh_name = libglue_param('local_dbh_name'); + $host = libglue_param('local_host'); + $db = libglue_param('local_db'); + $user = libglue_param('local_username'); + $pass = libglue_param('local_pass'); + $name = libglue_param('local_dhb_name'); + } + $dbh = setup_sess_db($dbh_name,$host,$db,$user,$pass); + + if(is_resource($dbh)) return $dbh; + else die("Could not connect to $dbtype database for session management"); +} + +// +// Really we are just checking the session here -- we want to see if + +// if the user has a valid session, if they do then we'll let them do +// what they need to do. +// + +function check_session($id=0) +{ + //If an id isn't passed in, retrieve one from the cookie + if($id===0) { + + /* + We don't need to set cookie params here php + is smart enough to know which cookie it wants + via the session_name. Setting cookie params + here sometimes made php create a new cookie + which is very bad :) -- Vollmer + */ + $name = libglue_param('sess_name'); + if($name) session_name($name); + + // Start the session, then get the cookie id + session_start(); + $id = strip_tags($_COOKIE[$name]); + } + + // Determine if we need to check the SSO database: + $auth_methods = libglue_param('auth_methods'); + if(!is_array($auth_methods)) $auth_methods = array($auth_methods); + $sso_mode = in_array('sso',$auth_methods,TRUE); + + $local = get_local_session($id); + if($sso_mode) $sso = get_sso_session($id); + + if($sso_mode && !$sso) + { + return FALSE; + } + else if ($sso_mode && is_array($sso)) + { + if(is_array($local)) return TRUE; + else + { + // + // Should we do gc here, just in case + // local is only expired? + // (The insert in make_local_session + // will fail if we don't) + // + $newlocal = make_local_session_sso($sso); + return $newlocal; + } + } + //If we get here, we're not using SSO mode + else if (!is_array($local)) + { + return FALSE; + } + else return TRUE; +} + +function make_local_session_only($data,$id=0) +{ + if($id===0) + { + $name = libglue_param('sess_name'); + $domain = libglue_param('sess_domain'); + if($name) session_name($name); + //Lifetime of the cookie: + $cookielife = libglue_param('sess_cookielife'); + if(empty($cookielife)) $cookielife = 0; + //Secure cookie? + $cookiesecure = libglue_param('sess_cookiesecure'); + if(empty($cookiesecure)) $cookiesecure = 0; + //Cookie path: + $cookiepath = libglue_param('sess_cookiepath'); + if(empty($cookiepath)) $cookiepath = '/'; + + if(!empty($domain)) session_set_cookie_params($cookielife,$cookiepath,$domain,$cookiesecure); + + // Start the session + session_start(); + + + /* + Before a refresh we do not have a cookie value + here so let's use session_id() --Vollmer + */ + $id = session_id(); + } + + $userfield = libglue_param('user_username'); + $username = $data['info'][$userfield]; + $type = $data['type']; + + $local_dbh = check_sess_db('local'); + $local_table = libglue_param('local_table'); + $local_sid = libglue_param('local_sid'); + $local_usercol = libglue_param('local_usercol'); + $local_datacol = libglue_param('local_datacol'); + $local_expirecol = libglue_param('local_expirecol'); + $local_typecol = libglue_param('local_typecol'); + $sql= "INSERT INTO $local_table ". + " ($local_sid,$local_usercol,$local_typecol)". + " VALUES ('$id','$username','$type')"; + $db_result = mysql_query($sql, $local_dbh); + + if($db_result) return TRUE; + else return FALSE; +} + +function make_local_session_sso($sso_session) +{ + $sso_usercol = $sso_session[libglue_param('sso_usercol')]; + $sso_sid = $sso_session[libglue_param('sso_sid')]; + $sso_expire = $sso_session[libglue_param('sso_expirecol')]; + + $user = get_ldap_user($sso_usercol); + + $data = array('user'=>$user); + + //Somewhat stupidly, we have to initialize $_SESSION here, + // or sess_write will blast it for us + $_SESSION = $data; + + $db_data = serialize($data); + $local_dbh = check_sess_db('local'); + + //Local stuff we need: + $local_table = libglue_param('local_table'); + $local_sid = libglue_param('local_sid'); + $local_usercol = libglue_param('local_usercol'); + $local_datacol = libglue_param('local_datacol'); + $local_expirecol = libglue_param('local_expirecol'); + $local_typecol = libglue_param('local_typecol'); + $sql= "INSERT INTO $local_table ". + " ($local_sid,$local_usercol,$local_datacol,$local_expirecol,$local_typecol)". + " VALUES ('$sso_sid','$sso_usercol','$db_data','$sso_expire','sso')"; + $db_result = mysql_query($sql, $local_dbh); + + if($db_result) return TRUE; + else return FALSE; +} + +function get_local_session($sid) +{ + $local_table = libglue_param('local_table'); + $local_sid = libglue_param('local_sid'); + $local_expirecol = libglue_param('local_expirecol'); + $local_length = libglue_param('local_length'); + $local_usercol = libglue_param('local_usercol'); + $local_datacol = libglue_param('local_datacol'); + $local_typecol = libglue_param('local_typecol'); + + $local_dbh = check_sess_db('local'); + $time = time(); + $sql = "SELECT * FROM $local_table WHERE $local_sid='$sid' AND $local_expirecol > $time"; + $db_result = mysql_query($sql, $local_dbh); + $session = mysql_fetch_array($db_result); + + if(is_array($session)) $retval = $session; + else $retval = FALSE; + + if($retval === FALSE) + { + //Find out what's going on + } + + return $retval; +} + +function get_sso_session($sid) +{ + $sso_table = libglue_param('sso_table'); + $sso_sid = libglue_param('sso_sid'); + $sso_expirecol = libglue_param('sso_expirecol'); + $sso_length = libglue_param('sso_length'); + $sso_usercol = libglue_param('sso_usercol'); + + $sso_dbh = check_sess_db('sso'); + $time = time(); + $sql = "SELECT * FROM $sso_table WHERE $sso_sid='$sid' AND $sso_expirecol > $time"; + $db_result = mysql_query($sql, $sso_dbh); + $sso_session = mysql_fetch_array($db_result); + + $retval = (is_array($sso_session))?$sso_session:FALSE; + return $retval; +} + + + +// This will start the session tools, then destroy anything in the database then +// clear all of the session information +function logout ($id=0) +{ + sess_destroy($id); + $login_page = libglue_param('login_page'); + // should clear both the database information as well as the + // current session info + header("Location: $login_page"); + die(); + return true; +} + +// Double checks that we have a database handle +// Args are completely ignored - we're using a database here +function sess_open($save_path, $session_name) +{ + $local_dbh = check_sess_db(); + if ( !is_resource($local_dbh) ) + { + echo "<!-- Unable to connect to local server in order to " . + "use the session database. Perhaps the database is not ". + "running, or perhaps the admin needs to change a few variables in ". + "the config file in order to point to the correct ". + "database.-->\n"; + return FALSE; + } + + $auth_methods = libglue_param('auth_methods'); + if(!is_array($auth_methods)) $auth_methods = array($auth_methods); + if(in_array('sso',$auth_methods,TRUE)) + { + $sso_dbh = check_sess_db('sso'); + if ( !is_resource($sso_dbh) ) + { + echo "<!-- Unable to connect to the SSO server in order to " . + "use the session database. Perhaps the database is not ". + "running, or perhaps the admin needs to change a few variables in ". + "modules/include/global_settings in order to point to the correct ". + "database.-->\n"; + return FALSE; + } + } + return TRUE; +} + +// Placeholder function, does nothing +function sess_close() +{ + return true; +} + +// Retrieve session identified by 'key' from the database +// and return the data field +function sess_read($key) +{ + $retval = 0; + $session = get_local_session($key); + $datacol = libglue_param('local_datacol'); + if(is_array($session)) $retval = $session[$datacol]; + else $retval = ""; + return $retval; +} + + +// +// Save the session data $val to the database +// +function sess_write($key, $val) +{ + $local_dbh = check_sess_db('local'); + $local_datacol = libglue_param('local_datacol'); + $local_table = libglue_param('local_table'); + $local_sid = libglue_param('local_sid'); + + $auth_methods = libglue_param('auth_methods'); + $local_expirecol = libglue_param('local_expirecol'); + $local_length = libglue_param('local_length'); + $time = $local_length+time(); + + // If they've got the long session + if ($_COOKIE['amp_longsess'] == '1') { + $time = time() + 86400*364; + } + + if(!is_array($auth_methods)) $auth_methods = array($auth_methods); + if(!in_array('sso',$auth_methods,TRUE)) + { + // If not using sso, we now need to update the expire time + $sql = "UPDATE $local_table SET $local_datacol='" . sql_escape($val) . "',$local_expirecol='$time'". + " WHERE $local_sid = '$key'"; + } + else $sql = "UPDATE $local_table SET $local_datacol='" . sql_escape($val) . "',$local_expirecol='$time'". + " WHERE $local_sid = '$key'"; + + return mysql_query($sql, $local_dbh); +} + +// +// Remove the current session from the database. +// +function sess_destroy($id=0) +{ + if($id == 0) { + session_start(); + $id = session_id(); + } + + $auth_methods = libglue_param('auth_methods'); + if(!is_array($auth_methods)) $auth_methods = array($auth_methods); + if(in_array('sso',$auth_methods,TRUE)) + { + $sso_sid = libglue_param('sso_sid'); + $sso_table = libglue_param('sso_table'); + $sso_dbh = check_sess_db('sso'); + $sql = "DELETE FROM $sso_table WHERE $sso_sid = '$id' LIMIT 1"; + $result = mysql_query($sql, $sso_dbh); + } + $local_sid = libglue_param('local_sid'); + $local_table = libglue_param('local_table'); + + $local_dbh = check_sess_db('local'); + $sql = "DELETE FROM $local_table WHERE $local_sid = '$id' LIMIT 1"; + $result = mysql_query($sql, $local_dbh); + $_SESSION = array(); + + /* Delete the long ampache session cookie */ + setcookie ("amp_longsess", "", time() - 3600); + + /* Delete the ampache cookie as well... */ + setcookie (libglue_param('sess_name'),"", time() - 3600); + + return TRUE; +} + +// +// This function is called with random frequency +// to remove expired session data +// +function sess_gc($maxlifetime) +{ + $auth_methods = libglue_param('auth_methods'); + if(!is_array($auth_methods)) $auth_methods = array($auth_methods); + if(in_array('sso',$auth_methods,TRUE)) + { + //Delete old sessions from SSO + // We do 'where length' so we don't accidentally blast + // another app's sessions + $sso_expirecol = libglue_param('sso_expirecol'); + $sso_table = libglue_param('sso_table'); + $sso_length = libglue_param('sso_length'); + $local_length = libglue_param('local_length'); + + $sso_dbh = check_sess_db('sso'); + $time = time(); + $sql = "DELETE FROM $sso_table WHERE $sso_expirecol < $time". + " AND $sso_length = '$local_length'"; + $result = mysql_query($sql, $sso_dbh); + } + $local_expirecol = libglue_param('local_expirecol'); + $local_table = libglue_param('local_table'); + $time = time(); + $local_dbh = check_sess_db('local'); + $sql = "DELETE FROM $local_table WHERE $local_expirecol < $time"; + $result = mysql_query($sql, $local_dbh); + return true; +} + +// +// Register all our cool session handling functions +// +session_set_save_handler( + "sess_open", + "sess_close", + "sess_read", + "sess_write", + "sess_destroy", + "sess_gc"); +?> diff --git a/libglue/session2.php b/libglue/session2.php new file mode 100644 index 00000000..171dc1ca --- /dev/null +++ b/libglue/session2.php @@ -0,0 +1,346 @@ +<?php + require_once('libdb.php'); + +function libglue_sess_db($dbtype = 'local') +{ + if($dbtype === 'sso') + { + $dbh = libglue_param(libglue_param('sso_dbh_name')); + if(is_resource($dbh)) return $dbh; + $dbh_name = libglue_param('sso_dbh_name'); + $host = libglue_param('sso_host'); + $db = libglue_param('sso_db'); + $user = libglue_param('sso_username'); + $pass = libglue_param('sso_pass'); + $name = libglue_param('sso_dbh_name'); + } + elseif($dbtype === 'local') + { + $dbh = libglue_param(libglue_param('local_dbh_name')); + if(is_resource($dbh)) return $dbh; + $dbh_name = libglue_param('local_dbh_name'); + $host = libglue_param('local_host'); + $db = libglue_param('local_db'); + $user = libglue_param('local_username'); + $pass = libglue_param('local_pass'); + $name = libglue_param('local_dhb_name'); + } + $dbh = db_connect($host,$user,$pass); + db_select($db); + libglue_param(array($dbh_name=>$dbh)); + + if(is_resource($dbh)) return $dbh; + else die("Could not connect to $dbtype database for session management"); +} + +/* This function is public */ +function check_session($id=null) +{ + if(is_null($id)) + { + //From Karl Vollmer, vollmerk@net.orst.edu: + // naming the session and starting it is sufficient + // to retrieve the cookie + $name = libglue_param('sess_name'); + if(!empty($name)) session_name($name); + session_start(); + $id = strip_tags($_COOKIE[$name]); + } + + // Now what we have a session id, let's verify it: + if(libglue_sso_mode()) + { + // if sso mode, we must have a valid sso session already + $sso_sess = libglue_sso_check($id); + if(!is_null($sso_sess)) + { + // if sso is valid, it's okay to create a new local session + if($local_sess = libglue_local_check($id)) + { + return true; + } + else + { + libglue_local_create($id, + $sso_sess[libglue_param('sso_username_col')], + 'sso', + $sso_sess[libglue_param('sso_expire_col')]); + return true; + } + } + else + // libglue_sso_check failed + { + libglue_sess_destroy($id); + return false; + } + } + else + { + //if not in sso mode, there must be a local session + if($local_sess = libglue_local_check($id)) + { + return true; + } + else + { + //you're gone buddy + libglue_sess_destroy($id); + return false; + } + } +} + +// private function, don't ever use this: +function libglue_sso_mode() +{ + $auth_methods = libglue_param('auth_methods'); + if(!is_array($auth_methods)) $auth_methods = array($auth_methods); + return (in_array('sso',$auth_methods))?true:false; +} + +function libglue_sso_check($sess_id) +{ + // Read the sso info from the config file: + $sso_table = libglue_param('sso_table'); + $sso_sid = libglue_param('sso_sessid_col'); + $sso_expire_col = libglue_param('sso_expire_col'); + $sso_length = libglue_param('sso_length'); + $sso_username_col = libglue_param('sso_username_col'); + + $sso_dbh = libglue_sess_db('sso'); + $sql = "SELECT * FROM $sso_table WHERE $sso_sid='$sess_id' AND $sso_expire_col > UNIX_TIMESTAMP()"; + $db_result = db_query($sql, $sso_dbh); + if(is_resource($db_result)) $sso_session = db_fetch($db_result); + else $sso_session = null; + + $retval = (is_array($sso_session))?$sso_session:null; + return $retval; +} + +function libglue_local_check($sess_id) +{ + static $retval = -1; + if($retval != -1) return $retval; + + $local_table = libglue_param('local_table'); + $local_sid = libglue_param('local_sid'); + $local_expirecol = libglue_param('local_expirecol'); + $local_length = libglue_param('local_length'); + $local_usercol = libglue_param('local_usercol'); + $local_datacol = libglue_param('local_datacol'); + $local_typecol = libglue_param('local_typecol'); + + $local_dbh = libglue_sess_db('local'); + $sql = "SELECT $local_datacol FROM $local_table WHERE $local_sid='$sess_id' AND $local_expirecol > UNIX_TIMESTAMP()"; + $db_result = db_query($sql, $local_dbh); + if(is_resource($db_result)) $session = db_fetch($db_result); + else $session = null; + + if(is_array($session)) + { + $retval = $session[$local_datacol]; + } + else $retval = null; + return $retval; +} + +function libglue_local_create($sess_id, $username, $type, $expire) +{ + if($type === "sso" || $type === "ldap") + $userdata = get_ldap_user($username); + else if($type === "mysql") + $userdata = get_mysql_user($username); + + $data = array(libglue_param('user_data_name')=>$userdata); + + // It seems we have to set $_SESSION manually, or it gets blasted + // by php's session write handler + $_SESSION = $data; + $db_data = serialize($data); + $local_dbh = libglue_sess_db('local'); + + // Local parameters we need: + $local_table = libglue_param('local_table'); + $local_sid = libglue_param('local_sid'); + $local_usercol = libglue_param('local_usercol'); + $local_datacol = libglue_param('local_datacol'); + $local_expirecol = libglue_param('local_expirecol'); + $local_typecol = libglue_param('local_typecol'); + + // session data will be saved when the script terminates, + // but not the rest of this fancy info + $sql= "INSERT INTO $local_table ". + " ($local_sid,$local_usercol,$local_datacol,$local_expirecol,$local_typecol)". + " VALUES ('$sess_id','$username','$db_data','$expire','$type')"; + $db_result = db_query($sql, $local_dbh); + if(!$db_result) die("Died trying to create local session: <pre><br>$sql</pre>"); +} + +function sess_open() +{ + if(libglue_sso_mode()) + { + if(!is_resource(libglue_sess_db('sso'))) + { + die("<!-- Unable to connect to the SSO server in order to " . + "use the session database. Perhaps the database is not ". + "running, or perhaps the admin needs to change a few variables in ". + "modules/include/global_settings in order to point to the correct ". + "database.-->\n"); + return false; + } + } + + if(!is_resource(libglue_sess_db('local'))) + { + die("<!-- Unable to connect to local server in order to " . + "use the session database. Perhaps the database is not ". + "running, or perhaps the admin needs to change a few variables in ". + "the config file in order to point to the correct ". + "database.-->\n"); + return false; + } + return true; +} + +function sess_close(){ return true; } + +function sess_write($sess_id, $sess_data) +{ + $local_dbh = libglue_sess_db('local'); + $local_datacol = libglue_param('local_datacol'); + $local_table = libglue_param('local_table'); + $local_sid = libglue_param('local_sid'); + + $auth_methods = libglue_param('auth_methods'); + $local_expire = libglue_param('local_expirecol'); + $local_length = libglue_param('local_length'); + $time = $local_length+time(); + + // If not using sso, we now need to update the expire time + $local_expire = libglue_param('local_expirecol'); + $local_length = libglue_param('local_length'); + $time = $local_length+time(); + $sql = "UPDATE $local_table SET $local_datacol='$sess_data',$local_expire='$time'". + " WHERE $local_sid = '$sess_id'"; + db_query($sql, $local_dbh); + + if(libglue_sso_mode()) + { + $sso_table = libglue_param('sso_table'); + $sso_expire_col = libglue_param('sso_expire_col'); + $sso_sess_length = libglue_param('sso_length_col'); + $sso_sess_id = libglue_param('sso_sessid_col'); + $time = time(); + $sql = "UPDATE $sso_table SET $sso_expire_col = $sso_sess_length + UNIX_TIMESTAMP() WHERE $sso_sess_id = '$sess_id'"; + $sso_dbh = libglue_sess_db('sso'); + db_query($sql, $sso_dbh); + } + return true; +} + +// +// This function is called with random frequency +// to remove expired session data +// +function sess_gc($maxlifetime) +{ + if(libglue_sso_mode()) + { + //Delete old sessions from SSO + // We do 'where length' so we don't accidentally blast + // another app's sessions + $sso_expirecol = libglue_param('sso_expire_col'); + $sso_table = libglue_param('sso_table'); + $sso_length = libglue_param('sso_length_col'); + $local_length = libglue_param('local_length'); + + $sso_dbh = libglue_sess_db('sso'); + $time = time(); + $sql = "DELETE FROM $sso_table WHERE $sso_expirecol < $time". + " AND $sso_length = '$local_length'"; + $result = db_query($sql, $sso_dbh); + } + $local_expire = libglue_param('local_expire'); + $local_table = libglue_param('local_table'); + $time = time(); + $local_dbh = libglue_sess_db('local'); + $sql = "DELETE FROM $local_table WHERE $local_expire < $time"; + $result = db_query($sql, $local_dbh); + return true; +} + +function libglue_sess_destroy($id=null) +{ + if(is_null($id)) + { + //From Karl Vollmer, vollmerk@net.orst.edu: + // naming the session and starting it is sufficient + // to retrieve the cookie + $name = libglue_param('sess_name'); + if(!empty($name)) session_name($name); + session_start(); + $id = strip_tags($_COOKIE[$name]); + } + if(libglue_sso_mode()) + { + $sso_sid = libglue_param('sso_sessid_col'); + $sso_table = libglue_param('sso_table'); + $sso_dbh = libglue_sess_db('sso'); + $sql = "DELETE FROM $sso_table WHERE $sso_sid = '$id' LIMIT 1"; + $result = db_query($sql, $sso_dbh); + } + $local_sid = libglue_param('local_sid'); + $local_table = libglue_param('local_table'); + + $local_dbh = libglue_sess_db('local'); + $sql = "DELETE FROM $local_table WHERE $local_sid = '$id' LIMIT 1"; + $result = db_query($sql, $local_dbh); + + // It is very important we destroy our current session cookie, + // because if we don't, a person won't be able to log in again + // without closing their browser - SSO doesn't respect + // + // Code from http://php.oregonstate.edu/manual/en/function.session-destroy.php, + // Written by powerlord@spamless.vgmusic.com, 18-Nov-2002 08:41 + // + $cookie = session_get_cookie_params(); + if ((empty($cookie['domain'])) && (empty($cookie['secure'])) ) + { + setcookie(session_name(), '', time()-3600, $cookie['path']); + } elseif (empty($CookieInfo['secure'])) { + setcookie(session_name(), '', time()-3600, $cookie['path'], $cookie['domain']); + } else { + setcookie(session_name(), '', time()-3600, $cookie['path'], $cookie['domain'], $cookie['secure']); + } + // end powerloard + + unset($_SESSION); + unset($_COOKIE[session_name()]); + + return TRUE; +} + +function logout($id=null) +{ + libglue_sess_destroy($id); + $login_page = libglue_param('login_page'); + header("Location: $login_page"); + die(); + return true; //because why not? +} + + +// +// Register all our cool session handling functions +// +session_set_save_handler( + "sess_open", + "sess_close", + "libglue_local_check", + "sess_write", + "libglue_sess_destroy", + "sess_gc"); + +?> diff --git a/locale/base/TRANSLATIONS b/locale/base/TRANSLATIONS new file mode 100644 index 00000000..5191bba8 --- /dev/null +++ b/locale/base/TRANSLATIONS @@ -0,0 +1,124 @@ +------------------------------------------------------------------------------- +--------- TRANSLATIONS - Ampache Translation Guide ----------- +------------------------------------------------------------------------------- + +Contents: + + 1. Introduction + a) Getting the Necessary tools + b) Quick Reference + 2. Creating a New Translation + a) Translating + b) Creating a MO file + 3. Updating an Existing Translation + a) Merging existing file + b) Generating the MO file + 4. Questions? + +Introduction: + + Ampache uses gettext to handle translating between different languages if + you are interested in translating Ampache into a new language or updating + an existing translations simply follow the directions provided below. + + A) Getting the Necessary Tools + + Before attempting to translate Ampache into a new language we recommend + contacting translations@ampache.org to make sure that nobody else is + already working on a translation. Once you are ready to start your + translation you will need to get a few tools + + - Gettext + - xgettext (Generates PO files) + - msgmerge (Merges old and new PO files) + - msgfmt (Generates the MO file from a PO file) + + B) Quick Reference + + Below are listed all of the commands you may have to run when working on + a translation + + # Gather All info + Run locale/base/gather-messages.sh + + # Create New po file + xgettext -n *.php -L PHP -o /tmp/mesasge.po + + # Merge with existing po file + xgettext -o /tmp/messages.po -L PHP -n *.inc -j + + # Combine Old & New po files + msgmerge old.po messages.po --output-file=new.po + + # Generate MO file for use by gettext + msgfmt messages.po -o /tmp/messages.mo + +Creating a New Translation: + + A) Translating + + I do my best to keep an up to date po file in /locale/base feel free to + use this file rather than attempting to generate your own. If you would + like to gather a new PO file simply run /locale/base/gather-messages.sh + (Linux only) + + Once you have an up to date PO file you will need to figure out the + country code for the language you are translating into. There are many + lists on the web. + http://www.gnu.org/software/gettext/manual/html_chapter/gettext_16.html + + Create the following directory structure and put your po file in the + LC_MESSAGES directory + /locale/<COUNTRY CODE>/LC_MESSAGES/ + + Start Translating! + + C) Creating a MO File + + Once you have finished translating the PO file you need to convert it into + a MO file in order for Gettext to be able to use it. Simply run the command + listed below. + + msgfmt <DIR>messages.po -o <DIR>/messages.mo + + Unfortunately currently Ampache doesn't automatically detect new languages + and thus you have to edit the code directly in order for it to pickup your + new language. Find /lib/preferences.php and then find "case 'lang':" under + the "create_preference_input" function and add a line for your own + language. For example to add en_US support add the following line + + echo "\t<option value=\"en_US\" $en_US_lang>" . _("English") . "</option>\n"; + + Make sure that it comes after the <select> statement. This will be fixed + for future releases... Sorry :S + +Updating an Existing Translation: + + A) Merging existing file + + If you are updating an existing PO file you will need to merge the new + file with the old so that you don't have to do everything over again. + simply run the following command. + + msgmerge old.po messages.po --output-file=new.po + + Once you have created the new PO file translate it as you normally would. + + B) Generating the MO file + + Because this is an existing translation you do not have to modify ampache + code at all simply run. + + msgfmt <DIR>messages.po -o <DIR>messages.mo + + And then check it in the web interface! + +Questions: + + If you have any questions or are unable to get gettext to work for you please + feel free to contact us at translations@ampache.org. Thanks! + + +Karl Vollmer + http://www.ampache.org + dev@ampache.org diff --git a/locale/base/gather-messages.sh b/locale/base/gather-messages.sh new file mode 100755 index 00000000..c06aa992 --- /dev/null +++ b/locale/base/gather-messages.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +find ../../ -name *.php > /tmp/filelist +find ../../ -name *.inc >> /tmp/filelist + +xgettext -f /tmp/filelist -L PHP -o /tmp/messages.po diff --git a/locale/base/messages.po b/locale/base/messages.po new file mode 100644 index 00000000..d10ce07c --- /dev/null +++ b/locale/base/messages.po @@ -0,0 +1,1801 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-04-17 17:14-0700\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../../docs/admin/catalog.php:54 ../../templates/catalog.inc:66 +msgid "Add to Catalog(s)" +msgstr "" + +#: ../../docs/admin/catalog.php:65 ../../templates/catalog.inc:67 +msgid "Add to all Catalogs" +msgstr "" + +#: ../../docs/admin/catalog.php:75 ../../templates/catalog.inc:73 +msgid "Update Catalog(s)" +msgstr "" + +#: ../../docs/admin/catalog.php:86 ../../templates/catalog.inc:74 +msgid "Update All Catalogs" +msgstr "" + +#: ../../docs/admin/catalog.php:118 ../../templates/catalog.inc:80 +msgid "Clean Catalog(s)" +msgstr "" + +#: ../../docs/admin/catalog.php:148 ../../templates/catalog.inc:81 +msgid "Clean All Catalogs" +msgstr "" + +#: ../../docs/admin/catalog.php:196 +msgid "Now Playing Cleared" +msgstr "" + +#: ../../docs/admin/catalog.php:196 +msgid "All now playing data has been cleared" +msgstr "" + +#: ../../docs/admin/catalog.php:201 +msgid "Do you really want to clear your catalog?" +msgstr "" + +#: ../../docs/admin/catalog.php:208 +msgid "Do you really want to clear the statistics for this catalog?" +msgstr "" + +#: ../../docs/admin/catalog.php:226 +msgid "Do you really want to delete this catalog?" +msgstr "" + +#: ../../docs/admin/users.php:76 ../../docs/admin/users.php:123 +msgid "Error Username Required" +msgstr "" + +#: ../../docs/admin/users.php:79 ../../docs/admin/users.php:120 +msgid "Error Passwords don't match" +msgstr "" + +#: ../../docs/admin/users.php:137 +msgid "Are you sure you want to permanently delete" +msgstr "" + +#: ../../docs/admin/users.php:144 +#: ../../templates/show_confirm_action.inc.php:29 +msgid "No" +msgstr "" + +#: ../../docs/admin/users.php:146 +msgid "User Deleted" +msgstr "" + +#: ../../docs/admin/users.php:149 +msgid "Delete Error" +msgstr "" + +#: ../../docs/admin/users.php:149 +msgid "Unable to delete last Admin User" +msgstr "" + +#: ../../docs/admin/access.php:43 +msgid "Do you really want to delete this Access Record?" +msgstr "" + +#: ../../docs/admin/access.php:51 +msgid "Entry Deleted" +msgstr "" + +#: ../../docs/admin/access.php:51 +msgid "Your Access List Entry has been removed" +msgstr "" + +#: ../../docs/admin/access.php:61 +msgid "Entry Added" +msgstr "" + +#: ../../docs/admin/access.php:61 +msgid "Your new Access List Entry has been created" +msgstr "" + +#: ../../docs/admin/song.php:70 +msgid "Songs Disabled" +msgstr "" + +#: ../../docs/admin/song.php:70 +msgid "The requested song(s) have been disabled" +msgstr "" + +#: ../../docs/admin/song.php:80 +msgid "Songs Enabled" +msgstr "" + +#: ../../docs/admin/song.php:80 +msgid "The requested song(s) have been enabled" +msgstr "" + +#: ../../docs/play/index.php:46 +msgid "Session Expired: please log in again at" +msgstr "" + +#: ../../docs/artists.php:47 +msgid "All songs by" +msgstr "" + +#: ../../docs/artists.php:56 ../../docs/albums.php:103 +msgid "Starting Update from Tags" +msgstr "" + +#: ../../docs/artists.php:61 ../../docs/albums.php:108 +msgid "Update From Tags Compleate" +msgstr "" + +#: ../../docs/artists.php:62 ../../docs/albums.php:109 +#: ../../modules/class/catalog.php:615 +msgid "Return" +msgstr "" + +#: ../../docs/artists.php:73 ../../docs/artists.php:82 +#: ../../docs/artists.php:94 ../../docs/artists.php:111 +msgid "<u>S</u>how artists starting with" +msgstr "" + +#: ../../docs/amp-mpd.php:32 +msgid "Error Connecting" +msgstr "" + +#: ../../docs/playlist.php:71 ../../templates/show_songs.inc:151 +#: ../../templates/show_artist.inc:95 +msgid "Play Selected" +msgstr "" + +#: ../../docs/playlist.php:89 ../../templates/show_songs.inc:152 +msgid "Flag Selected" +msgstr "" + +#: ../../docs/playlist.php:95 ../../templates/show_songs.inc:153 +msgid "Edit Selected" +msgstr "" + +#: ../../docs/playlist.php:125 ../../modules/lib.php:1007 +#: ../../templates/show_songs.inc:169 ../../templates/show_users.inc:51 +#: ../../templates/show_artist.inc:103 +msgid "Edit" +msgstr "" + +#: ../../docs/playlist.php:128 ../../modules/lib.php:1016 +#: ../../templates/show_localplay.inc:41 ../../templates/show_artists.inc:54 +#: ../../templates/show_albums.inc:57 ../../templates/show_mpdplay.inc:45 +#: ../../templates/show_artist.inc:79 +msgid "Play" +msgstr "" + +#: ../../docs/playlist.php:140 +msgid "New Playlist" +msgstr "" + +#: ../../docs/playlist.php:198 +msgid "Playlist updated." +msgstr "" + +#: ../../docs/playlist.php:305 +msgid "No songs in this playlist." +msgstr "" + +#: ../../docs/localplay.php:79 +msgid "Unknown action requested" +msgstr "" + +#: ../../docs/index.php:39 +msgid "Welcome to" +msgstr "" + +#: ../../docs/index.php:41 +msgid "you are currently logged in as" +msgstr "" + +#: ../../docs/index.php:72 +msgid "Most Popular Songs" +msgstr "" + +#: ../../docs/index.php:79 +msgid "Most Popular Artists" +msgstr "" + +#: ../../docs/index.php:91 +msgid "Newest Album Additions" +msgstr "" + +#: ../../docs/index.php:98 +msgid "Newest Artist Additions" +msgstr "" + +#: ../../docs/flag.php:35 +msgid "Flagging song completed." +msgstr "" + +#: ../../docs/albums.php:43 +msgid "Album Art Cleared" +msgstr "" + +#: ../../docs/albums.php:43 +msgid "Album Art information has been removed form the database" +msgstr "" + +#: ../../docs/albums.php:75 +msgid "Album Art Located" +msgstr "" + +#: ../../docs/albums.php:75 +msgid "" +"Album Art information has been located in Amazon. If incorrect, click " +"\"Reset Album Art\" below to remove the artwork." +msgstr "" + +#: ../../docs/albums.php:83 ../../docs/albums.php:93 +msgid "Get Art" +msgstr "" + +#: ../../docs/albums.php:87 +msgid "Album Art Not Located" +msgstr "" + +#: ../../docs/albums.php:87 +msgid "" +"Album Art could not be located at this time. This may be due to Amazon being " +"busy, or the album not being present in their collection." +msgstr "" + +#: ../../docs/albums.php:123 ../../docs/albums.php:138 +msgid "All Albums" +msgstr "" + +#: ../../docs/albums.php:124 ../../docs/albums.php:131 +msgid "<u>S</u>how all albums" +msgstr "" + +#: ../../docs/albums.php:130 +msgid "Albums with no artwork" +msgstr "" + +#: ../../docs/albums.php:139 ../../docs/albums.php:146 +#: ../../docs/albums.php:151 +msgid "<u>S</u>how only albums starting with" +msgstr "" + +#: ../../docs/albums.php:145 +msgid "Select a starting letter or Show all" +msgstr "" + +#: ../../docs/upload.php:124 +msgid "The uploaded file exceeds the upload_max_filesize directive in php.ini" +msgstr "" + +#: ../../docs/upload.php:127 +msgid "" +"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in " +"the HTML form." +msgstr "" + +#: ../../docs/upload.php:130 +msgid "The uploaded file was only partially uploaded." +msgstr "" + +#: ../../docs/upload.php:133 +msgid "No file was uploaded." +msgstr "" + +#: ../../docs/upload.php:136 +msgid "An Unknown Error has occured." +msgstr "" + +#: ../../docs/upload.php:157 +msgid "Successfully-Quarantined" +msgstr "" + +#: ../../docs/upload.php:167 +msgid "Successfully-Cataloged" +msgstr "" + +#: ../../docs/upload.php:229 ../../templates/show_songs.inc:42 +#: ../../templates/show_artists.inc:43 ../../templates/show_artists.inc:67 +#: ../../templates/show_albums.inc:45 ../../templates/show_albums.inc:73 +#: ../../templates/show_access_list.inc:51 ../../templates/show_artist.inc:56 +msgid "Action" +msgstr "" + +#: ../../docs/upload.php:230 ../../templates/flag.inc:58 +#: ../../templates/list_flagged.inc:41 +msgid "Song" +msgstr "" + +#: ../../docs/upload.php:231 ../../modules/class/song.php:275 +#: ../../templates/show_songs.inc:34 ../../templates/show_artists.inc:39 +#: ../../templates/show_artists.inc:62 ../../templates/show_albums.inc:40 +#: ../../templates/show_albums.inc:70 +msgid "Artist" +msgstr "" + +#: ../../docs/upload.php:232 ../../modules/class/song.php:280 +#: ../../templates/show_songs.inc:35 ../../templates/show_albums.inc:38 +#: ../../templates/show_albums.inc:68 +msgid "Album" +msgstr "" + +#: ../../docs/upload.php:233 ../../modules/class/song.php:293 +#: ../../templates/show_songs.inc:40 +msgid "Genre" +msgstr "" + +#: ../../docs/upload.php:234 ../../modules/class/song.php:262 +#: ../../templates/show_songs.inc:37 +msgid "Time" +msgstr "" + +#: ../../docs/upload.php:235 ../../modules/class/song.php:250 +#: ../../templates/show_songs.inc:39 +msgid "Bitrate" +msgstr "" + +#: ../../docs/upload.php:236 ../../templates/show_songs.inc:38 +msgid "Size" +msgstr "" + +#: ../../docs/upload.php:237 +msgid "Filename" +msgstr "" + +#: ../../docs/upload.php:238 +msgid "User" +msgstr "" + +#: ../../docs/upload.php:239 +msgid "Date" +msgstr "" + +#: ../../docs/upload.php:267 +msgid "Unknown" +msgstr "" + +#: ../../docs/upload.php:289 +msgid "Add" +msgstr "" + +#: ../../docs/upload.php:290 ../../modules/lib.php:1008 +#: ../../templates/catalog.inc:60 ../../templates/show_users.inc:57 +msgid "Delete" +msgstr "" + +#: ../../docs/upload.php:294 +msgid "Quarantined" +msgstr "" + +#: ../../templates/show_user_registration.inc.php:28 +#: ../../templates/show_install_account.inc.php:60 +#: ../../templates/userform.inc:41 ../../templates/show_users.inc:39 +msgid "Username" +msgstr "" + +#: ../../templates/show_user_registration.inc.php:36 +#: ../../templates/userform.inc:49 +msgid "Full Name" +msgstr "" + +#: ../../templates/show_user_registration.inc.php:44 +#: ../../templates/show_user.inc.php:40 ../../templates/userform.inc:56 +msgid "E-mail" +msgstr "" + +#: ../../templates/show_user_registration.inc.php:52 +#: ../../templates/show_install_account.inc.php:64 +#: ../../templates/userform.inc:64 ../../templates/show_login_form.inc:44 +msgid "Password" +msgstr "" + +#: ../../templates/show_user_registration.inc.php:60 +#: ../../templates/show_user.inc.php:75 ../../templates/userform.inc:73 +msgid "Confirm Password" +msgstr "" + +#: ../../templates/show_user_registration.inc.php:69 +msgid "Register User" +msgstr "" + +#: ../../templates/show_user.inc.php:31 +#: ../../templates/customize_catalog.inc:29 +#: ../../templates/show_add_access.inc:40 +#: ../../templates/show_access_list.inc:47 +msgid "Name" +msgstr "" + +#: ../../templates/show_user.inc.php:48 +msgid "View Limit" +msgstr "" + +#: ../../templates/show_user.inc.php:56 +msgid "Update Profile" +msgstr "" + +#: ../../templates/show_user.inc.php:67 +msgid "Enter password" +msgstr "" + +#: ../../templates/show_user.inc.php:83 +msgid "Change Password" +msgstr "" + +#: ../../templates/show_user.inc.php:91 +msgid "Clear Stats" +msgstr "" + +#: ../../templates/show_install_account.inc.php:35 +#: ../../templates/show_install_config.inc:35 +#: ../../templates/show_install.inc:34 +msgid "Ampache Installation" +msgstr "" + +#: ../../templates/show_install_account.inc.php:37 +#: ../../templates/show_install_config.inc:37 +#: ../../templates/show_install.inc:36 +msgid "" +"This Page handles the installation of the ampache database and the creation " +"of the ampache.cfg file. Before you continue please make sure that you have " +"the following pre-requisits" +msgstr "" + +#: ../../templates/show_install_account.inc.php:40 +#: ../../templates/show_install_config.inc:40 +#: ../../templates/show_install.inc:39 +msgid "" +"A MySQL Server with a username and password that can create/modify databases" +msgstr "" + +#: ../../templates/show_install_account.inc.php:41 +#: ../../templates/show_install_config.inc:41 +msgid "" +"Your webserver configured so that your config directory is not visable to " +"the web" +msgstr "" + +#: ../../templates/show_install_account.inc.php:42 +#: ../../templates/show_install_config.inc:42 +#: ../../templates/show_install.inc:41 +msgid "" +"Your webserver has read access to the /sql/ampache.sql file and the /config/" +"ampache.cfg.dist file" +msgstr "" + +#: ../../templates/show_install_account.inc.php:44 +#: ../../templates/show_install_config.inc:44 +#: ../../templates/show_install.inc:43 +msgid "" +"Once you have ensured that you have the above requirements please fill out " +"the information below. You will only be asked for the required config " +"values. If you would like to make changes to your ampache install at a later " +"date simply edit /config/ampache.cfg" +msgstr "" + +#: ../../templates/show_install_account.inc.php:49 +#: ../../templates/show_install_config.inc:49 +#: ../../templates/show_install.inc:47 +msgid "Step 1 - Creating and Inserting the Ampache Database" +msgstr "" + +#: ../../templates/show_install_account.inc.php:50 +#: ../../templates/show_install_config.inc:50 +#: ../../templates/show_install.inc:51 +msgid "Step 2 - Creating the Ampache.cfg file" +msgstr "" + +#: ../../templates/show_install_account.inc.php:51 +#: ../../templates/show_install_config.inc:54 +#: ../../templates/show_install.inc:52 +msgid "Step 3 - Setup Initial Account" +msgstr "" + +#: ../../templates/show_install_account.inc.php:53 +msgid "" +"This step creates your initial Ampache admin account. Once your admin " +"account has been created you will be directed to the login page" +msgstr "" + +#: ../../templates/show_install_account.inc.php:69 +msgid "Create Account" +msgstr "" + +#: ../../templates/show_confirm_action.inc.php:28 +msgid "Yes" +msgstr "" + +#: ../../templates/show_confirmation.inc.php:30 +msgid "Continue" +msgstr "" + +#: ../../lib/preferences.php:199 +msgid "Enable" +msgstr "" + +#: ../../lib/preferences.php:200 ../../templates/show_users.inc:63 +msgid "Disable" +msgstr "" + +#: ../../lib/preferences.php:210 ../../templates/add_catalog.inc:60 +msgid "Local" +msgstr "" + +#: ../../lib/preferences.php:211 +msgid "Stream" +msgstr "" + +#: ../../lib/preferences.php:212 +msgid "IceCast" +msgstr "" + +#: ../../lib/preferences.php:213 +msgid "Downsample" +msgstr "" + +#: ../../lib/preferences.php:214 +msgid "Music Player Daemon" +msgstr "" + +#: ../../lib/preferences.php:221 +msgid "M3U" +msgstr "" + +#: ../../lib/preferences.php:222 +msgid "Simple M3U" +msgstr "" + +#: ../../lib/preferences.php:223 +msgid "PLS" +msgstr "" + +#: ../../lib/preferences.php:224 +msgid "Asx" +msgstr "" + +#: ../../lib/preferences.php:231 +msgid "English" +msgstr "" + +#: ../../lib/preferences.php:232 +msgid "German" +msgstr "" + +#: ../../lib/preferences.php:233 +msgid "French" +msgstr "" + +#: ../../lib/search.php:52 ../../lib/search.php:68 ../../lib/search.php:84 +#: ../../lib/search.php:100 ../../lib/search.php:116 ../../lib/search.php:133 +#: ../../lib/search.php:145 ../../lib/search.php:161 ../../lib/search.php:177 +msgid "No Results Found" +msgstr "" + +#: ../../lib/ui.php:180 ../../templates/show_songs.inc:166 +#: ../../templates/show_artist.inc:100 +msgid "Playlist" +msgstr "" + +#: ../../lib/ui.php:180 +msgid "New" +msgstr "" + +#: ../../lib/ui.php:181 +msgid "View All" +msgstr "" + +#: ../../lib/ui.php:305 +msgid "Show w/o art" +msgstr "" + +#: ../../lib/ui.php:307 +msgid "Show all" +msgstr "" + +#: ../../lib/mpd.php:31 ../../modules/class/catalog.php:876 +msgid "Error" +msgstr "" + +#: ../../lib/mpd.php:31 +msgid "Could not add" +msgstr "" + +#: ../../lib/Browser.php:867 +msgid "file" +msgstr "" + +#: ../../lib/Browser.php:871 +msgid "File uploads not supported." +msgstr "" + +#: ../../lib/Browser.php:889 +msgid "No file uploaded" +msgstr "" + +#: ../../lib/Browser.php:896 +#, php-format +msgid "There was a problem with the file upload: No %s was uploaded." +msgstr "" + +#: ../../lib/Browser.php:901 +#, php-format +msgid "" +"There was a problem with the file upload: The %s was larger than the maximum " +"allowed size (%d bytes)." +msgstr "" + +#: ../../lib/Browser.php:903 +#, php-format +msgid "" +"There was a problem with the file upload: The %s was only partially uploaded." +msgstr "" + +#: ../../modules/class/catalog.php:267 ../../modules/class/catalog.php:527 +#: ../../modules/class/album.php:241 +msgid "Error: Unable to open" +msgstr "" + +#: ../../modules/class/catalog.php:289 +msgid "Error: Unable to change to directory" +msgstr "" + +#: ../../modules/class/catalog.php:312 +msgid "Error: Unable to get filesize for" +msgstr "" + +#: ../../modules/class/catalog.php:332 +msgid "Added" +msgstr "" + +#: ../../modules/class/catalog.php:342 +msgid "is not readable by ampache" +msgstr "" + +#: ../../modules/class/catalog.php:402 +msgid "Found in ID3" +msgstr "" + +#: ../../modules/class/catalog.php:406 +msgid "Found on Amazon" +msgstr "" + +#: ../../modules/class/catalog.php:410 +msgid "Found in Folder" +msgstr "" + +#: ../../modules/class/catalog.php:414 +msgid "Found" +msgstr "" + +#: ../../modules/class/catalog.php:417 +msgid "Not Found" +msgstr "" + +#: ../../modules/class/catalog.php:425 +msgid "Searched" +msgstr "" + +#: ../../modules/class/catalog.php:578 +msgid "Starting Dump Album Art" +msgstr "" + +#: ../../modules/class/catalog.php:598 +msgid "Written" +msgstr "" + +#: ../../modules/class/catalog.php:607 +msgid "Error unable to open file for writting" +msgstr "" + +#: ../../modules/class/catalog.php:614 +msgid "Album Art Dump Complete" +msgstr "" + +#: ../../modules/class/catalog.php:681 +msgid "Starting Catalog Build" +msgstr "" + +#: ../../modules/class/catalog.php:686 +msgid "Running Remote Sync" +msgstr "" + +#: ../../modules/class/catalog.php:696 ../../modules/class/catalog.php:843 +msgid "Starting Album Art Search" +msgstr "" + +#: ../../modules/class/catalog.php:706 +msgid "Catalog Finished" +msgstr "" + +#: ../../modules/class/catalog.php:706 ../../modules/class/catalog.php:862 +msgid "Total Time" +msgstr "" + +#: ../../modules/class/catalog.php:706 ../../modules/class/catalog.php:863 +msgid "Total Songs" +msgstr "" + +#: ../../modules/class/catalog.php:707 ../../modules/class/catalog.php:863 +msgid "Songs Per Seconds" +msgstr "" + +#: ../../modules/class/catalog.php:741 +msgid "Updated" +msgstr "" + +#: ../../modules/class/catalog.php:748 +msgid "No Update Needed" +msgstr "" + +#: ../../modules/class/catalog.php:823 +msgid "Starting New Song Search on" +msgstr "" + +#: ../../modules/class/catalog.php:823 +msgid "catalog" +msgstr "" + +#: ../../modules/class/catalog.php:827 +msgid "Running Remote Update" +msgstr "" + +#: ../../modules/class/catalog.php:862 +msgid "Catalog Update Finished" +msgstr "" + +#: ../../modules/class/catalog.php:876 +msgid "Unable to load XMLRPC library, make sure XML-RPC is enabled" +msgstr "" + +#: ../../modules/class/catalog.php:908 ../../modules/class/catalog.php:923 +msgid "Error connecting to" +msgstr "" + +#: ../../modules/class/catalog.php:908 ../../modules/class/catalog.php:923 +msgid "Code" +msgstr "" + +#: ../../modules/class/catalog.php:908 ../../modules/class/catalog.php:923 +msgid "Reason" +msgstr "" + +#: ../../modules/class/catalog.php:928 +msgid "Completed updating remote catalog(s)" +msgstr "" + +#: ../../modules/class/catalog.php:1016 +msgid "Checking" +msgstr "" + +#: ../../modules/class/catalog.php:1073 +msgid "Catalog Clean Done" +msgstr "" + +#: ../../modules/class/catalog.php:1073 +msgid "files removed" +msgstr "" + +#: ../../modules/class/catalog.php:1313 +msgid "Updating the" +msgstr "" + +#: ../../modules/class/catalog.php:1313 +#: ../../templates/show_admin_index.inc:34 ../../templates/admin_menu.inc:35 +msgid "Catalog" +msgstr "" + +#: ../../modules/class/catalog.php:1314 +msgid "songs found checking tag information." +msgstr "" + +#: ../../modules/class/stream.php:192 +msgid "Opened for writting" +msgstr "" + +#: ../../modules/class/stream.php:197 +msgid "Error, cannot write" +msgstr "" + +#: ../../modules/class/stream.php:208 +msgid "Error, cannot write song in file" +msgstr "" + +#: ../../modules/class/stream.php:214 +msgid "Closed after write" +msgstr "" + +#: ../../modules/class/album.php:126 +msgid "Various" +msgstr "" + +#: ../../modules/class/song.php:246 +msgid "Title" +msgstr "" + +#: ../../modules/class/song.php:246 ../../modules/class/song.php:250 +#: ../../modules/class/song.php:254 ../../modules/class/song.php:258 +#: ../../modules/class/song.php:262 ../../modules/class/song.php:266 +#: ../../modules/class/song.php:270 ../../modules/class/song.php:275 +#: ../../modules/class/song.php:280 ../../modules/class/song.php:284 +#: ../../modules/class/song.php:288 ../../modules/class/song.php:293 +msgid "updated to" +msgstr "" + +#: ../../modules/class/song.php:254 +msgid "Rate" +msgstr "" + +#: ../../modules/class/song.php:258 +msgid "Mode" +msgstr "" + +#: ../../modules/class/song.php:266 ../../templates/show_songs.inc:32 +#: ../../templates/show_songs.inc:36 +msgid "Track" +msgstr "" + +#: ../../modules/class/song.php:270 +msgid "Filesize" +msgstr "" + +#: ../../modules/class/song.php:284 ../../templates/show_albums.inc:43 +#: ../../templates/show_albums.inc:72 +msgid "Year" +msgstr "" + +#: ../../modules/class/song.php:288 ../../templates/flag.inc:66 +#: ../../templates/list_flagged.inc:46 +msgid "Comment" +msgstr "" + +#: ../../modules/lib.php:51 +msgid "day" +msgstr "" + +#: ../../modules/lib.php:54 +msgid "days" +msgstr "" + +#: ../../modules/lib.php:58 +msgid "hour" +msgstr "" + +#: ../../modules/lib.php:61 +msgid "hours" +msgstr "" + +#: ../../modules/lib.php:78 +msgid "Catalog Statistics" +msgstr "" + +#: ../../modules/lib.php:81 +msgid "Total Users" +msgstr "" + +#: ../../modules/lib.php:85 +msgid "Connected Users" +msgstr "" + +#: ../../modules/lib.php:89 ../../templates/show_artists.inc:42 +#: ../../templates/show_artists.inc:65 ../../templates/menu.inc:32 +msgid "Albums" +msgstr "" + +#: ../../modules/lib.php:93 ../../templates/menu.inc:33 +msgid "Artists" +msgstr "" + +#: ../../modules/lib.php:97 ../../templates/show_artists.inc:41 +#: ../../templates/show_artists.inc:64 ../../templates/show_albums.inc:41 +#: ../../templates/show_albums.inc:71 +msgid "Songs" +msgstr "" + +#: ../../modules/lib.php:101 +msgid "Catalog Size" +msgstr "" + +#: ../../modules/lib.php:105 +msgid "Catalog Time" +msgstr "" + +#: ../../modules/lib.php:159 +msgid "Play Random Selection" +msgstr "" + +#: ../../modules/lib.php:166 +msgid "Item count" +msgstr "" + +#: ../../modules/lib.php:178 ../../templates/show_artists.inc:55 +#: ../../templates/show_albums.inc:58 +msgid "All" +msgstr "" + +#: ../../modules/lib.php:180 +msgid "From genre" +msgstr "" + +#: ../../modules/lib.php:190 +msgid "Favor Unplayed" +msgstr "" + +#: ../../modules/lib.php:191 +msgid "Full Albums" +msgstr "" + +#: ../../modules/lib.php:192 +msgid "Full Artist" +msgstr "" + +#: ../../modules/lib.php:201 +msgid "from catalog" +msgstr "" + +#: ../../modules/lib.php:212 +msgid "Play Random Songs" +msgstr "" + +#: ../../modules/lib.php:921 +msgid "Public" +msgstr "" + +#: ../../modules/lib.php:922 +msgid "Your Private" +msgstr "" + +#: ../../modules/lib.php:923 +msgid "Other Private" +msgstr "" + +#: ../../modules/lib.php:1004 ../../templates/show_songs.inc:168 +#: ../../templates/show_artist.inc:102 +msgid "View" +msgstr "" + +#: ../../modules/lib.php:1022 ../../templates/show_songs.inc:110 +#: ../../templates/show_album.inc:61 ../../templates/show_albums.inc:61 +#: ../../templates/show_artist.inc:81 +msgid "Download" +msgstr "" + +#: ../../modules/lib.php:1033 +msgid "There are no playlists of this type" +msgstr "" + +#: ../../modules/lib.php:1061 +msgid "Create a new playlist" +msgstr "" + +#: ../../modules/admin.php:45 +msgid "Manage Users" +msgstr "" + +#: ../../modules/admin.php:47 +msgid "Add a new user" +msgstr "" + +#: ../../templates/catalog.inc:33 +msgid "" +"Error: ICONV not found, ID3V2 Tags will not import correctly. See <a href=" +"\"http://php.oregonstate.edu/iconv\">Iconv</a> for information on getting " +"ICONV" +msgstr "" + +#: ../../templates/catalog.inc:42 +msgid "Update Catalogs" +msgstr "" + +#: ../../templates/catalog.inc:68 +msgid "Fast Add" +msgstr "" + +#: ../../templates/catalog.inc:75 +msgid "Fast Update" +msgstr "" + +#: ../../templates/catalog.inc:88 +msgid "You don't have any catalogs." +msgstr "" + +#: ../../templates/catalog.inc:97 +msgid "Add a catalog" +msgstr "" + +#: ../../templates/catalog.inc:98 ../../templates/show_admin_index.inc:36 +#: ../../templates/admin_menu.inc:37 +msgid "Access Lists" +msgstr "" + +#: ../../templates/catalog.inc:99 +msgid "Show Duplicate Songs" +msgstr "" + +#: ../../templates/catalog.inc:100 +msgid "Show Disabled Songs" +msgstr "" + +#: ../../templates/catalog.inc:101 +msgid "Clear Catalog Stats" +msgstr "" + +#: ../../templates/catalog.inc:102 +msgid "Clear Now Playing" +msgstr "" + +#: ../../templates/catalog.inc:103 +msgid "Dump Album Art" +msgstr "" + +#: ../../templates/catalog.inc:104 +msgid "View flagged songs" +msgstr "" + +#: ../../templates/catalog.inc:105 +msgid "Catalog Tools" +msgstr "" + +#: ../../templates/flag.inc:43 +msgid "Flag song" +msgstr "" + +#: ../../templates/flag.inc:45 +msgid "" +"Flag the following song as having one of the problems listed below. Site " +"admins will then take the appropriate action for the flagged files." +msgstr "" + +#: ../../templates/flag.inc:62 +msgid "Reason to flag" +msgstr "" + +#: ../../templates/flag.inc:73 +msgid "Flag Song" +msgstr "" + +#: ../../templates/customize_catalog.inc:24 +msgid "Settings for catalog in" +msgstr "" + +#: ../../templates/customize_catalog.inc:32 ../../templates/add_catalog.inc:39 +msgid "Auto-inserted Fields" +msgstr "" + +#: ../../templates/customize_catalog.inc:33 ../../templates/add_catalog.inc:40 +msgid "album name" +msgstr "" + +#: ../../templates/customize_catalog.inc:34 ../../templates/add_catalog.inc:41 +msgid "artist name" +msgstr "" + +#: ../../templates/customize_catalog.inc:35 +msgid "catalog path" +msgstr "" + +#: ../../templates/customize_catalog.inc:36 ../../templates/add_catalog.inc:42 +msgid "id3 comment" +msgstr "" + +#: ../../templates/customize_catalog.inc:37 ../../templates/add_catalog.inc:43 +msgid "genre" +msgstr "" + +#: ../../templates/customize_catalog.inc:38 ../../templates/add_catalog.inc:44 +msgid "track number (padded with leading 0)" +msgstr "" + +#: ../../templates/customize_catalog.inc:39 ../../templates/add_catalog.inc:45 +msgid "song title" +msgstr "" + +#: ../../templates/customize_catalog.inc:40 ../../templates/add_catalog.inc:46 +msgid "year" +msgstr "" + +#: ../../templates/customize_catalog.inc:41 ../../templates/add_catalog.inc:47 +msgid "other" +msgstr "" + +#: ../../templates/customize_catalog.inc:45 +msgid "ID3 set command" +msgstr "" + +#: ../../templates/customize_catalog.inc:51 +msgid "Filename pattern" +msgstr "" + +#: ../../templates/customize_catalog.inc:58 ../../templates/add_catalog.inc:74 +msgid "Folder Pattern" +msgstr "" + +#: ../../templates/customize_catalog.inc:58 ../../templates/add_catalog.inc:74 +msgid "(no leading or ending '/')" +msgstr "" + +#: ../../templates/customize_catalog.inc:69 +msgid "Save Catalog Settings" +msgstr "" + +#: ../../templates/show_test.inc:29 +msgid "Ampache Debug" +msgstr "" + +#: ../../templates/show_test.inc:30 +msgid "" +"You've reached this page because a configuration error has occured. Debug " +"Information below" +msgstr "" + +#: ../../templates/show_test.inc:35 +msgid "STATUS" +msgstr "" + +#: ../../templates/show_test.inc:39 +msgid "PHP Version" +msgstr "" + +#: ../../templates/show_test.inc:54 +msgid "" +"This tests to make sure that you are running a version of PHP that is known " +"to work with Ampache." +msgstr "" + +#: ../../templates/show_test.inc:58 +msgid "Mysql for PHP" +msgstr "" + +#: ../../templates/show_test.inc:73 +msgid "" +"This test checks to see if you have the mysql extensions loaded for PHP. " +"These are required for Ampache to work." +msgstr "" + +#: ../../templates/show_test.inc:77 +msgid "PHP Session Support" +msgstr "" + +#: ../../templates/show_test.inc:92 +msgid "" +"This test checks to make sure that you have PHP session support enabled. " +"Sessions are required for Ampache to work." +msgstr "" + +#: ../../templates/show_test.inc:96 +msgid "PHP ICONV Support" +msgstr "" + +#: ../../templates/show_test.inc:110 +msgid "" +"This test checks to make sure you have Iconv support installed. Iconv " +"support is not required for Ampache, but it is highly recommended" +msgstr "" + +#: ../../templates/show_test.inc:114 +#: ../../templates/show_install_config.inc:88 +msgid "Ampache.cfg Exists" +msgstr "" + +#: ../../templates/show_test.inc:129 +msgid "" +"This attempts to read /config/ampache.cfg If this fails either the ampache." +"cfg is not in the correct locations or\n" +"\tit is not currently readable by your webserver." +msgstr "" + +#: ../../templates/show_test.inc:135 +#: ../../templates/show_install_config.inc:105 +msgid "Ampache.cfg Configured?" +msgstr "" + +#: ../../templates/show_test.inc:152 +msgid "" +"This test makes sure that you have set all of the required config variables " +"and that we are able to \n" +"\tcompleatly parse your config file" +msgstr "" + +#: ../../templates/show_test.inc:159 +msgid "Ampache.cfg Up to Date?" +msgstr "" + +#: ../../templates/show_test.inc:165 +msgid "DB Connection" +msgstr "" + +#: ../../templates/show_test.inc:181 +msgid "" +"This attempts to connect to your database using the values from your ampache." +"cfg" +msgstr "" + +#: ../../templates/show_admin_index.inc:30 +msgid "Admin Section" +msgstr "" + +#: ../../templates/show_admin_index.inc:32 ../../templates/admin_menu.inc:33 +msgid "Users" +msgstr "" + +#: ../../templates/show_admin_index.inc:32 +msgid "Create/Modify User Accounts for Ampache" +msgstr "" + +#: ../../templates/show_admin_index.inc:33 +msgid "Mail" +msgstr "" + +#: ../../templates/show_admin_index.inc:33 +msgid "Mail your users to notfiy them of changes" +msgstr "" + +#: ../../templates/show_admin_index.inc:34 +msgid "Create/Update/Clean your catalog here" +msgstr "" + +#: ../../templates/show_admin_index.inc:35 ../../templates/admin_menu.inc:36 +msgid "Admin Preferences" +msgstr "" + +#: ../../templates/show_admin_index.inc:35 +msgid "Modify Site-wide preferences" +msgstr "" + +#: ../../templates/show_admin_index.inc:36 +msgid "Modify Access List Permissions" +msgstr "" + +#: ../../templates/show_admin_index.inc:36 +msgid "Must have access_control=true in ampache.cfg" +msgstr "" + +#: ../../templates/show_localplay.inc:30 +msgid "Local Play Control" +msgstr "" + +#: ../../templates/show_localplay.inc:35 +msgid "Playback" +msgstr "" + +#: ../../templates/show_localplay.inc:39 ../../templates/list_header.inc:69 +#: ../../templates/show_mpdplay.inc:43 +msgid "Prev" +msgstr "" + +#: ../../templates/show_localplay.inc:40 ../../templates/show_mpdplay.inc:44 +msgid "Stop" +msgstr "" + +#: ../../templates/show_localplay.inc:42 ../../templates/show_mpdplay.inc:46 +msgid "Pause" +msgstr "" + +#: ../../templates/show_localplay.inc:43 ../../templates/list_header.inc:92 +#: ../../templates/show_mpdplay.inc:47 +msgid "Next" +msgstr "" + +#: ../../templates/show_localplay.inc:49 +msgid "Volume" +msgstr "" + +#: ../../templates/show_localplay.inc:53 ../../templates/show_localplay.inc:54 +msgid "Increase Volume" +msgstr "" + +#: ../../templates/show_localplay.inc:55 ../../templates/show_localplay.inc:56 +msgid "Decrease Volume" +msgstr "" + +#: ../../templates/show_localplay.inc:62 +msgid "Clear queue" +msgstr "" + +#: ../../templates/add_catalog.inc:28 +msgid "Add a Catalog" +msgstr "" + +#: ../../templates/add_catalog.inc:30 +msgid "" +"In the form below enter either a local path (i.e. /data/music) or the URL to " +"a remote Ampache installation (i.e http://theotherampache.com)" +msgstr "" + +#: ../../templates/add_catalog.inc:36 +msgid "Catalog Name" +msgstr "" + +#: ../../templates/add_catalog.inc:53 +msgid "Path" +msgstr "" + +#: ../../templates/add_catalog.inc:57 +msgid "Catalog Type" +msgstr "" + +#: ../../templates/add_catalog.inc:61 +msgid "Remote" +msgstr "" + +#: ../../templates/add_catalog.inc:66 +msgid "ID3 Set Command" +msgstr "" + +#: ../../templates/add_catalog.inc:70 +msgid "Filename Pattern" +msgstr "" + +#: ../../templates/add_catalog.inc:78 +msgid "Gather Album Art" +msgstr "" + +#: ../../templates/add_catalog.inc:82 +msgid "ID3V2 Tags" +msgstr "" + +#: ../../templates/add_catalog.inc:85 +msgid "Amazon" +msgstr "" + +#: ../../templates/add_catalog.inc:88 +msgid "File Folder" +msgstr "" + +#: ../../templates/add_catalog.inc:98 +msgid "Add Catalog" +msgstr "" + +#: ../../templates/list_flagged.inc:42 ../../templates/show_songs.inc:41 +msgid "Flag" +msgstr "" + +#: ../../templates/list_flagged.inc:43 +msgid "New Flag" +msgstr "" + +#: ../../templates/list_flagged.inc:44 +msgid "Flagged by" +msgstr "" + +#: ../../templates/list_flagged.inc:45 +msgid "ID3 Update" +msgstr "" + +#: ../../templates/list_flagged.inc:69 +msgid "Accept" +msgstr "" + +#: ../../templates/list_flagged.inc:70 +msgid "Reject" +msgstr "" + +#: ../../templates/show_songs.inc:33 +msgid "Song title" +msgstr "" + +#: ../../templates/show_songs.inc:114 +msgid "Direct Link" +msgstr "" + +#: ../../templates/show_songs.inc:133 +msgid "Total" +msgstr "" + +#: ../../templates/show_songs.inc:159 +msgid "Set Track Numbers" +msgstr "" + +#: ../../templates/show_songs.inc:160 +msgid "Remove Selected Tracks" +msgstr "" + +#: ../../templates/show_songs.inc:166 ../../templates/show_artist.inc:100 +msgid "Add to" +msgstr "" + +#: ../../templates/show_artists.inc:56 ../../templates/show_albums.inc:59 +msgid "Random" +msgstr "" + +#: ../../templates/show_install_config.inc:52 +msgid "" +"This steps takes the basic config values, and first attempts to write them " +"out directly to your webserver. If access is denied it will prompt you to " +"download the config file. Please put the downloaded config file in /config" +msgstr "" + +#: ../../templates/show_install_config.inc:60 +msgid "Web Path" +msgstr "" + +#: ../../templates/show_install_config.inc:64 +#: ../../templates/show_install.inc:58 +msgid "Desired Database Name" +msgstr "" + +#: ../../templates/show_install_config.inc:68 +#: ../../templates/show_install.inc:62 +msgid "MySQL Hostname" +msgstr "" + +#: ../../templates/show_install_config.inc:72 +msgid "MySQL Username" +msgstr "" + +#: ../../templates/show_install_config.inc:76 +msgid "MySQL Password" +msgstr "" + +#: ../../templates/show_install_config.inc:81 +msgid "Write Config" +msgstr "" + +#: ../../templates/show_install_config.inc:125 +msgid "Check for Config" +msgstr "" + +#: ../../templates/show_album.inc:53 +msgid "Play Album" +msgstr "" + +#: ../../templates/show_album.inc:54 +msgid "Play Random from Album" +msgstr "" + +#: ../../templates/show_album.inc:55 +msgid "Reset Album Art" +msgstr "" + +#: ../../templates/show_album.inc:56 +msgid "Find Album Art" +msgstr "" + +#: ../../templates/show_album.inc:58 ../../templates/show_artist.inc:37 +msgid "Update from tags" +msgstr "" + +#: ../../templates/show_preferences.inc:31 +msgid "Editing" +msgstr "" + +#: ../../templates/show_preferences.inc:31 +msgid "preferences" +msgstr "" + +#: ../../templates/show_preferences.inc:33 +msgid "Rebuild Preferences" +msgstr "" + +#: ../../templates/show_preferences.inc:39 +msgid "Preference" +msgstr "" + +#: ../../templates/show_preferences.inc:40 +msgid "Value" +msgstr "" + +#: ../../templates/show_preferences.inc:42 +msgid "Type" +msgstr "" + +#: ../../templates/show_preferences.inc:43 +msgid "Apply to All" +msgstr "" + +#: ../../templates/show_preferences.inc:58 +msgid "Update Preferences" +msgstr "" + +#: ../../templates/show_preferences.inc:62 +msgid "Cancel" +msgstr "" + +#: ../../templates/userform.inc:25 +msgid "Adding a New User" +msgstr "" + +#: ../../templates/userform.inc:29 +msgid "Editing existing User" +msgstr "" + +#: ../../templates/userform.inc:81 +msgid "User Access Level" +msgstr "" + +#: ../../templates/userform.inc:98 +msgid "Add User" +msgstr "" + +#: ../../templates/userform.inc:103 +msgid "Update User" +msgstr "" + +#: ../../templates/show_install.inc:40 +msgid "Your webserver configured so that <ampache_root>/docs is your webroot" +msgstr "" + +#: ../../templates/show_install.inc:49 +msgid "" +"This step creates and inserts the Ampache database, as such please provide a " +"mysql account with database creation rights. This step may take a while " +"depending upon the speed of your computer" +msgstr "" + +#: ../../templates/show_install.inc:66 +msgid "MySQL Administrative Username" +msgstr "" + +#: ../../templates/show_install.inc:70 +msgid "MySQL Administrative Password" +msgstr "" + +#: ../../templates/show_install.inc:75 +msgid "Insert Database" +msgstr "" + +#: ../../templates/show_add_access.inc:31 +msgid "Add Access for a Host" +msgstr "" + +#: ../../templates/show_add_access.inc:33 +msgid "" +"Use the form below to add a host that you want to have access to your " +"Ampache catalog." +msgstr "" + +#: ../../templates/show_add_access.inc:46 +msgid "Start IP Address" +msgstr "" + +#: ../../templates/show_add_access.inc:52 +msgid "End IP Address" +msgstr "" + +#: ../../templates/show_add_access.inc:58 +#: ../../templates/show_access_list.inc:50 +msgid "Level" +msgstr "" + +#: ../../templates/show_add_access.inc:72 +msgid "Add Host" +msgstr "" + +#: ../../templates/admin_menu.inc:34 +msgid "Mail Users" +msgstr "" + +#: ../../templates/show_upload.inc:27 +msgid "Please Ensure All Files Are Tagged Correctly" +msgstr "" + +#: ../../templates/show_upload.inc:30 +msgid "" +"Ampache relies on id3 tags to sort data. If your file is not tagged it may " +"be deleted." +msgstr "" + +#: ../../templates/show_upload.inc:34 +msgid "max_upload_size" +msgstr "" + +#: ../../templates/show_upload.inc:59 ../../templates/menu.inc:39 +msgid "Upload" +msgstr "" + +#: ../../templates/show_users.inc:42 +msgid "Fullname" +msgstr "" + +#: ../../templates/show_users.inc:47 +msgid "Last Seen" +msgstr "" + +#: ../../templates/show_users.inc:54 +msgid "Prefs" +msgstr "" + +#: ../../templates/show_users.inc:60 +msgid "Set Access" +msgstr "" + +#: ../../templates/show_users.inc:66 +msgid "On-line" +msgstr "" + +#: ../../templates/show_users.inc:88 +msgid "edit" +msgstr "" + +#: ../../templates/show_users.inc:93 +msgid "prefs" +msgstr "" + +#: ../../templates/show_users.inc:98 +msgid "delete" +msgstr "" + +#: ../../templates/show_users.inc:104 ../../templates/show_users.inc:113 +#: ../../templates/show_users.inc:116 +msgid "set to user" +msgstr "" + +#: ../../templates/show_users.inc:105 ../../templates/show_users.inc:109 +#: ../../templates/show_users.inc:117 +msgid "disable" +msgstr "" + +#: ../../templates/show_users.inc:108 ../../templates/show_users.inc:112 +msgid "set to admin" +msgstr "" + +#: ../../templates/show_now_playing.inc:34 ../../templates/show_mpdplay.inc:68 +msgid "Now Playing" +msgstr "" + +#: ../../templates/show_login_form.inc:40 +#: ../../templates/show_login_form.inc:54 +msgid "Login" +msgstr "" + +#: ../../templates/show_login_form.inc:50 +msgid "Remember Me" +msgstr "" + +#: ../../templates/show_access_list.inc:34 +msgid "Host Access to Your Catalog" +msgstr "" + +#: ../../templates/show_access_list.inc:43 +msgid "Add Entry" +msgstr "" + +#: ../../templates/show_access_list.inc:48 +msgid "Start Address" +msgstr "" + +#: ../../templates/show_access_list.inc:49 +msgid "End Address" +msgstr "" + +#: ../../templates/show_access_list.inc:65 +msgid "Revoke" +msgstr "" + +#: ../../templates/menu.inc:31 +msgid "Home" +msgstr "" + +#: ../../templates/menu.inc:34 +msgid "Playlists" +msgstr "" + +#: ../../templates/menu.inc:35 ../../templates/show_search.inc:38 +#: ../../templates/show_search.inc:84 +msgid "Search" +msgstr "" + +#: ../../templates/menu.inc:36 +msgid "Preferences" +msgstr "" + +#: ../../templates/menu.inc:57 ../../templates/menu.inc:60 +msgid "Admin" +msgstr "" + +#: ../../templates/menu.inc:69 ../../templates/menu.inc:76 +msgid "Account" +msgstr "" + +#: ../../templates/menu.inc:70 ../../templates/menu.inc:77 +msgid "Stats" +msgstr "" + +#: ../../templates/menu.inc:71 ../../templates/menu.inc:78 +#: ../../templates/menu.inc:83 +msgid "Logout" +msgstr "" + +#: ../../templates/show_search.inc:35 +msgid "Search Ampache" +msgstr "" + +#: ../../templates/show_search.inc:42 +msgid "Object Type" +msgstr "" + +#: ../../templates/show_search.inc:75 +msgid "Search Type" +msgstr "" + +#: ../../templates/show_mpdplay.inc:33 +msgid "MPD Play Control" +msgstr "" + +#: ../../templates/show_mpdplay.inc:52 +msgid "Loop" +msgstr "" + +#: ../../templates/show_mpdplay.inc:54 +msgid "Loop is off" +msgstr "" + +#: ../../templates/show_mpdplay.inc:55 +msgid "Loop is on" +msgstr "" + +#: ../../templates/show_mpdplay.inc:81 +msgid "Refresh the Playlist Window" +msgstr "" + +#: ../../templates/show_mpdplay.inc:81 +msgid "refresh now" +msgstr "" + +#: ../../templates/show_mpdplay.inc:89 +msgid "Server Playlist" +msgstr "" + +#: ../../templates/show_mpdplay.inc:122 +msgid "Click to shuffle (randomize) the playlist" +msgstr "" + +#: ../../templates/show_mpdplay.inc:122 +msgid "shuffle" +msgstr "" + +#: ../../templates/show_mpdplay.inc:123 +msgid "Click the clear the playlist" +msgstr "" + +#: ../../templates/show_mpdplay.inc:123 +msgid "clear" +msgstr "" + +#: ../../templates/show_artist.inc:31 +msgid "Albums by" +msgstr "" + +#: ../../templates/show_artist.inc:33 +msgid "Show All Songs By" +msgstr "" + +#: ../../templates/show_artist.inc:34 +msgid "Play All Songs By" +msgstr "" + +#: ../../templates/show_artist.inc:35 +msgid "Play Random Songs By" +msgstr "" + +#: ../../templates/show_artist.inc:50 +msgid "Select" +msgstr "" + +#: ../../templates/show_artist.inc:52 +msgid "Cover" +msgstr "" + +#: ../../templates/show_artist.inc:53 +msgid "Album Name" +msgstr "" + +#: ../../templates/show_artist.inc:54 +msgid "Album Year" +msgstr "" + +#: ../../templates/show_artist.inc:55 +msgid "Total Tracks" +msgstr "" diff --git a/locale/de_DE/LC_MESSAGES/messages.mo b/locale/de_DE/LC_MESSAGES/messages.mo Binary files differnew file mode 100644 index 00000000..6c00ce7e --- /dev/null +++ b/locale/de_DE/LC_MESSAGES/messages.mo diff --git a/locale/de_DE/LC_MESSAGES/messages.po b/locale/de_DE/LC_MESSAGES/messages.po new file mode 100644 index 00000000..86a85f2b --- /dev/null +++ b/locale/de_DE/LC_MESSAGES/messages.po @@ -0,0 +1,2026 @@ +# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-04-17 17:14-0700\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-15\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../../docs/admin/catalog.php:54 ../../templates/catalog.inc:66 +msgid "Add to Catalog(s)" +msgstr "Zu Katalog hinzufügen" + +#: ../../docs/admin/catalog.php:65 ../../templates/catalog.inc:67 +msgid "Add to all Catalogs" +msgstr "Zu allen Katalogen hinzufügen" + +#: ../../docs/admin/catalog.php:75 ../../templates/catalog.inc:73 +msgid "Update Catalog(s)" +msgstr "Katalog(e) aktualisieren" + +#: ../../docs/admin/catalog.php:86 ../../templates/catalog.inc:74 +msgid "Update All Catalogs" +msgstr "Alle Kataloge aktualisieren" + +#: ../../docs/admin/catalog.php:118 ../../templates/catalog.inc:80 +msgid "Clean Catalog(s)" +msgstr "Katalog säubern" + +#: ../../docs/admin/catalog.php:148 ../../templates/catalog.inc:81 +msgid "Clean All Catalogs" +msgstr "Alle Kataloge säubern" + +#: ../../docs/admin/catalog.php:196 +msgid "Now Playing Cleared" +msgstr "Zur Zeit gespielte Liederliste geleert" + +#: ../../docs/admin/catalog.php:196 +msgid "All now playing data has been cleared" +msgstr "Übersicht der aktuell abspielenden Lieder geleert" + +#: ../../docs/admin/catalog.php:201 +msgid "Do you really want to clear your catalog?" +msgstr "Wollen sie wirklich den Katalog leeren?" + +#: ../../docs/admin/catalog.php:208 +msgid "Do you really want to clear the statistics for this catalog?" +msgstr "Wollen sie wirklich die Katalogstatistik leeren?" + +#: ../../docs/admin/catalog.php:226 +msgid "Do you really want to delete this catalog?" +msgstr "Wollen sie wirklich den Katalog löschen?" + +#: ../../docs/admin/users.php:76 ../../docs/admin/users.php:123 +msgid "Error Username Required" +msgstr "" + +#: ../../docs/admin/users.php:79 ../../docs/admin/users.php:120 +msgid "Error Passwords don't match" +msgstr "" + +#: ../../docs/admin/users.php:137 +msgid "Are you sure you want to permanently delete" +msgstr "" + +#: ../../docs/admin/users.php:144 +#: ../../templates/show_confirm_action.inc.php:29 +msgid "No" +msgstr "" + +#: ../../docs/admin/users.php:146 +#, fuzzy +msgid "User Deleted" +msgstr "Eintrag gelöscht" + +#: ../../docs/admin/users.php:149 +#, fuzzy +msgid "Delete Error" +msgstr "Löschen" + +#: ../../docs/admin/users.php:149 +msgid "Unable to delete last Admin User" +msgstr "" + +#: ../../docs/admin/access.php:43 +msgid "Do you really want to delete this Access Record?" +msgstr "Wollen sie wirklich diesen Zugangs-Eintrag löschen?" + +#: ../../docs/admin/access.php:51 +msgid "Entry Deleted" +msgstr "Eintrag gelöscht" + +#: ../../docs/admin/access.php:51 +msgid "Your Access List Entry has been removed" +msgstr "Ein Zugangs-Eintrag wurde gelöscht" + +#: ../../docs/admin/access.php:61 +#, fuzzy +msgid "Entry Added" +msgstr "Eintrag gelöscht" + +#: ../../docs/admin/access.php:61 +msgid "Your new Access List Entry has been created" +msgstr "Ein neuer Zugangs-Eintrag wurde erstellt" + +#: ../../docs/admin/song.php:70 +#, fuzzy +msgid "Songs Disabled" +msgstr "Songname" + +#: ../../docs/admin/song.php:70 +msgid "The requested song(s) have been disabled" +msgstr "" + +#: ../../docs/admin/song.php:80 +#, fuzzy +msgid "Songs Enabled" +msgstr "Lieder gelöscht" + +#: ../../docs/admin/song.php:80 +msgid "The requested song(s) have been enabled" +msgstr "" + +#: ../../docs/play/index.php:46 +msgid "Session Expired: please log in again at" +msgstr "" + +#: ../../docs/artists.php:47 +#, fuzzy +msgid "All songs by" +msgstr "Alle Lieder" + +#: ../../docs/artists.php:56 ../../docs/albums.php:103 +msgid "Starting Update from Tags" +msgstr "Starte Aktuallisierung vom TAG" + +#: ../../docs/artists.php:61 ../../docs/albums.php:108 +msgid "Update From Tags Compleate" +msgstr "Aktuallisierung vom Tag vollständig" + +#: ../../docs/artists.php:62 ../../docs/albums.php:109 +#: ../../modules/class/catalog.php:615 +msgid "Return" +msgstr "" + +#: ../../docs/artists.php:73 ../../docs/artists.php:82 +#: ../../docs/artists.php:94 ../../docs/artists.php:111 +msgid "<u>S</u>how artists starting with" +msgstr "(<u>S</u>) Zeige Interpreten mit folgendem Beginn" + +#: ../../docs/amp-mpd.php:32 +#, fuzzy +msgid "Error Connecting" +msgstr "Datenbank Verbindung" + +#: ../../docs/playlist.php:71 ../../templates/show_songs.inc:151 +#: ../../templates/show_artist.inc:95 +msgid "Play Selected" +msgstr "Ausgewählte abspielen" + +#: ../../docs/playlist.php:89 ../../templates/show_songs.inc:152 +msgid "Flag Selected" +msgstr "Ausgewählte markieren" + +#: ../../docs/playlist.php:95 ../../templates/show_songs.inc:153 +msgid "Edit Selected" +msgstr "Ausgwählte editieren" + +#: ../../docs/playlist.php:125 ../../modules/lib.php:1007 +#: ../../templates/show_songs.inc:169 ../../templates/show_users.inc:51 +#: ../../templates/show_artist.inc:103 +msgid "Edit" +msgstr "Editieren" + +#: ../../docs/playlist.php:128 ../../modules/lib.php:1016 +#: ../../templates/show_localplay.inc:41 ../../templates/show_artists.inc:54 +#: ../../templates/show_albums.inc:57 ../../templates/show_mpdplay.inc:45 +#: ../../templates/show_artist.inc:79 +msgid "Play" +msgstr "Abspielen" + +#: ../../docs/playlist.php:140 +#, fuzzy +msgid "New Playlist" +msgstr "Playlist" + +#: ../../docs/playlist.php:198 +#, fuzzy +msgid "Playlist updated." +msgstr "Playlistenname" + +#: ../../docs/playlist.php:305 +msgid "No songs in this playlist." +msgstr "" + +#: ../../docs/localplay.php:79 +msgid "Unknown action requested" +msgstr "" + +#: ../../docs/index.php:39 +msgid "Welcome to" +msgstr "Willkommen bei" + +#: ../../docs/index.php:41 +msgid "you are currently logged in as" +msgstr "" + +#: ../../docs/index.php:72 +msgid "Most Popular Songs" +msgstr "Meistgespielte Lieder" + +#: ../../docs/index.php:79 +msgid "Most Popular Artists" +msgstr "Meistgespielte Interpreten" + +#: ../../docs/index.php:91 +msgid "Newest Album Additions" +msgstr "Neuste Albenzugänge" + +#: ../../docs/index.php:98 +msgid "Newest Artist Additions" +msgstr "Neuste Interpretenzugänge" + +#: ../../docs/flag.php:35 +msgid "Flagging song completed." +msgstr "" + +#: ../../docs/albums.php:43 +#, fuzzy +msgid "Album Art Cleared" +msgstr "Album Jahr" + +#: ../../docs/albums.php:43 +msgid "Album Art information has been removed form the database" +msgstr "" + +#: ../../docs/albums.php:75 +msgid "Album Art Located" +msgstr "" + +#: ../../docs/albums.php:75 +msgid "" +"Album Art information has been located in Amazon. If incorrect, click " +"\"Reset Album Art\" below to remove the artwork." +msgstr "" + +#: ../../docs/albums.php:83 ../../docs/albums.php:93 +msgid "Get Art" +msgstr "" + +#: ../../docs/albums.php:87 +msgid "Album Art Not Located" +msgstr "" + +#: ../../docs/albums.php:87 +msgid "" +"Album Art could not be located at this time. This may be due to Amazon being " +"busy, or the album not being present in their collection." +msgstr "" + +#: ../../docs/albums.php:123 ../../docs/albums.php:138 +msgid "All Albums" +msgstr "Alle Alben" + +#: ../../docs/albums.php:124 ../../docs/albums.php:131 +msgid "<u>S</u>how all albums" +msgstr "(<u>S</u>) Zeige alle Alben" + +#: ../../docs/albums.php:130 +msgid "Albums with no artwork" +msgstr "" + +#: ../../docs/albums.php:139 ../../docs/albums.php:146 +#: ../../docs/albums.php:151 +msgid "<u>S</u>how only albums starting with" +msgstr "(<u>S</u>) Zeige alle Alben mit folgendem Beginn" + +#: ../../docs/albums.php:145 +msgid "Select a starting letter or Show all" +msgstr "Wähle einen Anfangsbuchstaben oder 'Zeige Alle'" + +#: ../../docs/upload.php:124 +msgid "The uploaded file exceeds the upload_max_filesize directive in php.ini" +msgstr "" + +#: ../../docs/upload.php:127 +msgid "" +"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in " +"the HTML form." +msgstr "" + +#: ../../docs/upload.php:130 +msgid "The uploaded file was only partially uploaded." +msgstr "" + +#: ../../docs/upload.php:133 +msgid "No file was uploaded." +msgstr "" + +#: ../../docs/upload.php:136 +msgid "An Unknown Error has occured." +msgstr "" + +#: ../../docs/upload.php:157 +msgid "Successfully-Quarantined" +msgstr "" + +#: ../../docs/upload.php:167 +msgid "Successfully-Cataloged" +msgstr "" + +#: ../../docs/upload.php:229 ../../templates/show_songs.inc:42 +#: ../../templates/show_artists.inc:43 ../../templates/show_artists.inc:67 +#: ../../templates/show_albums.inc:45 ../../templates/show_albums.inc:73 +#: ../../templates/show_access_list.inc:51 ../../templates/show_artist.inc:56 +msgid "Action" +msgstr "Aktion" + +#: ../../docs/upload.php:230 ../../templates/flag.inc:58 +#: ../../templates/list_flagged.inc:41 +#, fuzzy +msgid "Song" +msgstr "Lieder" + +#: ../../docs/upload.php:231 ../../modules/class/song.php:275 +#: ../../templates/show_songs.inc:34 ../../templates/show_artists.inc:39 +#: ../../templates/show_artists.inc:62 ../../templates/show_albums.inc:40 +#: ../../templates/show_albums.inc:70 +msgid "Artist" +msgstr "Interpret" + +#: ../../docs/upload.php:232 ../../modules/class/song.php:280 +#: ../../templates/show_songs.inc:35 ../../templates/show_albums.inc:38 +#: ../../templates/show_albums.inc:68 +msgid "Album" +msgstr "Album" + +#: ../../docs/upload.php:233 ../../modules/class/song.php:293 +#: ../../templates/show_songs.inc:40 +msgid "Genre" +msgstr "Genre" + +#: ../../docs/upload.php:234 ../../modules/class/song.php:262 +#: ../../templates/show_songs.inc:37 +msgid "Time" +msgstr "Dauer" + +#: ../../docs/upload.php:235 ../../modules/class/song.php:250 +#: ../../templates/show_songs.inc:39 +msgid "Bitrate" +msgstr "Bitrate" + +#: ../../docs/upload.php:236 ../../templates/show_songs.inc:38 +msgid "Size" +msgstr "Größe" + +#: ../../docs/upload.php:237 +#, fuzzy +msgid "Filename" +msgstr "Dateinamenmuster" + +#: ../../docs/upload.php:238 +#, fuzzy +msgid "User" +msgstr "Benutzer" + +#: ../../docs/upload.php:239 +#, fuzzy +msgid "Date" +msgstr "Entfernt" + +#: ../../docs/upload.php:267 +msgid "Unknown" +msgstr "" + +#: ../../docs/upload.php:289 +#, fuzzy +msgid "Add" +msgstr "Katalog hinzufügen" + +#: ../../docs/upload.php:290 ../../modules/lib.php:1008 +#: ../../templates/catalog.inc:60 ../../templates/show_users.inc:57 +msgid "Delete" +msgstr "Löschen" + +#: ../../docs/upload.php:294 +#, fuzzy +msgid "Quarantined" +msgstr "Quarantine" + +#: ../../templates/show_user_registration.inc.php:28 +#: ../../templates/show_install_account.inc.php:60 +#: ../../templates/userform.inc:41 ../../templates/show_users.inc:39 +#, fuzzy +msgid "Username" +msgstr "Benutzer" + +#: ../../templates/show_user_registration.inc.php:36 +#: ../../templates/userform.inc:49 +#, fuzzy +msgid "Full Name" +msgstr "Playlistenname" + +#: ../../templates/show_user_registration.inc.php:44 +#: ../../templates/show_user.inc.php:40 ../../templates/userform.inc:56 +msgid "E-mail" +msgstr "" + +#: ../../templates/show_user_registration.inc.php:52 +#: ../../templates/show_install_account.inc.php:64 +#: ../../templates/userform.inc:64 ../../templates/show_login_form.inc:44 +msgid "Password" +msgstr "Passwort" + +#: ../../templates/show_user_registration.inc.php:60 +#: ../../templates/show_user.inc.php:75 ../../templates/userform.inc:73 +#, fuzzy +msgid "Confirm Password" +msgstr "Passwort" + +#: ../../templates/show_user_registration.inc.php:69 +msgid "Register User" +msgstr "" + +#: ../../templates/show_user.inc.php:31 +#: ../../templates/customize_catalog.inc:29 +#: ../../templates/show_add_access.inc:40 +#: ../../templates/show_access_list.inc:47 +msgid "Name" +msgstr "Name" + +#: ../../templates/show_user.inc.php:48 +#, fuzzy +msgid "View Limit" +msgstr "Speicherbegrenzung" + +#: ../../templates/show_user.inc.php:56 +#, fuzzy +msgid "Update Profile" +msgstr "Einstellungen aktualisieren" + +#: ../../templates/show_user.inc.php:67 +#, fuzzy +msgid "Enter password" +msgstr "Passwort" + +#: ../../templates/show_user.inc.php:83 +#, fuzzy +msgid "Change Password" +msgstr "Passwort" + +#: ../../templates/show_user.inc.php:91 +#, fuzzy +msgid "Clear Stats" +msgstr "Katalogstatistiken löschen" + +#: ../../templates/show_install_account.inc.php:35 +#: ../../templates/show_install_config.inc:35 +#: ../../templates/show_install.inc:34 +msgid "Ampache Installation" +msgstr "" + +#: ../../templates/show_install_account.inc.php:37 +#: ../../templates/show_install_config.inc:37 +#: ../../templates/show_install.inc:36 +msgid "" +"This Page handles the installation of the ampache database and the creation " +"of the ampache.cfg file. Before you continue please make sure that you have " +"the following pre-requisits" +msgstr "" + +#: ../../templates/show_install_account.inc.php:40 +#: ../../templates/show_install_config.inc:40 +#: ../../templates/show_install.inc:39 +msgid "" +"A MySQL Server with a username and password that can create/modify databases" +msgstr "" + +#: ../../templates/show_install_account.inc.php:41 +#: ../../templates/show_install_config.inc:41 +msgid "" +"Your webserver configured so that your config directory is not visable to " +"the web" +msgstr "" + +#: ../../templates/show_install_account.inc.php:42 +#: ../../templates/show_install_config.inc:42 +#: ../../templates/show_install.inc:41 +msgid "" +"Your webserver has read access to the /sql/ampache.sql file and the /config/" +"ampache.cfg.dist file" +msgstr "" + +#: ../../templates/show_install_account.inc.php:44 +#: ../../templates/show_install_config.inc:44 +#: ../../templates/show_install.inc:43 +msgid "" +"Once you have ensured that you have the above requirements please fill out " +"the information below. You will only be asked for the required config " +"values. If you would like to make changes to your ampache install at a later " +"date simply edit /config/ampache.cfg" +msgstr "" + +#: ../../templates/show_install_account.inc.php:49 +#: ../../templates/show_install_config.inc:49 +#: ../../templates/show_install.inc:47 +msgid "Step 1 - Creating and Inserting the Ampache Database" +msgstr "" + +#: ../../templates/show_install_account.inc.php:50 +#: ../../templates/show_install_config.inc:50 +#: ../../templates/show_install.inc:51 +msgid "Step 2 - Creating the Ampache.cfg file" +msgstr "" + +#: ../../templates/show_install_account.inc.php:51 +#: ../../templates/show_install_config.inc:54 +#: ../../templates/show_install.inc:52 +msgid "Step 3 - Setup Initial Account" +msgstr "" + +#: ../../templates/show_install_account.inc.php:53 +msgid "" +"This step creates your initial Ampache admin account. Once your admin " +"account has been created you will be directed to the login page" +msgstr "" + +#: ../../templates/show_install_account.inc.php:69 +#, fuzzy +msgid "Create Account" +msgstr "Account" + +#: ../../templates/show_confirm_action.inc.php:28 +msgid "Yes" +msgstr "" + +#: ../../templates/show_confirmation.inc.php:30 +msgid "Continue" +msgstr "Weiter" + +#: ../../lib/preferences.php:199 +msgid "Enable" +msgstr "" + +#: ../../lib/preferences.php:200 ../../templates/show_users.inc:63 +msgid "Disable" +msgstr "" + +#: ../../lib/preferences.php:210 ../../templates/add_catalog.inc:60 +msgid "Local" +msgstr "Lokal" + +#: ../../lib/preferences.php:211 +msgid "Stream" +msgstr "" + +#: ../../lib/preferences.php:212 +msgid "IceCast" +msgstr "" + +#: ../../lib/preferences.php:213 +msgid "Downsample" +msgstr "" + +#: ../../lib/preferences.php:214 +msgid "Music Player Daemon" +msgstr "" + +#: ../../lib/preferences.php:221 +msgid "M3U" +msgstr "" + +#: ../../lib/preferences.php:222 +msgid "Simple M3U" +msgstr "" + +#: ../../lib/preferences.php:223 +msgid "PLS" +msgstr "" + +#: ../../lib/preferences.php:224 +msgid "Asx" +msgstr "" + +#: ../../lib/preferences.php:231 +msgid "English" +msgstr "" + +#: ../../lib/preferences.php:232 +msgid "German" +msgstr "" + +#: ../../lib/preferences.php:233 +msgid "French" +msgstr "" + +#: ../../lib/search.php:52 ../../lib/search.php:68 ../../lib/search.php:84 +#: ../../lib/search.php:100 ../../lib/search.php:116 ../../lib/search.php:133 +#: ../../lib/search.php:145 ../../lib/search.php:161 ../../lib/search.php:177 +#, fuzzy +msgid "No Results Found" +msgstr "Keine Dateileichen gefunden" + +#: ../../lib/ui.php:180 ../../templates/show_songs.inc:166 +#: ../../templates/show_artist.inc:100 +msgid "Playlist" +msgstr "Playlist" + +#: ../../lib/ui.php:180 +msgid "New" +msgstr "Neu" + +#: ../../lib/ui.php:181 +msgid "View All" +msgstr "Alle ansehen" + +#: ../../lib/ui.php:305 +#, fuzzy +msgid "Show w/o art" +msgstr "Zeige Alle" + +#: ../../lib/ui.php:307 +msgid "Show all" +msgstr "Zeige Alle" + +#: ../../lib/mpd.php:31 ../../modules/class/catalog.php:876 +#, fuzzy +msgid "Error" +msgstr "Fehler-Farbe" + +#: ../../lib/mpd.php:31 +msgid "Could not add" +msgstr "" + +#: ../../lib/Browser.php:867 +#, fuzzy +msgid "file" +msgstr "Seitentitel" + +#: ../../lib/Browser.php:871 +msgid "File uploads not supported." +msgstr "" + +#: ../../lib/Browser.php:889 +msgid "No file uploaded" +msgstr "" + +#: ../../lib/Browser.php:896 +#, php-format +msgid "There was a problem with the file upload: No %s was uploaded." +msgstr "" + +#: ../../lib/Browser.php:901 +#, php-format +msgid "" +"There was a problem with the file upload: The %s was larger than the maximum " +"allowed size (%d bytes)." +msgstr "" + +#: ../../lib/Browser.php:903 +#, php-format +msgid "" +"There was a problem with the file upload: The %s was only partially uploaded." +msgstr "" + +#: ../../modules/class/catalog.php:267 ../../modules/class/catalog.php:527 +#: ../../modules/class/album.php:241 +msgid "Error: Unable to open" +msgstr "" + +#: ../../modules/class/catalog.php:289 +msgid "Error: Unable to change to directory" +msgstr "" + +#: ../../modules/class/catalog.php:312 +msgid "Error: Unable to get filesize for" +msgstr "" + +#: ../../modules/class/catalog.php:332 +msgid "Added" +msgstr "" + +#: ../../modules/class/catalog.php:342 +msgid "is not readable by ampache" +msgstr "" + +#: ../../modules/class/catalog.php:402 +msgid "Found in ID3" +msgstr "" + +#: ../../modules/class/catalog.php:406 +msgid "Found on Amazon" +msgstr "" + +#: ../../modules/class/catalog.php:410 +msgid "Found in Folder" +msgstr "" + +#: ../../modules/class/catalog.php:414 +#, fuzzy +msgid "Found" +msgstr "Schrift" + +#: ../../modules/class/catalog.php:417 +msgid "Not Found" +msgstr "" + +#: ../../modules/class/catalog.php:425 +#, fuzzy +msgid "Searched" +msgstr "Suchen" + +#: ../../modules/class/catalog.php:578 +#, fuzzy +msgid "Starting Dump Album Art" +msgstr "Cover finden" + +#: ../../modules/class/catalog.php:598 +msgid "Written" +msgstr "" + +#: ../../modules/class/catalog.php:607 +msgid "Error unable to open file for writting" +msgstr "" + +#: ../../modules/class/catalog.php:614 +#, fuzzy +msgid "Album Art Dump Complete" +msgstr "Album Jahr" + +#: ../../modules/class/catalog.php:681 +msgid "Starting Catalog Build" +msgstr "" + +#: ../../modules/class/catalog.php:686 +msgid "Running Remote Sync" +msgstr "" + +#: ../../modules/class/catalog.php:696 ../../modules/class/catalog.php:843 +#, fuzzy +msgid "Starting Album Art Search" +msgstr "Album Jahr" + +#: ../../modules/class/catalog.php:706 +#, fuzzy +msgid "Catalog Finished" +msgstr "Kataloggröße" + +#: ../../modules/class/catalog.php:706 ../../modules/class/catalog.php:862 +#, fuzzy +msgid "Total Time" +msgstr "Katalog Spielzeit" + +#: ../../modules/class/catalog.php:706 ../../modules/class/catalog.php:863 +#, fuzzy +msgid "Total Songs" +msgstr "Alle Lieder" + +#: ../../modules/class/catalog.php:707 ../../modules/class/catalog.php:863 +msgid "Songs Per Seconds" +msgstr "" + +#: ../../modules/class/catalog.php:741 +#, fuzzy +msgid "Updated" +msgstr "Schnelles Aktualisieren" + +#: ../../modules/class/catalog.php:748 +msgid "No Update Needed" +msgstr "" + +#: ../../modules/class/catalog.php:823 +msgid "Starting New Song Search on" +msgstr "" + +#: ../../modules/class/catalog.php:823 +#, fuzzy +msgid "catalog" +msgstr "Katalog" + +#: ../../modules/class/catalog.php:827 +msgid "Running Remote Update" +msgstr "" + +#: ../../modules/class/catalog.php:862 +#, fuzzy +msgid "Catalog Update Finished" +msgstr "Katalogstatistik" + +#: ../../modules/class/catalog.php:876 +msgid "Unable to load XMLRPC library, make sure XML-RPC is enabled" +msgstr "" + +#: ../../modules/class/catalog.php:908 ../../modules/class/catalog.php:923 +msgid "Error connecting to" +msgstr "" + +#: ../../modules/class/catalog.php:908 ../../modules/class/catalog.php:923 +#, fuzzy +msgid "Code" +msgstr "Demo Modus" + +#: ../../modules/class/catalog.php:908 ../../modules/class/catalog.php:923 +#, fuzzy +msgid "Reason" +msgstr "Lied" + +#: ../../modules/class/catalog.php:928 +msgid "Completed updating remote catalog(s)" +msgstr "" + +#: ../../modules/class/catalog.php:1016 +msgid "Checking" +msgstr "" + +#: ../../modules/class/catalog.php:1073 +#, fuzzy +msgid "Catalog Clean Done" +msgstr "Katalogname" + +#: ../../modules/class/catalog.php:1073 +msgid "files removed" +msgstr "" + +#: ../../modules/class/catalog.php:1313 +msgid "Updating the" +msgstr "" + +#: ../../modules/class/catalog.php:1313 +#: ../../templates/show_admin_index.inc:34 ../../templates/admin_menu.inc:35 +#, fuzzy +msgid "Catalog" +msgstr "Katalog" + +#: ../../modules/class/catalog.php:1314 +msgid "songs found checking tag information." +msgstr "" + +#: ../../modules/class/stream.php:192 +msgid "Opened for writting" +msgstr "" + +#: ../../modules/class/stream.php:197 +msgid "Error, cannot write" +msgstr "" + +#: ../../modules/class/stream.php:208 +msgid "Error, cannot write song in file" +msgstr "" + +#: ../../modules/class/stream.php:214 +msgid "Closed after write" +msgstr "" + +#: ../../modules/class/album.php:126 +msgid "Various" +msgstr "" + +#: ../../modules/class/song.php:246 +#, fuzzy +msgid "Title" +msgstr "Seitentitel" + +#: ../../modules/class/song.php:246 ../../modules/class/song.php:250 +#: ../../modules/class/song.php:254 ../../modules/class/song.php:258 +#: ../../modules/class/song.php:262 ../../modules/class/song.php:266 +#: ../../modules/class/song.php:270 ../../modules/class/song.php:275 +#: ../../modules/class/song.php:280 ../../modules/class/song.php:284 +#: ../../modules/class/song.php:288 ../../modules/class/song.php:293 +#, fuzzy +msgid "updated to" +msgstr "Katalog aktualisieren" + +#: ../../modules/class/song.php:254 +#, fuzzy +msgid "Rate" +msgstr "Entfernt" + +#: ../../modules/class/song.php:258 +#, fuzzy +msgid "Mode" +msgstr "Demo Modus" + +#: ../../modules/class/song.php:266 ../../templates/show_songs.inc:32 +#: ../../templates/show_songs.inc:36 +msgid "Track" +msgstr "Lied" + +#: ../../modules/class/song.php:270 +msgid "Filesize" +msgstr "" + +#: ../../modules/class/song.php:284 ../../templates/show_albums.inc:43 +#: ../../templates/show_albums.inc:72 +#, fuzzy +msgid "Year" +msgstr "Jahr" + +#: ../../modules/class/song.php:288 ../../templates/flag.inc:66 +#: ../../templates/list_flagged.inc:46 +#, fuzzy +msgid "Comment" +msgstr "id3-Kommentar" + +#: ../../modules/lib.php:51 +msgid "day" +msgstr "Tag" + +#: ../../modules/lib.php:54 +msgid "days" +msgstr "Tage" + +#: ../../modules/lib.php:58 +msgid "hour" +msgstr "Stunde" + +#: ../../modules/lib.php:61 +msgid "hours" +msgstr "Stunden" + +#: ../../modules/lib.php:78 +msgid "Catalog Statistics" +msgstr "Katalogstatistik" + +#: ../../modules/lib.php:81 +msgid "Total Users" +msgstr "Gesamtzahl Benutzer" + +#: ../../modules/lib.php:85 +msgid "Connected Users" +msgstr "Verbundene Benutzer" + +#: ../../modules/lib.php:89 ../../templates/show_artists.inc:42 +#: ../../templates/show_artists.inc:65 ../../templates/menu.inc:32 +msgid "Albums" +msgstr "Alben" + +#: ../../modules/lib.php:93 ../../templates/menu.inc:33 +msgid "Artists" +msgstr "Interpreten" + +#: ../../modules/lib.php:97 ../../templates/show_artists.inc:41 +#: ../../templates/show_artists.inc:64 ../../templates/show_albums.inc:41 +#: ../../templates/show_albums.inc:71 +msgid "Songs" +msgstr "Lieder" + +#: ../../modules/lib.php:101 +msgid "Catalog Size" +msgstr "Kataloggröße" + +#: ../../modules/lib.php:105 +msgid "Catalog Time" +msgstr "Katalog Spielzeit" + +#: ../../modules/lib.php:159 +msgid "Play Random Selection" +msgstr "Spiele Zufallsauswahl" + +#: ../../modules/lib.php:166 +#, fuzzy +msgid "Item count" +msgstr "Liederanzahl" + +#: ../../modules/lib.php:178 ../../templates/show_artists.inc:55 +#: ../../templates/show_albums.inc:58 +msgid "All" +msgstr "Alle" + +#: ../../modules/lib.php:180 +#, fuzzy +msgid "From genre" +msgstr "vom Genre" + +#: ../../modules/lib.php:190 +#, fuzzy +msgid "Favor Unplayed" +msgstr "nicht gespielte vorziehen" + +#: ../../modules/lib.php:191 +#, fuzzy +msgid "Full Albums" +msgstr "Alle Alben" + +#: ../../modules/lib.php:192 +#, fuzzy +msgid "Full Artist" +msgstr "Interpret" + +#: ../../modules/lib.php:201 +msgid "from catalog" +msgstr "vom Katalog" + +#: ../../modules/lib.php:212 +msgid "Play Random Songs" +msgstr "Spiele zufällige Lieder" + +#: ../../modules/lib.php:921 +msgid "Public" +msgstr "öffentliche" + +#: ../../modules/lib.php:922 +msgid "Your Private" +msgstr "Deine private" + +#: ../../modules/lib.php:923 +msgid "Other Private" +msgstr "Andere private" + +#: ../../modules/lib.php:1004 ../../templates/show_songs.inc:168 +#: ../../templates/show_artist.inc:102 +msgid "View" +msgstr "Anschauen" + +#: ../../modules/lib.php:1022 ../../templates/show_songs.inc:110 +#: ../../templates/show_album.inc:61 ../../templates/show_albums.inc:61 +#: ../../templates/show_artist.inc:81 +msgid "Download" +msgstr "Download" + +#: ../../modules/lib.php:1033 +msgid "There are no playlists of this type" +msgstr "Keine Playliste von diesem Typ verhanden" + +#: ../../modules/lib.php:1061 +msgid "Create a new playlist" +msgstr "Neue Playlist erstellen" + +#: ../../modules/admin.php:45 +#, fuzzy +msgid "Manage Users" +msgstr "Den Benutzern mailen" + +#: ../../modules/admin.php:47 +#, fuzzy +msgid "Add a new user" +msgstr "Rechner hinzufügen" + +#: ../../templates/catalog.inc:33 +msgid "" +"Error: ICONV not found, ID3V2 Tags will not import correctly. See <a href=" +"\"http://php.oregonstate.edu/iconv\">Iconv</a> for information on getting " +"ICONV" +msgstr "" +"Fehler: ICONV nicht gefunden, die ID3V Tags werden nicht korrekt importiert " +"werden. Besuchen sie die <a href=\"http://php.oregonstate.edu/iconv\">Iconv-" +"Seite</a> um an Informationen zu ICONV zu gelangen." + +#: ../../templates/catalog.inc:42 +msgid "Update Catalogs" +msgstr "Katalog aktualisieren" + +#: ../../templates/catalog.inc:68 +msgid "Fast Add" +msgstr "Schnelles Hinzufügen" + +#: ../../templates/catalog.inc:75 +msgid "Fast Update" +msgstr "Schnelles Aktualisieren" + +#: ../../templates/catalog.inc:88 +msgid "You don't have any catalogs." +msgstr "Sie haben keine Kataloge" + +#: ../../templates/catalog.inc:97 +msgid "Add a catalog" +msgstr "Katalog hinzufügen" + +#: ../../templates/catalog.inc:98 ../../templates/show_admin_index.inc:36 +#: ../../templates/admin_menu.inc:37 +msgid "Access Lists" +msgstr "Zugriffsliste" + +#: ../../templates/catalog.inc:99 +msgid "Show Duplicate Songs" +msgstr "Doppelte Songs anzeigen" + +#: ../../templates/catalog.inc:100 +msgid "Show Disabled Songs" +msgstr "Gesperrte Songs anzeigen" + +#: ../../templates/catalog.inc:101 +msgid "Clear Catalog Stats" +msgstr "Katalogstatistiken löschen" + +#: ../../templates/catalog.inc:102 +msgid "Clear Now Playing" +msgstr "Die aktuelle Songanzeige löschen" + +#: ../../templates/catalog.inc:103 +#, fuzzy +msgid "Dump Album Art" +msgstr "Cover finden" + +#: ../../templates/catalog.inc:104 +msgid "View flagged songs" +msgstr "Markierte Songs betrachten" + +#: ../../templates/catalog.inc:105 +msgid "Catalog Tools" +msgstr "Katalogwerkzeuge" + +#: ../../templates/flag.inc:43 +#, fuzzy +msgid "Flag song" +msgstr "Markieren" + +#: ../../templates/flag.inc:45 +msgid "" +"Flag the following song as having one of the problems listed below. Site " +"admins will then take the appropriate action for the flagged files." +msgstr "" + +#: ../../templates/flag.inc:62 +msgid "Reason to flag" +msgstr "" + +#: ../../templates/flag.inc:73 +#, fuzzy +msgid "Flag Song" +msgstr "Alle Lieder" + +#: ../../templates/customize_catalog.inc:24 +msgid "Settings for catalog in" +msgstr "" + +#: ../../templates/customize_catalog.inc:32 ../../templates/add_catalog.inc:39 +msgid "Auto-inserted Fields" +msgstr "Automatisch ausgefüllte Felder" + +#: ../../templates/customize_catalog.inc:33 ../../templates/add_catalog.inc:40 +msgid "album name" +msgstr "Albumname" + +#: ../../templates/customize_catalog.inc:34 ../../templates/add_catalog.inc:41 +msgid "artist name" +msgstr "Interpretenname" + +#: ../../templates/customize_catalog.inc:35 +msgid "catalog path" +msgstr "Katalogpfad" + +#: ../../templates/customize_catalog.inc:36 ../../templates/add_catalog.inc:42 +msgid "id3 comment" +msgstr "id3-Kommentar" + +#: ../../templates/customize_catalog.inc:37 ../../templates/add_catalog.inc:43 +msgid "genre" +msgstr "Genre" + +#: ../../templates/customize_catalog.inc:38 ../../templates/add_catalog.inc:44 +msgid "track number (padded with leading 0)" +msgstr "Titelummer (Mit einer 0 am Anfang)" + +#: ../../templates/customize_catalog.inc:39 ../../templates/add_catalog.inc:45 +msgid "song title" +msgstr "Titel" + +#: ../../templates/customize_catalog.inc:40 ../../templates/add_catalog.inc:46 +msgid "year" +msgstr "Jahr" + +#: ../../templates/customize_catalog.inc:41 ../../templates/add_catalog.inc:47 +#, fuzzy +msgid "other" +msgstr "Cover" + +#: ../../templates/customize_catalog.inc:45 +#, fuzzy +msgid "ID3 set command" +msgstr "Kommando zum Setzen der ID3-Tags" + +#: ../../templates/customize_catalog.inc:51 +#, fuzzy +msgid "Filename pattern" +msgstr "Dateinamenmuster" + +#: ../../templates/customize_catalog.inc:58 ../../templates/add_catalog.inc:74 +#, fuzzy +msgid "Folder Pattern" +msgstr "Dateinamenmuster" + +#: ../../templates/customize_catalog.inc:58 ../../templates/add_catalog.inc:74 +#, fuzzy +msgid "(no leading or ending '/')" +msgstr "Sortiermuster: (Ohne '/' an Anfang oder Ende" + +#: ../../templates/customize_catalog.inc:69 +#, fuzzy +msgid "Save Catalog Settings" +msgstr "Katalogstatistiken löschen" + +#: ../../templates/show_test.inc:29 +msgid "Ampache Debug" +msgstr "Ampache Debug" + +#: ../../templates/show_test.inc:30 +msgid "" +"You've reached this page because a configuration error has occured. Debug " +"Information below" +msgstr "" +"Sie sind auf dieser Seite gelandet, weil ein Konfigurationsfehler " +"aufgetreten ist. Debuginformationen erhalten sie unterhalb dieses Textes." + +#: ../../templates/show_test.inc:35 +msgid "STATUS" +msgstr "Status" + +#: ../../templates/show_test.inc:39 +msgid "PHP Version" +msgstr "PHP Version" + +#: ../../templates/show_test.inc:54 +msgid "" +"This tests to make sure that you are running a version of PHP that is known " +"to work with Ampache." +msgstr "" +"Dieser Test überprüft, ob sie eine PHP Version benutzen, mit der Ampache " +"funktioniert." + +#: ../../templates/show_test.inc:58 +msgid "Mysql for PHP" +msgstr "" + +#: ../../templates/show_test.inc:73 +msgid "" +"This test checks to see if you have the mysql extensions loaded for PHP. " +"These are required for Ampache to work." +msgstr "" + +#: ../../templates/show_test.inc:77 +#, fuzzy +msgid "PHP Session Support" +msgstr "PHP Version" + +#: ../../templates/show_test.inc:92 +msgid "" +"This test checks to make sure that you have PHP session support enabled. " +"Sessions are required for Ampache to work." +msgstr "" + +#: ../../templates/show_test.inc:96 +#, fuzzy +msgid "PHP ICONV Support" +msgstr "PHP Version" + +#: ../../templates/show_test.inc:110 +msgid "" +"This test checks to make sure you have Iconv support installed. Iconv " +"support is not required for Ampache, but it is highly recommended" +msgstr "" + +#: ../../templates/show_test.inc:114 +#: ../../templates/show_install_config.inc:88 +msgid "Ampache.cfg Exists" +msgstr "Ampache.cfg vorhanden" + +#: ../../templates/show_test.inc:129 +msgid "" +"This attempts to read /config/ampache.cfg If this fails either the ampache." +"cfg is not in the correct locations or\n" +"\tit is not currently readable by your webserver." +msgstr "" +"Hier versuchen wir /config/ampache.cfg zu lesen. Wenn dies fehlschläft ist " +"die Datei entweder nicht an der richtigen Stelle, oder sie ist nicht lesbar " +"(Dateirechte?)" + +#: ../../templates/show_test.inc:135 +#: ../../templates/show_install_config.inc:105 +msgid "Ampache.cfg Configured?" +msgstr "Ampache.cfg konfiguriert?" + +#: ../../templates/show_test.inc:152 +msgid "" +"This test makes sure that you have set all of the required config variables " +"and that we are able to \n" +"\tcompleatly parse your config file" +msgstr "" +"Hier versuchen wir, dass alle erforderlichen Konfigurationsvariablen gesetzt " +"sind, und dass wir ihre Konfigurationsdatei lesen können." + +#: ../../templates/show_test.inc:159 +#, fuzzy +msgid "Ampache.cfg Up to Date?" +msgstr "Ampache.cfg konfiguriert?" + +#: ../../templates/show_test.inc:165 +msgid "DB Connection" +msgstr "Datenbank Verbindung" + +#: ../../templates/show_test.inc:181 +msgid "" +"This attempts to connect to your database using the values from your ampache." +"cfg" +msgstr "" +"Hier versuchen wir, uns mit den Werten aus ihrer ampache.cfg zur Datenbank " +"zu verbinden." + +#: ../../templates/show_admin_index.inc:30 +msgid "Admin Section" +msgstr "Administrator Bereich" + +#: ../../templates/show_admin_index.inc:32 ../../templates/admin_menu.inc:33 +msgid "Users" +msgstr "Benutzer" + +#: ../../templates/show_admin_index.inc:32 +msgid "Create/Modify User Accounts for Ampache" +msgstr "Erstelle/Verändere Ampache-Benutzerkontos" + +#: ../../templates/show_admin_index.inc:33 +msgid "Mail" +msgstr "" + +#: ../../templates/show_admin_index.inc:33 +msgid "Mail your users to notfiy them of changes" +msgstr "Notiz über Neuerungen den Benutzern mailen" + +#: ../../templates/show_admin_index.inc:34 +msgid "Create/Update/Clean your catalog here" +msgstr "Erstelle/Aktuallisiere/Säubere deinen Katalog hier" + +#: ../../templates/show_admin_index.inc:35 ../../templates/admin_menu.inc:36 +msgid "Admin Preferences" +msgstr "Admineinstellungen" + +#: ../../templates/show_admin_index.inc:35 +#, fuzzy +msgid "Modify Site-wide preferences" +msgstr "Einstellungen aktualisieren" + +#: ../../templates/show_admin_index.inc:36 +msgid "Modify Access List Permissions" +msgstr "Einstellungen Zugriffsrechteliste" + +#: ../../templates/show_admin_index.inc:36 +msgid "Must have access_control=true in ampache.cfg" +msgstr "access_control muss auf true in der ampache.cfg stehen" + +#: ../../templates/show_localplay.inc:30 +msgid "Local Play Control" +msgstr "" + +#: ../../templates/show_localplay.inc:35 +#, fuzzy +msgid "Playback" +msgstr "Abspielen" + +#: ../../templates/show_localplay.inc:39 ../../templates/list_header.inc:69 +#: ../../templates/show_mpdplay.inc:43 +msgid "Prev" +msgstr "Vorherige" + +#: ../../templates/show_localplay.inc:40 ../../templates/show_mpdplay.inc:44 +msgid "Stop" +msgstr "" + +#: ../../templates/show_localplay.inc:42 ../../templates/show_mpdplay.inc:46 +#, fuzzy +msgid "Pause" +msgstr "Wert" + +#: ../../templates/show_localplay.inc:43 ../../templates/list_header.inc:92 +#: ../../templates/show_mpdplay.inc:47 +msgid "Next" +msgstr "Nächste" + +#: ../../templates/show_localplay.inc:49 +#, fuzzy +msgid "Volume" +msgstr "Wert" + +#: ../../templates/show_localplay.inc:53 ../../templates/show_localplay.inc:54 +msgid "Increase Volume" +msgstr "" + +#: ../../templates/show_localplay.inc:55 ../../templates/show_localplay.inc:56 +msgid "Decrease Volume" +msgstr "" + +#: ../../templates/show_localplay.inc:62 +msgid "Clear queue" +msgstr "" + +#: ../../templates/add_catalog.inc:28 +#, fuzzy +msgid "Add a Catalog" +msgstr "Katalog hinzufügen" + +#: ../../templates/add_catalog.inc:30 +msgid "" +"In the form below enter either a local path (i.e. /data/music) or the URL to " +"a remote Ampache installation (i.e http://theotherampache.com)" +msgstr "" + +#: ../../templates/add_catalog.inc:36 +#, fuzzy +msgid "Catalog Name" +msgstr "Katalogname" + +#: ../../templates/add_catalog.inc:53 +msgid "Path" +msgstr "Pfad" + +#: ../../templates/add_catalog.inc:57 +#, fuzzy +msgid "Catalog Type" +msgstr "Katalogtyp" + +#: ../../templates/add_catalog.inc:61 +msgid "Remote" +msgstr "Entfernt" + +#: ../../templates/add_catalog.inc:66 +msgid "ID3 Set Command" +msgstr "Kommando zum Setzen der ID3-Tags" + +#: ../../templates/add_catalog.inc:70 +msgid "Filename Pattern" +msgstr "Dateinamenmuster" + +#: ../../templates/add_catalog.inc:78 +msgid "Gather Album Art" +msgstr "Nach Covern suchen" + +#: ../../templates/add_catalog.inc:82 +msgid "ID3V2 Tags" +msgstr "ID3V2 Tags" + +#: ../../templates/add_catalog.inc:85 +msgid "Amazon" +msgstr "Amazon" + +#: ../../templates/add_catalog.inc:88 +#, fuzzy +msgid "File Folder" +msgstr "MP3 Verzeichnis" + +#: ../../templates/add_catalog.inc:98 +#, fuzzy +msgid "Add Catalog" +msgstr "Katalog hinzufügen" + +#: ../../templates/list_flagged.inc:42 ../../templates/show_songs.inc:41 +msgid "Flag" +msgstr "Markieren" + +#: ../../templates/list_flagged.inc:43 +#, fuzzy +msgid "New Flag" +msgstr "Markieren" + +#: ../../templates/list_flagged.inc:44 +msgid "Flagged by" +msgstr "" + +#: ../../templates/list_flagged.inc:45 +#, fuzzy +msgid "ID3 Update" +msgstr "Schnelles Aktualisieren" + +#: ../../templates/list_flagged.inc:69 +#, fuzzy +msgid "Accept" +msgstr "Account" + +#: ../../templates/list_flagged.inc:70 +#, fuzzy +msgid "Reject" +msgstr "Auswahl" + +#: ../../templates/show_songs.inc:33 +msgid "Song title" +msgstr "Songname" + +#: ../../templates/show_songs.inc:114 +msgid "Direct Link" +msgstr "" + +#: ../../templates/show_songs.inc:133 +msgid "Total" +msgstr "Insgesamt" + +#: ../../templates/show_songs.inc:159 +msgid "Set Track Numbers" +msgstr "Tracknummern setzen" + +#: ../../templates/show_songs.inc:160 +msgid "Remove Selected Tracks" +msgstr "Ausgwählte Lieder entfernen" + +#: ../../templates/show_songs.inc:166 ../../templates/show_artist.inc:100 +#, fuzzy +msgid "Add to" +msgstr "Katalog hinzufügen" + +#: ../../templates/show_artists.inc:56 ../../templates/show_albums.inc:59 +msgid "Random" +msgstr "Zufällig" + +#: ../../templates/show_install_config.inc:52 +msgid "" +"This steps takes the basic config values, and first attempts to write them " +"out directly to your webserver. If access is denied it will prompt you to " +"download the config file. Please put the downloaded config file in /config" +msgstr "" + +#: ../../templates/show_install_config.inc:60 +#, fuzzy +msgid "Web Path" +msgstr "Pfad" + +#: ../../templates/show_install_config.inc:64 +#: ../../templates/show_install.inc:58 +msgid "Desired Database Name" +msgstr "" + +#: ../../templates/show_install_config.inc:68 +#: ../../templates/show_install.inc:62 +msgid "MySQL Hostname" +msgstr "" + +#: ../../templates/show_install_config.inc:72 +#, fuzzy +msgid "MySQL Username" +msgstr "Benutzer" + +#: ../../templates/show_install_config.inc:76 +#, fuzzy +msgid "MySQL Password" +msgstr "Passwort" + +#: ../../templates/show_install_config.inc:81 +msgid "Write Config" +msgstr "" + +#: ../../templates/show_install_config.inc:125 +msgid "Check for Config" +msgstr "" + +#: ../../templates/show_album.inc:53 +msgid "Play Album" +msgstr "Album abspielen" + +#: ../../templates/show_album.inc:54 +msgid "Play Random from Album" +msgstr "Zufällig vom Album" + +#: ../../templates/show_album.inc:55 +msgid "Reset Album Art" +msgstr "Cover zurücksetzen" + +#: ../../templates/show_album.inc:56 +msgid "Find Album Art" +msgstr "Cover finden" + +#: ../../templates/show_album.inc:58 ../../templates/show_artist.inc:37 +msgid "Update from tags" +msgstr "Aktuallisieren vom TAG" + +#: ../../templates/show_preferences.inc:31 +msgid "Editing" +msgstr "Editieren" + +#: ../../templates/show_preferences.inc:31 +msgid "preferences" +msgstr "Einstellungen" + +#: ../../templates/show_preferences.inc:33 +#, fuzzy +msgid "Rebuild Preferences" +msgstr "Einstellungen" + +#: ../../templates/show_preferences.inc:39 +msgid "Preference" +msgstr "Einstellung" + +#: ../../templates/show_preferences.inc:40 +msgid "Value" +msgstr "Wert" + +#: ../../templates/show_preferences.inc:42 +msgid "Type" +msgstr "Typ" + +#: ../../templates/show_preferences.inc:43 +msgid "Apply to All" +msgstr "" + +#: ../../templates/show_preferences.inc:58 +msgid "Update Preferences" +msgstr "Einstellungen aktualisieren" + +#: ../../templates/show_preferences.inc:62 +msgid "Cancel" +msgstr "Abbrechen" + +#: ../../templates/userform.inc:25 +#, fuzzy +msgid "Adding a New User" +msgstr "Rechner hinzufügen" + +#: ../../templates/userform.inc:29 +msgid "Editing existing User" +msgstr "" + +#: ../../templates/userform.inc:81 +msgid "User Access Level" +msgstr "" + +#: ../../templates/userform.inc:98 +#, fuzzy +msgid "Add User" +msgstr "Rechner hinzufügen" + +#: ../../templates/userform.inc:103 +#, fuzzy +msgid "Update User" +msgstr "Einstellungen aktualisieren" + +#: ../../templates/show_install.inc:40 +msgid "Your webserver configured so that <ampache_root>/docs is your webroot" +msgstr "" + +#: ../../templates/show_install.inc:49 +msgid "" +"This step creates and inserts the Ampache database, as such please provide a " +"mysql account with database creation rights. This step may take a while " +"depending upon the speed of your computer" +msgstr "" + +#: ../../templates/show_install.inc:66 +msgid "MySQL Administrative Username" +msgstr "" + +#: ../../templates/show_install.inc:70 +msgid "MySQL Administrative Password" +msgstr "" + +#: ../../templates/show_install.inc:75 +msgid "Insert Database" +msgstr "" + +#: ../../templates/show_add_access.inc:31 +msgid "Add Access for a Host" +msgstr "Zugang für einen Rechner einrichten" + +#: ../../templates/show_add_access.inc:33 +msgid "" +"Use the form below to add a host that you want to have access to your " +"Ampache catalog." +msgstr "" +"Um einem Rechner den Zugang zum Ampache-Katalog zu gewähren, füllen sie das " +"Formular mit den entsprechenden Daten aus" + +#: ../../templates/show_add_access.inc:46 +msgid "Start IP Address" +msgstr "Start IP Adresse" + +#: ../../templates/show_add_access.inc:52 +msgid "End IP Address" +msgstr "End IP Adresse" + +#: ../../templates/show_add_access.inc:58 +#: ../../templates/show_access_list.inc:50 +msgid "Level" +msgstr "Level" + +#: ../../templates/show_add_access.inc:72 +msgid "Add Host" +msgstr "Rechner hinzufügen" + +#: ../../templates/admin_menu.inc:34 +msgid "Mail Users" +msgstr "Den Benutzern mailen" + +#: ../../templates/show_upload.inc:27 +msgid "Please Ensure All Files Are Tagged Correctly" +msgstr "" + +#: ../../templates/show_upload.inc:30 +msgid "" +"Ampache relies on id3 tags to sort data. If your file is not tagged it may " +"be deleted." +msgstr "" + +#: ../../templates/show_upload.inc:34 +msgid "max_upload_size" +msgstr "" + +#: ../../templates/show_upload.inc:59 ../../templates/menu.inc:39 +msgid "Upload" +msgstr "Upload" + +#: ../../templates/show_users.inc:42 +#, fuzzy +msgid "Fullname" +msgstr "Playlistenname" + +#: ../../templates/show_users.inc:47 +msgid "Last Seen" +msgstr "" + +#: ../../templates/show_users.inc:54 +#, fuzzy +msgid "Prefs" +msgstr "Vorherige" + +#: ../../templates/show_users.inc:60 +#, fuzzy +msgid "Set Access" +msgstr "Start Adresse" + +#: ../../templates/show_users.inc:66 +msgid "On-line" +msgstr "" + +#: ../../templates/show_users.inc:88 +#, fuzzy +msgid "edit" +msgstr "Editieren" + +#: ../../templates/show_users.inc:93 +#, fuzzy +msgid "prefs" +msgstr "Einstellungen" + +#: ../../templates/show_users.inc:98 +#, fuzzy +msgid "delete" +msgstr "Löschen" + +#: ../../templates/show_users.inc:104 ../../templates/show_users.inc:113 +#: ../../templates/show_users.inc:116 +msgid "set to user" +msgstr "" + +#: ../../templates/show_users.inc:105 ../../templates/show_users.inc:109 +#: ../../templates/show_users.inc:117 +msgid "disable" +msgstr "" + +#: ../../templates/show_users.inc:108 ../../templates/show_users.inc:112 +msgid "set to admin" +msgstr "" + +#: ../../templates/show_now_playing.inc:34 ../../templates/show_mpdplay.inc:68 +msgid "Now Playing" +msgstr "Aktuell wird gespielt" + +#: ../../templates/show_login_form.inc:40 +#: ../../templates/show_login_form.inc:54 +msgid "Login" +msgstr "Benutzer" + +#: ../../templates/show_login_form.inc:50 +msgid "Remember Me" +msgstr "" + +#: ../../templates/show_access_list.inc:34 +msgid "Host Access to Your Catalog" +msgstr "Zugangsliste zum Katalog" + +#: ../../templates/show_access_list.inc:43 +msgid "Add Entry" +msgstr "Eintrag hinzufügen" + +#: ../../templates/show_access_list.inc:48 +msgid "Start Address" +msgstr "Start Adresse" + +#: ../../templates/show_access_list.inc:49 +msgid "End Address" +msgstr "End Adresse" + +#: ../../templates/show_access_list.inc:65 +msgid "Revoke" +msgstr "Zurückziehen" + +#: ../../templates/menu.inc:31 +msgid "Home" +msgstr "Home" + +#: ../../templates/menu.inc:34 +msgid "Playlists" +msgstr "Playlists" + +#: ../../templates/menu.inc:35 ../../templates/show_search.inc:38 +#: ../../templates/show_search.inc:84 +msgid "Search" +msgstr "Suchen" + +#: ../../templates/menu.inc:36 +msgid "Preferences" +msgstr "Einstellungen" + +#: ../../templates/menu.inc:57 ../../templates/menu.inc:60 +msgid "Admin" +msgstr "Admin" + +#: ../../templates/menu.inc:69 ../../templates/menu.inc:76 +msgid "Account" +msgstr "Account" + +#: ../../templates/menu.inc:70 ../../templates/menu.inc:77 +msgid "Stats" +msgstr "Statistiken" + +#: ../../templates/menu.inc:71 ../../templates/menu.inc:78 +#: ../../templates/menu.inc:83 +msgid "Logout" +msgstr "Ausloggen" + +#: ../../templates/show_search.inc:35 +msgid "Search Ampache" +msgstr "In Ampache suchen" + +#: ../../templates/show_search.inc:42 +msgid "Object Type" +msgstr "Objektart" + +#: ../../templates/show_search.inc:75 +msgid "Search Type" +msgstr "Suchart" + +#: ../../templates/show_mpdplay.inc:33 +msgid "MPD Play Control" +msgstr "" + +#: ../../templates/show_mpdplay.inc:52 +#, fuzzy +msgid "Loop" +msgstr "Ausloggen" + +#: ../../templates/show_mpdplay.inc:54 +msgid "Loop is off" +msgstr "" + +#: ../../templates/show_mpdplay.inc:55 +msgid "Loop is on" +msgstr "" + +#: ../../templates/show_mpdplay.inc:81 +msgid "Refresh the Playlist Window" +msgstr "" + +#: ../../templates/show_mpdplay.inc:81 +msgid "refresh now" +msgstr "" + +#: ../../templates/show_mpdplay.inc:89 +#, fuzzy +msgid "Server Playlist" +msgstr "Playlist" + +#: ../../templates/show_mpdplay.inc:122 +msgid "Click to shuffle (randomize) the playlist" +msgstr "" + +#: ../../templates/show_mpdplay.inc:122 +msgid "shuffle" +msgstr "" + +#: ../../templates/show_mpdplay.inc:123 +#, fuzzy +msgid "Click the clear the playlist" +msgstr "Neue Playlist erstellen" + +#: ../../templates/show_mpdplay.inc:123 +#, fuzzy +msgid "clear" +msgstr "Jahr" + +#: ../../templates/show_artist.inc:31 +msgid "Albums by" +msgstr "Alben von" + +#: ../../templates/show_artist.inc:33 +msgid "Show All Songs By" +msgstr "Zeige alle Lieder von" + +#: ../../templates/show_artist.inc:34 +msgid "Play All Songs By" +msgstr "Spiele alle Lieder von" + +#: ../../templates/show_artist.inc:35 +msgid "Play Random Songs By" +msgstr "Spiele Zufallsauswahl von" + +#: ../../templates/show_artist.inc:50 +msgid "Select" +msgstr "Auswahl" + +#: ../../templates/show_artist.inc:52 +msgid "Cover" +msgstr "Cover" + +#: ../../templates/show_artist.inc:53 +msgid "Album Name" +msgstr "Album Name" + +#: ../../templates/show_artist.inc:54 +msgid "Album Year" +msgstr "Album Jahr" + +#: ../../templates/show_artist.inc:55 +msgid "Total Tracks" +msgstr "Anzahl Stücke" + +#~ msgid "In the form below enter either a local path entry (i.e. /data/music)" +#~ msgstr "Hier bitte den Pfad zur Musik angeben (z.B. /daten/musik)" + +#~ msgid "current filename" +#~ msgstr "Momentaner Dateiname" + +#~ msgid "Remove Dead Songs from DB" +#~ msgstr "Alte Songs entfernen" + +#~ msgid "Sorry, you don't have access to that part of the server." +#~ msgstr "" +#~ "Entschuldigung, sie haben keinen Zugriff auf diesen Teil des Servers." + +#~ msgid "All Albums starting with" +#~ msgstr "Alle Alben, beginnend mit" + +#~ msgid "Actions" +#~ msgstr "Aktionen" + +#~ msgid "No Songs Removed" +#~ msgstr "Keine Lieder gelöscht" + +#~ msgid "No Disabled songs found" +#~ msgstr "Keine 'abgeschalteten' Lieder gefunden" + +#~ msgid "Play Type" +#~ msgstr "Abspielart" + +#~ msgid "Popular Threshold" +#~ msgstr "Weiter" + +#~ msgid "Bg Color1" +#~ msgstr "Hg Farbe 1" + +#~ msgid "Bg Color2" +#~ msgstr "Hg Farbe 2" + +#~ msgid "Base Color1" +#~ msgstr "Grundfarbe 1" + +#~ msgid "Base Color2" +#~ msgstr "Grundfarbe 2" + +#~ msgid "Font Color1" +#~ msgstr "Schriftfarbe 1" + +#~ msgid "Font Color2" +#~ msgstr "Schriftfarbe 2" + +#~ msgid "Font Color3" +#~ msgstr "Schriftfarbe 3" + +#~ msgid "Row Color1" +#~ msgstr "Zeilenfarbe 1" + +#~ msgid "Row Color2" +#~ msgstr "Zeilenfarbe 2" + +#~ msgid "Row Color3" +#~ msgstr "Zeilenfarbe 3" + +#~ msgid "Font Size" +#~ msgstr "Schriftgröße" + +#~ msgid "Upload Dir" +#~ msgstr "Upload Verzeichnis" + +#~ msgid "Sample Rate" +#~ msgstr "Sample Rate" + +#~ msgid "Access Control" +#~ msgstr "Zugangskontrolle" + +#~ msgid "Album Cache Limit" +#~ msgstr "Album Cache Begrenzung" + +#~ msgid "Artist Cache Limit" +#~ msgstr "Interpret Cache Begrenzung" + +#~ msgid "Catalog Echo Count" +#~ msgstr "Catalog Echo Count" + +#~ msgid "Do Mp3 Md5" +#~ msgstr "Do Mp3 Md5" + +#~ msgid "Force Http Play" +#~ msgstr "Unterdrücke HTTP abspielen" + +#~ msgid "Http Port" +#~ msgstr "HTTP Port" + +#~ msgid "Local Length" +#~ msgstr "Local Length" + +#~ msgid "Lock Songs" +#~ msgstr "Lied sperren" + +#~ msgid "No Symlinks" +#~ msgstr "Keine Symb. Links" + +#~ msgid "Refresh Limit" +#~ msgstr "Wiederholungsrate" + +#~ msgid "Use Auth" +#~ msgstr "Benutzer Authentifizierung" + +#~ msgid "Xml Rpc" +#~ msgstr "Xml Rpc" diff --git a/locale/fr_FR/LC_MESSAGES/messages.mo b/locale/fr_FR/LC_MESSAGES/messages.mo Binary files differnew file mode 100644 index 00000000..d9f677a1 --- /dev/null +++ b/locale/fr_FR/LC_MESSAGES/messages.mo diff --git a/locale/fr_FR/LC_MESSAGES/messages.po b/locale/fr_FR/LC_MESSAGES/messages.po new file mode 100644 index 00000000..3479a19a --- /dev/null +++ b/locale/fr_FR/LC_MESSAGES/messages.po @@ -0,0 +1,1847 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: v0.1a\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-04-17 17:14-0700\n" +"PO-Revision-Date: 2004-11-12 13:16-1000\n" +"Last-Translator: HAUTZ Gilles <cocobu@mail.pf>\n" +"Language-Team: FRENCH\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../../docs/admin/catalog.php:54 ../../templates/catalog.inc:66 +msgid "Add to Catalog(s)" +msgstr "Ajouter au(x) Catalogue(s)" + +#: ../../docs/admin/catalog.php:65 ../../templates/catalog.inc:67 +msgid "Add to all Catalogs" +msgstr "Ajouter a tous les Catalogues" + +#: ../../docs/admin/catalog.php:75 ../../templates/catalog.inc:73 +msgid "Update Catalog(s)" +msgstr "Mettre a jour les Catalogues" + +#: ../../docs/admin/catalog.php:86 ../../templates/catalog.inc:74 +msgid "Update All Catalogs" +msgstr "Tout mettre a jour" + +#: ../../docs/admin/catalog.php:118 ../../templates/catalog.inc:80 +msgid "Clean Catalog(s)" +msgstr "Nettoyer le(s) Catalogue(s)" + +#: ../../docs/admin/catalog.php:148 ../../templates/catalog.inc:81 +msgid "Clean All Catalogs" +msgstr "Nettoyer tous les Catalogues" + +#: ../../docs/admin/catalog.php:196 +#, fuzzy +msgid "Now Playing Cleared" +msgstr "Effacer Lecture en cours" + +#: ../../docs/admin/catalog.php:196 +msgid "All now playing data has been cleared" +msgstr "" + +#: ../../docs/admin/catalog.php:201 +msgid "Do you really want to clear your catalog?" +msgstr "" + +#: ../../docs/admin/catalog.php:208 +msgid "Do you really want to clear the statistics for this catalog?" +msgstr "" + +#: ../../docs/admin/catalog.php:226 +msgid "Do you really want to delete this catalog?" +msgstr "" + +#: ../../docs/admin/users.php:76 ../../docs/admin/users.php:123 +msgid "Error Username Required" +msgstr "" + +#: ../../docs/admin/users.php:79 ../../docs/admin/users.php:120 +msgid "Error Passwords don't match" +msgstr "" + +#: ../../docs/admin/users.php:137 +msgid "Are you sure you want to permanently delete" +msgstr "" + +#: ../../docs/admin/users.php:144 +#: ../../templates/show_confirm_action.inc.php:29 +msgid "No" +msgstr "" + +#: ../../docs/admin/users.php:146 +#, fuzzy +msgid "User Deleted" +msgstr "Effacer" + +#: ../../docs/admin/users.php:149 +#, fuzzy +msgid "Delete Error" +msgstr "Effacer" + +#: ../../docs/admin/users.php:149 +msgid "Unable to delete last Admin User" +msgstr "" + +#: ../../docs/admin/access.php:43 +msgid "Do you really want to delete this Access Record?" +msgstr "" + +#: ../../docs/admin/access.php:51 +#, fuzzy +msgid "Entry Deleted" +msgstr "Effacer" + +#: ../../docs/admin/access.php:51 +msgid "Your Access List Entry has been removed" +msgstr "" + +#: ../../docs/admin/access.php:61 +msgid "Entry Added" +msgstr "" + +#: ../../docs/admin/access.php:61 +msgid "Your new Access List Entry has been created" +msgstr "" + +#: ../../docs/admin/song.php:70 +#, fuzzy +msgid "Songs Disabled" +msgstr "Voir les chansons desactivees" + +#: ../../docs/admin/song.php:70 +msgid "The requested song(s) have been disabled" +msgstr "" + +#: ../../docs/admin/song.php:80 +msgid "Songs Enabled" +msgstr "" + +#: ../../docs/admin/song.php:80 +msgid "The requested song(s) have been enabled" +msgstr "" + +#: ../../docs/play/index.php:46 +msgid "Session Expired: please log in again at" +msgstr "" + +#: ../../docs/artists.php:47 +msgid "All songs by" +msgstr "" + +#: ../../docs/artists.php:56 ../../docs/albums.php:103 +msgid "Starting Update from Tags" +msgstr "" + +#: ../../docs/artists.php:61 ../../docs/albums.php:108 +msgid "Update From Tags Compleate" +msgstr "" + +#: ../../docs/artists.php:62 ../../docs/albums.php:109 +#: ../../modules/class/catalog.php:615 +msgid "Return" +msgstr "" + +#: ../../docs/artists.php:73 ../../docs/artists.php:82 +#: ../../docs/artists.php:94 ../../docs/artists.php:111 +msgid "<u>S</u>how artists starting with" +msgstr "" + +#: ../../docs/amp-mpd.php:32 +msgid "Error Connecting" +msgstr "" + +#: ../../docs/playlist.php:71 ../../templates/show_songs.inc:151 +#: ../../templates/show_artist.inc:95 +msgid "Play Selected" +msgstr "" + +#: ../../docs/playlist.php:89 ../../templates/show_songs.inc:152 +msgid "Flag Selected" +msgstr "" + +#: ../../docs/playlist.php:95 ../../templates/show_songs.inc:153 +msgid "Edit Selected" +msgstr "" + +#: ../../docs/playlist.php:125 ../../modules/lib.php:1007 +#: ../../templates/show_songs.inc:169 ../../templates/show_users.inc:51 +#: ../../templates/show_artist.inc:103 +msgid "Edit" +msgstr "" + +#: ../../docs/playlist.php:128 ../../modules/lib.php:1016 +#: ../../templates/show_localplay.inc:41 ../../templates/show_artists.inc:54 +#: ../../templates/show_albums.inc:57 ../../templates/show_mpdplay.inc:45 +#: ../../templates/show_artist.inc:79 +msgid "Play" +msgstr "" + +#: ../../docs/playlist.php:140 +msgid "New Playlist" +msgstr "" + +#: ../../docs/playlist.php:198 +#, fuzzy +msgid "Playlist updated." +msgstr "Mise a jour rapide" + +#: ../../docs/playlist.php:305 +msgid "No songs in this playlist." +msgstr "" + +#: ../../docs/localplay.php:79 +msgid "Unknown action requested" +msgstr "" + +#: ../../docs/index.php:39 +msgid "Welcome to" +msgstr "" + +#: ../../docs/index.php:41 +msgid "you are currently logged in as" +msgstr "" + +#: ../../docs/index.php:72 +msgid "Most Popular Songs" +msgstr "" + +#: ../../docs/index.php:79 +msgid "Most Popular Artists" +msgstr "" + +#: ../../docs/index.php:91 +msgid "Newest Album Additions" +msgstr "" + +#: ../../docs/index.php:98 +msgid "Newest Artist Additions" +msgstr "" + +#: ../../docs/flag.php:35 +msgid "Flagging song completed." +msgstr "" + +#: ../../docs/albums.php:43 +msgid "Album Art Cleared" +msgstr "" + +#: ../../docs/albums.php:43 +msgid "Album Art information has been removed form the database" +msgstr "" + +#: ../../docs/albums.php:75 +msgid "Album Art Located" +msgstr "" + +#: ../../docs/albums.php:75 +msgid "" +"Album Art information has been located in Amazon. If incorrect, click " +"\"Reset Album Art\" below to remove the artwork." +msgstr "" + +#: ../../docs/albums.php:83 ../../docs/albums.php:93 +msgid "Get Art" +msgstr "" + +#: ../../docs/albums.php:87 +msgid "Album Art Not Located" +msgstr "" + +#: ../../docs/albums.php:87 +msgid "" +"Album Art could not be located at this time. This may be due to Amazon being " +"busy, or the album not being present in their collection." +msgstr "" + +#: ../../docs/albums.php:123 ../../docs/albums.php:138 +msgid "All Albums" +msgstr "" + +#: ../../docs/albums.php:124 ../../docs/albums.php:131 +msgid "<u>S</u>how all albums" +msgstr "" + +#: ../../docs/albums.php:130 +msgid "Albums with no artwork" +msgstr "" + +#: ../../docs/albums.php:139 ../../docs/albums.php:146 +#: ../../docs/albums.php:151 +msgid "<u>S</u>how only albums starting with" +msgstr "" + +#: ../../docs/albums.php:145 +msgid "Select a starting letter or Show all" +msgstr "" + +#: ../../docs/upload.php:124 +msgid "The uploaded file exceeds the upload_max_filesize directive in php.ini" +msgstr "" + +#: ../../docs/upload.php:127 +msgid "" +"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in " +"the HTML form." +msgstr "" + +#: ../../docs/upload.php:130 +msgid "The uploaded file was only partially uploaded." +msgstr "" + +#: ../../docs/upload.php:133 +msgid "No file was uploaded." +msgstr "" + +#: ../../docs/upload.php:136 +msgid "An Unknown Error has occured." +msgstr "" + +#: ../../docs/upload.php:157 +msgid "Successfully-Quarantined" +msgstr "" + +#: ../../docs/upload.php:167 +msgid "Successfully-Cataloged" +msgstr "" + +#: ../../docs/upload.php:229 ../../templates/show_songs.inc:42 +#: ../../templates/show_artists.inc:43 ../../templates/show_artists.inc:67 +#: ../../templates/show_albums.inc:45 ../../templates/show_albums.inc:73 +#: ../../templates/show_access_list.inc:51 ../../templates/show_artist.inc:56 +msgid "Action" +msgstr "" + +#: ../../docs/upload.php:230 ../../templates/flag.inc:58 +#: ../../templates/list_flagged.inc:41 +msgid "Song" +msgstr "" + +#: ../../docs/upload.php:231 ../../modules/class/song.php:275 +#: ../../templates/show_songs.inc:34 ../../templates/show_artists.inc:39 +#: ../../templates/show_artists.inc:62 ../../templates/show_albums.inc:40 +#: ../../templates/show_albums.inc:70 +msgid "Artist" +msgstr "" + +#: ../../docs/upload.php:232 ../../modules/class/song.php:280 +#: ../../templates/show_songs.inc:35 ../../templates/show_albums.inc:38 +#: ../../templates/show_albums.inc:68 +msgid "Album" +msgstr "" + +#: ../../docs/upload.php:233 ../../modules/class/song.php:293 +#: ../../templates/show_songs.inc:40 +msgid "Genre" +msgstr "" + +#: ../../docs/upload.php:234 ../../modules/class/song.php:262 +#: ../../templates/show_songs.inc:37 +msgid "Time" +msgstr "" + +#: ../../docs/upload.php:235 ../../modules/class/song.php:250 +#: ../../templates/show_songs.inc:39 +msgid "Bitrate" +msgstr "" + +#: ../../docs/upload.php:236 ../../templates/show_songs.inc:38 +msgid "Size" +msgstr "" + +#: ../../docs/upload.php:237 +msgid "Filename" +msgstr "" + +#: ../../docs/upload.php:238 +msgid "User" +msgstr "" + +#: ../../docs/upload.php:239 +#, fuzzy +msgid "Date" +msgstr "Effacer" + +#: ../../docs/upload.php:267 +msgid "Unknown" +msgstr "" + +#: ../../docs/upload.php:289 +#, fuzzy +msgid "Add" +msgstr "Ajouter un catalogue" + +#: ../../docs/upload.php:290 ../../modules/lib.php:1008 +#: ../../templates/catalog.inc:60 ../../templates/show_users.inc:57 +msgid "Delete" +msgstr "Effacer" + +#: ../../docs/upload.php:294 +msgid "Quarantined" +msgstr "" + +#: ../../templates/show_user_registration.inc.php:28 +#: ../../templates/show_install_account.inc.php:60 +#: ../../templates/userform.inc:41 ../../templates/show_users.inc:39 +msgid "Username" +msgstr "" + +#: ../../templates/show_user_registration.inc.php:36 +#: ../../templates/userform.inc:49 +msgid "Full Name" +msgstr "" + +#: ../../templates/show_user_registration.inc.php:44 +#: ../../templates/show_user.inc.php:40 ../../templates/userform.inc:56 +msgid "E-mail" +msgstr "" + +#: ../../templates/show_user_registration.inc.php:52 +#: ../../templates/show_install_account.inc.php:64 +#: ../../templates/userform.inc:64 ../../templates/show_login_form.inc:44 +msgid "Password" +msgstr "Mot de passe" + +#: ../../templates/show_user_registration.inc.php:60 +#: ../../templates/show_user.inc.php:75 ../../templates/userform.inc:73 +#, fuzzy +msgid "Confirm Password" +msgstr "Mot de passe" + +#: ../../templates/show_user_registration.inc.php:69 +msgid "Register User" +msgstr "" + +#: ../../templates/show_user.inc.php:31 +#: ../../templates/customize_catalog.inc:29 +#: ../../templates/show_add_access.inc:40 +#: ../../templates/show_access_list.inc:47 +msgid "Name" +msgstr "" + +#: ../../templates/show_user.inc.php:48 +msgid "View Limit" +msgstr "" + +#: ../../templates/show_user.inc.php:56 +#, fuzzy +msgid "Update Profile" +msgstr "Mettre a jour les Catalogues" + +#: ../../templates/show_user.inc.php:67 +#, fuzzy +msgid "Enter password" +msgstr "Mot de passe" + +#: ../../templates/show_user.inc.php:83 +#, fuzzy +msgid "Change Password" +msgstr "Mot de passe" + +#: ../../templates/show_user.inc.php:91 +#, fuzzy +msgid "Clear Stats" +msgstr "Effacer les Statistiques des Catalogues" + +#: ../../templates/show_install_account.inc.php:35 +#: ../../templates/show_install_config.inc:35 +#: ../../templates/show_install.inc:34 +msgid "Ampache Installation" +msgstr "" + +#: ../../templates/show_install_account.inc.php:37 +#: ../../templates/show_install_config.inc:37 +#: ../../templates/show_install.inc:36 +msgid "" +"This Page handles the installation of the ampache database and the creation " +"of the ampache.cfg file. Before you continue please make sure that you have " +"the following pre-requisits" +msgstr "" + +#: ../../templates/show_install_account.inc.php:40 +#: ../../templates/show_install_config.inc:40 +#: ../../templates/show_install.inc:39 +msgid "" +"A MySQL Server with a username and password that can create/modify databases" +msgstr "" + +#: ../../templates/show_install_account.inc.php:41 +#: ../../templates/show_install_config.inc:41 +msgid "" +"Your webserver configured so that your config directory is not visable to " +"the web" +msgstr "" + +#: ../../templates/show_install_account.inc.php:42 +#: ../../templates/show_install_config.inc:42 +#: ../../templates/show_install.inc:41 +msgid "" +"Your webserver has read access to the /sql/ampache.sql file and the /config/" +"ampache.cfg.dist file" +msgstr "" + +#: ../../templates/show_install_account.inc.php:44 +#: ../../templates/show_install_config.inc:44 +#: ../../templates/show_install.inc:43 +msgid "" +"Once you have ensured that you have the above requirements please fill out " +"the information below. You will only be asked for the required config " +"values. If you would like to make changes to your ampache install at a later " +"date simply edit /config/ampache.cfg" +msgstr "" + +#: ../../templates/show_install_account.inc.php:49 +#: ../../templates/show_install_config.inc:49 +#: ../../templates/show_install.inc:47 +msgid "Step 1 - Creating and Inserting the Ampache Database" +msgstr "" + +#: ../../templates/show_install_account.inc.php:50 +#: ../../templates/show_install_config.inc:50 +#: ../../templates/show_install.inc:51 +msgid "Step 2 - Creating the Ampache.cfg file" +msgstr "" + +#: ../../templates/show_install_account.inc.php:51 +#: ../../templates/show_install_config.inc:54 +#: ../../templates/show_install.inc:52 +msgid "Step 3 - Setup Initial Account" +msgstr "" + +#: ../../templates/show_install_account.inc.php:53 +msgid "" +"This step creates your initial Ampache admin account. Once your admin " +"account has been created you will be directed to the login page" +msgstr "" + +#: ../../templates/show_install_account.inc.php:69 +msgid "Create Account" +msgstr "" + +#: ../../templates/show_confirm_action.inc.php:28 +msgid "Yes" +msgstr "" + +#: ../../templates/show_confirmation.inc.php:30 +msgid "Continue" +msgstr "" + +#: ../../lib/preferences.php:199 +msgid "Enable" +msgstr "" + +#: ../../lib/preferences.php:200 ../../templates/show_users.inc:63 +msgid "Disable" +msgstr "" + +#: ../../lib/preferences.php:210 ../../templates/add_catalog.inc:60 +msgid "Local" +msgstr "" + +#: ../../lib/preferences.php:211 +msgid "Stream" +msgstr "" + +#: ../../lib/preferences.php:212 +msgid "IceCast" +msgstr "" + +#: ../../lib/preferences.php:213 +msgid "Downsample" +msgstr "" + +#: ../../lib/preferences.php:214 +msgid "Music Player Daemon" +msgstr "" + +#: ../../lib/preferences.php:221 +msgid "M3U" +msgstr "" + +#: ../../lib/preferences.php:222 +msgid "Simple M3U" +msgstr "" + +#: ../../lib/preferences.php:223 +msgid "PLS" +msgstr "" + +#: ../../lib/preferences.php:224 +msgid "Asx" +msgstr "" + +#: ../../lib/preferences.php:231 +msgid "English" +msgstr "" + +#: ../../lib/preferences.php:232 +msgid "German" +msgstr "" + +#: ../../lib/preferences.php:233 +msgid "French" +msgstr "" + +#: ../../lib/search.php:52 ../../lib/search.php:68 ../../lib/search.php:84 +#: ../../lib/search.php:100 ../../lib/search.php:116 ../../lib/search.php:133 +#: ../../lib/search.php:145 ../../lib/search.php:161 ../../lib/search.php:177 +msgid "No Results Found" +msgstr "" + +#: ../../lib/ui.php:180 ../../templates/show_songs.inc:166 +#: ../../templates/show_artist.inc:100 +msgid "Playlist" +msgstr "" + +#: ../../lib/ui.php:180 +msgid "New" +msgstr "" + +#: ../../lib/ui.php:181 +msgid "View All" +msgstr "" + +#: ../../lib/ui.php:305 +msgid "Show w/o art" +msgstr "" + +#: ../../lib/ui.php:307 +msgid "Show all" +msgstr "" + +#: ../../lib/mpd.php:31 ../../modules/class/catalog.php:876 +msgid "Error" +msgstr "" + +#: ../../lib/mpd.php:31 +msgid "Could not add" +msgstr "" + +#: ../../lib/Browser.php:867 +msgid "file" +msgstr "" + +#: ../../lib/Browser.php:871 +msgid "File uploads not supported." +msgstr "" + +#: ../../lib/Browser.php:889 +msgid "No file uploaded" +msgstr "" + +#: ../../lib/Browser.php:896 +#, php-format +msgid "There was a problem with the file upload: No %s was uploaded." +msgstr "" + +#: ../../lib/Browser.php:901 +#, php-format +msgid "" +"There was a problem with the file upload: The %s was larger than the maximum " +"allowed size (%d bytes)." +msgstr "" + +#: ../../lib/Browser.php:903 +#, php-format +msgid "" +"There was a problem with the file upload: The %s was only partially uploaded." +msgstr "" + +#: ../../modules/class/catalog.php:267 ../../modules/class/catalog.php:527 +#: ../../modules/class/album.php:241 +msgid "Error: Unable to open" +msgstr "" + +#: ../../modules/class/catalog.php:289 +msgid "Error: Unable to change to directory" +msgstr "" + +#: ../../modules/class/catalog.php:312 +msgid "Error: Unable to get filesize for" +msgstr "" + +#: ../../modules/class/catalog.php:332 +msgid "Added" +msgstr "" + +#: ../../modules/class/catalog.php:342 +msgid "is not readable by ampache" +msgstr "" + +#: ../../modules/class/catalog.php:402 +msgid "Found in ID3" +msgstr "" + +#: ../../modules/class/catalog.php:406 +msgid "Found on Amazon" +msgstr "" + +#: ../../modules/class/catalog.php:410 +msgid "Found in Folder" +msgstr "" + +#: ../../modules/class/catalog.php:414 +msgid "Found" +msgstr "" + +#: ../../modules/class/catalog.php:417 +msgid "Not Found" +msgstr "" + +#: ../../modules/class/catalog.php:425 +msgid "Searched" +msgstr "" + +#: ../../modules/class/catalog.php:578 +msgid "Starting Dump Album Art" +msgstr "" + +#: ../../modules/class/catalog.php:598 +msgid "Written" +msgstr "" + +#: ../../modules/class/catalog.php:607 +msgid "Error unable to open file for writting" +msgstr "" + +#: ../../modules/class/catalog.php:614 +msgid "Album Art Dump Complete" +msgstr "" + +#: ../../modules/class/catalog.php:681 +msgid "Starting Catalog Build" +msgstr "" + +#: ../../modules/class/catalog.php:686 +msgid "Running Remote Sync" +msgstr "" + +#: ../../modules/class/catalog.php:696 ../../modules/class/catalog.php:843 +msgid "Starting Album Art Search" +msgstr "" + +#: ../../modules/class/catalog.php:706 +#, fuzzy +msgid "Catalog Finished" +msgstr "Outils pour les Catalogues" + +#: ../../modules/class/catalog.php:706 ../../modules/class/catalog.php:862 +#, fuzzy +msgid "Total Time" +msgstr "Outils pour les Catalogues" + +#: ../../modules/class/catalog.php:706 ../../modules/class/catalog.php:863 +#, fuzzy +msgid "Total Songs" +msgstr "Voir les chansons desactivees" + +#: ../../modules/class/catalog.php:707 ../../modules/class/catalog.php:863 +msgid "Songs Per Seconds" +msgstr "" + +#: ../../modules/class/catalog.php:741 +#, fuzzy +msgid "Updated" +msgstr "Mise a jour rapide" + +#: ../../modules/class/catalog.php:748 +msgid "No Update Needed" +msgstr "" + +#: ../../modules/class/catalog.php:823 +msgid "Starting New Song Search on" +msgstr "" + +#: ../../modules/class/catalog.php:823 +#, fuzzy +msgid "catalog" +msgstr "Outils pour les Catalogues" + +#: ../../modules/class/catalog.php:827 +msgid "Running Remote Update" +msgstr "" + +#: ../../modules/class/catalog.php:862 +#, fuzzy +msgid "Catalog Update Finished" +msgstr "Effacer les Statistiques des Catalogues" + +#: ../../modules/class/catalog.php:876 +msgid "Unable to load XMLRPC library, make sure XML-RPC is enabled" +msgstr "" + +#: ../../modules/class/catalog.php:908 ../../modules/class/catalog.php:923 +msgid "Error connecting to" +msgstr "" + +#: ../../modules/class/catalog.php:908 ../../modules/class/catalog.php:923 +msgid "Code" +msgstr "" + +#: ../../modules/class/catalog.php:908 ../../modules/class/catalog.php:923 +msgid "Reason" +msgstr "" + +#: ../../modules/class/catalog.php:928 +msgid "Completed updating remote catalog(s)" +msgstr "" + +#: ../../modules/class/catalog.php:1016 +msgid "Checking" +msgstr "" + +#: ../../modules/class/catalog.php:1073 +#, fuzzy +msgid "Catalog Clean Done" +msgstr "Outils pour les Catalogues" + +#: ../../modules/class/catalog.php:1073 +msgid "files removed" +msgstr "" + +#: ../../modules/class/catalog.php:1313 +msgid "Updating the" +msgstr "" + +#: ../../modules/class/catalog.php:1313 +#: ../../templates/show_admin_index.inc:34 ../../templates/admin_menu.inc:35 +#, fuzzy +msgid "Catalog" +msgstr "Outils pour les Catalogues" + +#: ../../modules/class/catalog.php:1314 +msgid "songs found checking tag information." +msgstr "" + +#: ../../modules/class/stream.php:192 +msgid "Opened for writting" +msgstr "" + +#: ../../modules/class/stream.php:197 +msgid "Error, cannot write" +msgstr "" + +#: ../../modules/class/stream.php:208 +msgid "Error, cannot write song in file" +msgstr "" + +#: ../../modules/class/stream.php:214 +msgid "Closed after write" +msgstr "" + +#: ../../modules/class/album.php:126 +msgid "Various" +msgstr "" + +#: ../../modules/class/song.php:246 +msgid "Title" +msgstr "" + +#: ../../modules/class/song.php:246 ../../modules/class/song.php:250 +#: ../../modules/class/song.php:254 ../../modules/class/song.php:258 +#: ../../modules/class/song.php:262 ../../modules/class/song.php:266 +#: ../../modules/class/song.php:270 ../../modules/class/song.php:275 +#: ../../modules/class/song.php:280 ../../modules/class/song.php:284 +#: ../../modules/class/song.php:288 ../../modules/class/song.php:293 +#, fuzzy +msgid "updated to" +msgstr "Mettre a jour les Catalogues" + +#: ../../modules/class/song.php:254 +msgid "Rate" +msgstr "" + +#: ../../modules/class/song.php:258 +msgid "Mode" +msgstr "" + +#: ../../modules/class/song.php:266 ../../templates/show_songs.inc:32 +#: ../../templates/show_songs.inc:36 +msgid "Track" +msgstr "" + +#: ../../modules/class/song.php:270 +msgid "Filesize" +msgstr "" + +#: ../../modules/class/song.php:284 ../../templates/show_albums.inc:43 +#: ../../templates/show_albums.inc:72 +msgid "Year" +msgstr "" + +#: ../../modules/class/song.php:288 ../../templates/flag.inc:66 +#: ../../templates/list_flagged.inc:46 +msgid "Comment" +msgstr "" + +#: ../../modules/lib.php:51 +msgid "day" +msgstr "" + +#: ../../modules/lib.php:54 +msgid "days" +msgstr "" + +#: ../../modules/lib.php:58 +msgid "hour" +msgstr "" + +#: ../../modules/lib.php:61 +msgid "hours" +msgstr "" + +#: ../../modules/lib.php:78 +#, fuzzy +msgid "Catalog Statistics" +msgstr "Effacer les Statistiques des Catalogues" + +#: ../../modules/lib.php:81 +msgid "Total Users" +msgstr "" + +#: ../../modules/lib.php:85 +msgid "Connected Users" +msgstr "" + +#: ../../modules/lib.php:89 ../../templates/show_artists.inc:42 +#: ../../templates/show_artists.inc:65 ../../templates/menu.inc:32 +msgid "Albums" +msgstr "" + +#: ../../modules/lib.php:93 ../../templates/menu.inc:33 +msgid "Artists" +msgstr "" + +#: ../../modules/lib.php:97 ../../templates/show_artists.inc:41 +#: ../../templates/show_artists.inc:64 ../../templates/show_albums.inc:41 +#: ../../templates/show_albums.inc:71 +msgid "Songs" +msgstr "" + +#: ../../modules/lib.php:101 +#, fuzzy +msgid "Catalog Size" +msgstr "Outils pour les Catalogues" + +#: ../../modules/lib.php:105 +#, fuzzy +msgid "Catalog Time" +msgstr "Outils pour les Catalogues" + +#: ../../modules/lib.php:159 +msgid "Play Random Selection" +msgstr "" + +#: ../../modules/lib.php:166 +msgid "Item count" +msgstr "" + +#: ../../modules/lib.php:178 ../../templates/show_artists.inc:55 +#: ../../templates/show_albums.inc:58 +msgid "All" +msgstr "" + +#: ../../modules/lib.php:180 +msgid "From genre" +msgstr "" + +#: ../../modules/lib.php:190 +msgid "Favor Unplayed" +msgstr "" + +#: ../../modules/lib.php:191 +msgid "Full Albums" +msgstr "" + +#: ../../modules/lib.php:192 +msgid "Full Artist" +msgstr "" + +#: ../../modules/lib.php:201 +#, fuzzy +msgid "from catalog" +msgstr "Ajouter un catalogue" + +#: ../../modules/lib.php:212 +msgid "Play Random Songs" +msgstr "" + +#: ../../modules/lib.php:921 +msgid "Public" +msgstr "" + +#: ../../modules/lib.php:922 +msgid "Your Private" +msgstr "" + +#: ../../modules/lib.php:923 +msgid "Other Private" +msgstr "" + +#: ../../modules/lib.php:1004 ../../templates/show_songs.inc:168 +#: ../../templates/show_artist.inc:102 +msgid "View" +msgstr "" + +#: ../../modules/lib.php:1022 ../../templates/show_songs.inc:110 +#: ../../templates/show_album.inc:61 ../../templates/show_albums.inc:61 +#: ../../templates/show_artist.inc:81 +msgid "Download" +msgstr "" + +#: ../../modules/lib.php:1033 +msgid "There are no playlists of this type" +msgstr "" + +#: ../../modules/lib.php:1061 +msgid "Create a new playlist" +msgstr "" + +#: ../../modules/admin.php:45 +#, fuzzy +msgid "Manage Users" +msgstr "Mettre a jour les Catalogues" + +#: ../../modules/admin.php:47 +msgid "Add a new user" +msgstr "" + +#: ../../templates/catalog.inc:33 +msgid "" +"Error: ICONV not found, ID3V2 Tags will not import correctly. See <a href=" +"\"http://php.oregonstate.edu/iconv\">Iconv</a> for information on getting " +"ICONV" +msgstr "" + +#: ../../templates/catalog.inc:42 +msgid "Update Catalogs" +msgstr "Mettre a jour les Catalogues" + +#: ../../templates/catalog.inc:68 +msgid "Fast Add" +msgstr "Ajout Rapide" + +#: ../../templates/catalog.inc:75 +msgid "Fast Update" +msgstr "Mise a jour rapide" + +#: ../../templates/catalog.inc:88 +msgid "You don't have any catalogs." +msgstr "Vous avez aucun catalogue" + +#: ../../templates/catalog.inc:97 +msgid "Add a catalog" +msgstr "Ajouter un catalogue" + +#: ../../templates/catalog.inc:98 ../../templates/show_admin_index.inc:36 +#: ../../templates/admin_menu.inc:37 +msgid "Access Lists" +msgstr "" + +#: ../../templates/catalog.inc:99 +msgid "Show Duplicate Songs" +msgstr "Voir les doublons" + +#: ../../templates/catalog.inc:100 +msgid "Show Disabled Songs" +msgstr "Voir les chansons desactivees" + +#: ../../templates/catalog.inc:101 +msgid "Clear Catalog Stats" +msgstr "Effacer les Statistiques des Catalogues" + +#: ../../templates/catalog.inc:102 +msgid "Clear Now Playing" +msgstr "Effacer Lecture en cours" + +#: ../../templates/catalog.inc:103 +msgid "Dump Album Art" +msgstr "" + +#: ../../templates/catalog.inc:104 +msgid "View flagged songs" +msgstr "Voir les chansons marquees" + +#: ../../templates/catalog.inc:105 +msgid "Catalog Tools" +msgstr "Outils pour les Catalogues" + +#: ../../templates/flag.inc:43 +msgid "Flag song" +msgstr "" + +#: ../../templates/flag.inc:45 +msgid "" +"Flag the following song as having one of the problems listed below. Site " +"admins will then take the appropriate action for the flagged files." +msgstr "" + +#: ../../templates/flag.inc:62 +msgid "Reason to flag" +msgstr "" + +#: ../../templates/flag.inc:73 +msgid "Flag Song" +msgstr "" + +#: ../../templates/customize_catalog.inc:24 +msgid "Settings for catalog in" +msgstr "" + +#: ../../templates/customize_catalog.inc:32 ../../templates/add_catalog.inc:39 +msgid "Auto-inserted Fields" +msgstr "" + +#: ../../templates/customize_catalog.inc:33 ../../templates/add_catalog.inc:40 +msgid "album name" +msgstr "" + +#: ../../templates/customize_catalog.inc:34 ../../templates/add_catalog.inc:41 +msgid "artist name" +msgstr "" + +#: ../../templates/customize_catalog.inc:35 +msgid "catalog path" +msgstr "" + +#: ../../templates/customize_catalog.inc:36 ../../templates/add_catalog.inc:42 +msgid "id3 comment" +msgstr "" + +#: ../../templates/customize_catalog.inc:37 ../../templates/add_catalog.inc:43 +msgid "genre" +msgstr "" + +#: ../../templates/customize_catalog.inc:38 ../../templates/add_catalog.inc:44 +msgid "track number (padded with leading 0)" +msgstr "" + +#: ../../templates/customize_catalog.inc:39 ../../templates/add_catalog.inc:45 +msgid "song title" +msgstr "" + +#: ../../templates/customize_catalog.inc:40 ../../templates/add_catalog.inc:46 +msgid "year" +msgstr "" + +#: ../../templates/customize_catalog.inc:41 ../../templates/add_catalog.inc:47 +msgid "other" +msgstr "" + +#: ../../templates/customize_catalog.inc:45 +msgid "ID3 set command" +msgstr "" + +#: ../../templates/customize_catalog.inc:51 +msgid "Filename pattern" +msgstr "" + +#: ../../templates/customize_catalog.inc:58 ../../templates/add_catalog.inc:74 +msgid "Folder Pattern" +msgstr "" + +#: ../../templates/customize_catalog.inc:58 ../../templates/add_catalog.inc:74 +msgid "(no leading or ending '/')" +msgstr "" + +#: ../../templates/customize_catalog.inc:69 +#, fuzzy +msgid "Save Catalog Settings" +msgstr "Effacer les Statistiques des Catalogues" + +#: ../../templates/show_test.inc:29 +msgid "Ampache Debug" +msgstr "" + +#: ../../templates/show_test.inc:30 +msgid "" +"You've reached this page because a configuration error has occured. Debug " +"Information below" +msgstr "" + +#: ../../templates/show_test.inc:35 +msgid "STATUS" +msgstr "" + +#: ../../templates/show_test.inc:39 +msgid "PHP Version" +msgstr "" + +#: ../../templates/show_test.inc:54 +msgid "" +"This tests to make sure that you are running a version of PHP that is known " +"to work with Ampache." +msgstr "" + +#: ../../templates/show_test.inc:58 +msgid "Mysql for PHP" +msgstr "" + +#: ../../templates/show_test.inc:73 +msgid "" +"This test checks to see if you have the mysql extensions loaded for PHP. " +"These are required for Ampache to work." +msgstr "" + +#: ../../templates/show_test.inc:77 +msgid "PHP Session Support" +msgstr "" + +#: ../../templates/show_test.inc:92 +msgid "" +"This test checks to make sure that you have PHP session support enabled. " +"Sessions are required for Ampache to work." +msgstr "" + +#: ../../templates/show_test.inc:96 +msgid "PHP ICONV Support" +msgstr "" + +#: ../../templates/show_test.inc:110 +msgid "" +"This test checks to make sure you have Iconv support installed. Iconv " +"support is not required for Ampache, but it is highly recommended" +msgstr "" + +#: ../../templates/show_test.inc:114 +#: ../../templates/show_install_config.inc:88 +msgid "Ampache.cfg Exists" +msgstr "" + +#: ../../templates/show_test.inc:129 +msgid "" +"This attempts to read /config/ampache.cfg If this fails either the ampache." +"cfg is not in the correct locations or\n" +"\tit is not currently readable by your webserver." +msgstr "" + +#: ../../templates/show_test.inc:135 +#: ../../templates/show_install_config.inc:105 +msgid "Ampache.cfg Configured?" +msgstr "" + +#: ../../templates/show_test.inc:152 +msgid "" +"This test makes sure that you have set all of the required config variables " +"and that we are able to \n" +"\tcompleatly parse your config file" +msgstr "" + +#: ../../templates/show_test.inc:159 +msgid "Ampache.cfg Up to Date?" +msgstr "" + +#: ../../templates/show_test.inc:165 +msgid "DB Connection" +msgstr "" + +#: ../../templates/show_test.inc:181 +msgid "" +"This attempts to connect to your database using the values from your ampache." +"cfg" +msgstr "" + +#: ../../templates/show_admin_index.inc:30 +msgid "Admin Section" +msgstr "" + +#: ../../templates/show_admin_index.inc:32 ../../templates/admin_menu.inc:33 +msgid "Users" +msgstr "" + +#: ../../templates/show_admin_index.inc:32 +msgid "Create/Modify User Accounts for Ampache" +msgstr "" + +#: ../../templates/show_admin_index.inc:33 +msgid "Mail" +msgstr "" + +#: ../../templates/show_admin_index.inc:33 +msgid "Mail your users to notfiy them of changes" +msgstr "" + +#: ../../templates/show_admin_index.inc:34 +msgid "Create/Update/Clean your catalog here" +msgstr "" + +#: ../../templates/show_admin_index.inc:35 ../../templates/admin_menu.inc:36 +msgid "Admin Preferences" +msgstr "" + +#: ../../templates/show_admin_index.inc:35 +msgid "Modify Site-wide preferences" +msgstr "" + +#: ../../templates/show_admin_index.inc:36 +msgid "Modify Access List Permissions" +msgstr "" + +#: ../../templates/show_admin_index.inc:36 +msgid "Must have access_control=true in ampache.cfg" +msgstr "" + +#: ../../templates/show_localplay.inc:30 +msgid "Local Play Control" +msgstr "" + +#: ../../templates/show_localplay.inc:35 +msgid "Playback" +msgstr "" + +#: ../../templates/show_localplay.inc:39 ../../templates/list_header.inc:69 +#: ../../templates/show_mpdplay.inc:43 +msgid "Prev" +msgstr "" + +#: ../../templates/show_localplay.inc:40 ../../templates/show_mpdplay.inc:44 +msgid "Stop" +msgstr "" + +#: ../../templates/show_localplay.inc:42 ../../templates/show_mpdplay.inc:46 +msgid "Pause" +msgstr "" + +#: ../../templates/show_localplay.inc:43 ../../templates/list_header.inc:92 +#: ../../templates/show_mpdplay.inc:47 +msgid "Next" +msgstr "" + +#: ../../templates/show_localplay.inc:49 +msgid "Volume" +msgstr "" + +#: ../../templates/show_localplay.inc:53 ../../templates/show_localplay.inc:54 +msgid "Increase Volume" +msgstr "" + +#: ../../templates/show_localplay.inc:55 ../../templates/show_localplay.inc:56 +msgid "Decrease Volume" +msgstr "" + +#: ../../templates/show_localplay.inc:62 +msgid "Clear queue" +msgstr "" + +#: ../../templates/add_catalog.inc:28 +#, fuzzy +msgid "Add a Catalog" +msgstr "Ajouter un catalogue" + +#: ../../templates/add_catalog.inc:30 +msgid "" +"In the form below enter either a local path (i.e. /data/music) or the URL to " +"a remote Ampache installation (i.e http://theotherampache.com)" +msgstr "" + +#: ../../templates/add_catalog.inc:36 +#, fuzzy +msgid "Catalog Name" +msgstr "Outils pour les Catalogues" + +#: ../../templates/add_catalog.inc:53 +msgid "Path" +msgstr "" + +#: ../../templates/add_catalog.inc:57 +#, fuzzy +msgid "Catalog Type" +msgstr "Outils pour les Catalogues" + +#: ../../templates/add_catalog.inc:61 +msgid "Remote" +msgstr "" + +#: ../../templates/add_catalog.inc:66 +msgid "ID3 Set Command" +msgstr "" + +#: ../../templates/add_catalog.inc:70 +msgid "Filename Pattern" +msgstr "" + +#: ../../templates/add_catalog.inc:78 +msgid "Gather Album Art" +msgstr "" + +#: ../../templates/add_catalog.inc:82 +msgid "ID3V2 Tags" +msgstr "" + +#: ../../templates/add_catalog.inc:85 +msgid "Amazon" +msgstr "" + +#: ../../templates/add_catalog.inc:88 +msgid "File Folder" +msgstr "" + +#: ../../templates/add_catalog.inc:98 +#, fuzzy +msgid "Add Catalog" +msgstr "Ajouter un catalogue" + +#: ../../templates/list_flagged.inc:42 ../../templates/show_songs.inc:41 +msgid "Flag" +msgstr "" + +#: ../../templates/list_flagged.inc:43 +#, fuzzy +msgid "New Flag" +msgstr "Effacer Lecture en cours" + +#: ../../templates/list_flagged.inc:44 +msgid "Flagged by" +msgstr "" + +#: ../../templates/list_flagged.inc:45 +#, fuzzy +msgid "ID3 Update" +msgstr "Mise a jour rapide" + +#: ../../templates/list_flagged.inc:69 +msgid "Accept" +msgstr "" + +#: ../../templates/list_flagged.inc:70 +#, fuzzy +msgid "Reject" +msgstr "Effacer" + +#: ../../templates/show_songs.inc:33 +msgid "Song title" +msgstr "" + +#: ../../templates/show_songs.inc:114 +msgid "Direct Link" +msgstr "" + +#: ../../templates/show_songs.inc:133 +msgid "Total" +msgstr "" + +#: ../../templates/show_songs.inc:159 +msgid "Set Track Numbers" +msgstr "" + +#: ../../templates/show_songs.inc:160 +msgid "Remove Selected Tracks" +msgstr "" + +#: ../../templates/show_songs.inc:166 ../../templates/show_artist.inc:100 +#, fuzzy +msgid "Add to" +msgstr "Ajouter un catalogue" + +#: ../../templates/show_artists.inc:56 ../../templates/show_albums.inc:59 +msgid "Random" +msgstr "" + +#: ../../templates/show_install_config.inc:52 +msgid "" +"This steps takes the basic config values, and first attempts to write them " +"out directly to your webserver. If access is denied it will prompt you to " +"download the config file. Please put the downloaded config file in /config" +msgstr "" + +#: ../../templates/show_install_config.inc:60 +msgid "Web Path" +msgstr "" + +#: ../../templates/show_install_config.inc:64 +#: ../../templates/show_install.inc:58 +msgid "Desired Database Name" +msgstr "" + +#: ../../templates/show_install_config.inc:68 +#: ../../templates/show_install.inc:62 +msgid "MySQL Hostname" +msgstr "" + +#: ../../templates/show_install_config.inc:72 +msgid "MySQL Username" +msgstr "" + +#: ../../templates/show_install_config.inc:76 +#, fuzzy +msgid "MySQL Password" +msgstr "Mot de passe" + +#: ../../templates/show_install_config.inc:81 +msgid "Write Config" +msgstr "" + +#: ../../templates/show_install_config.inc:125 +msgid "Check for Config" +msgstr "" + +#: ../../templates/show_album.inc:53 +msgid "Play Album" +msgstr "" + +#: ../../templates/show_album.inc:54 +msgid "Play Random from Album" +msgstr "" + +#: ../../templates/show_album.inc:55 +msgid "Reset Album Art" +msgstr "" + +#: ../../templates/show_album.inc:56 +msgid "Find Album Art" +msgstr "" + +#: ../../templates/show_album.inc:58 ../../templates/show_artist.inc:37 +#, fuzzy +msgid "Update from tags" +msgstr "Mettre a jour les Catalogues" + +#: ../../templates/show_preferences.inc:31 +msgid "Editing" +msgstr "" + +#: ../../templates/show_preferences.inc:31 +msgid "preferences" +msgstr "" + +#: ../../templates/show_preferences.inc:33 +msgid "Rebuild Preferences" +msgstr "" + +#: ../../templates/show_preferences.inc:39 +msgid "Preference" +msgstr "" + +#: ../../templates/show_preferences.inc:40 +msgid "Value" +msgstr "" + +#: ../../templates/show_preferences.inc:42 +msgid "Type" +msgstr "" + +#: ../../templates/show_preferences.inc:43 +msgid "Apply to All" +msgstr "" + +#: ../../templates/show_preferences.inc:58 +msgid "Update Preferences" +msgstr "" + +#: ../../templates/show_preferences.inc:62 +msgid "Cancel" +msgstr "" + +#: ../../templates/userform.inc:25 +msgid "Adding a New User" +msgstr "" + +#: ../../templates/userform.inc:29 +msgid "Editing existing User" +msgstr "" + +#: ../../templates/userform.inc:81 +msgid "User Access Level" +msgstr "" + +#: ../../templates/userform.inc:98 +msgid "Add User" +msgstr "" + +#: ../../templates/userform.inc:103 +#, fuzzy +msgid "Update User" +msgstr "Mettre a jour les Catalogues" + +#: ../../templates/show_install.inc:40 +msgid "Your webserver configured so that <ampache_root>/docs is your webroot" +msgstr "" + +#: ../../templates/show_install.inc:49 +msgid "" +"This step creates and inserts the Ampache database, as such please provide a " +"mysql account with database creation rights. This step may take a while " +"depending upon the speed of your computer" +msgstr "" + +#: ../../templates/show_install.inc:66 +msgid "MySQL Administrative Username" +msgstr "" + +#: ../../templates/show_install.inc:70 +msgid "MySQL Administrative Password" +msgstr "" + +#: ../../templates/show_install.inc:75 +msgid "Insert Database" +msgstr "" + +#: ../../templates/show_add_access.inc:31 +msgid "Add Access for a Host" +msgstr "" + +#: ../../templates/show_add_access.inc:33 +msgid "" +"Use the form below to add a host that you want to have access to your " +"Ampache catalog." +msgstr "" + +#: ../../templates/show_add_access.inc:46 +msgid "Start IP Address" +msgstr "" + +#: ../../templates/show_add_access.inc:52 +msgid "End IP Address" +msgstr "" + +#: ../../templates/show_add_access.inc:58 +#: ../../templates/show_access_list.inc:50 +msgid "Level" +msgstr "" + +#: ../../templates/show_add_access.inc:72 +msgid "Add Host" +msgstr "" + +#: ../../templates/admin_menu.inc:34 +msgid "Mail Users" +msgstr "" + +#: ../../templates/show_upload.inc:27 +msgid "Please Ensure All Files Are Tagged Correctly" +msgstr "" + +#: ../../templates/show_upload.inc:30 +msgid "" +"Ampache relies on id3 tags to sort data. If your file is not tagged it may " +"be deleted." +msgstr "" + +#: ../../templates/show_upload.inc:34 +msgid "max_upload_size" +msgstr "" + +#: ../../templates/show_upload.inc:59 ../../templates/menu.inc:39 +msgid "Upload" +msgstr "" + +#: ../../templates/show_users.inc:42 +msgid "Fullname" +msgstr "" + +#: ../../templates/show_users.inc:47 +msgid "Last Seen" +msgstr "" + +#: ../../templates/show_users.inc:54 +msgid "Prefs" +msgstr "" + +#: ../../templates/show_users.inc:60 +msgid "Set Access" +msgstr "" + +#: ../../templates/show_users.inc:66 +msgid "On-line" +msgstr "" + +#: ../../templates/show_users.inc:88 +msgid "edit" +msgstr "" + +#: ../../templates/show_users.inc:93 +msgid "prefs" +msgstr "" + +#: ../../templates/show_users.inc:98 +#, fuzzy +msgid "delete" +msgstr "Effacer" + +#: ../../templates/show_users.inc:104 ../../templates/show_users.inc:113 +#: ../../templates/show_users.inc:116 +msgid "set to user" +msgstr "" + +#: ../../templates/show_users.inc:105 ../../templates/show_users.inc:109 +#: ../../templates/show_users.inc:117 +msgid "disable" +msgstr "" + +#: ../../templates/show_users.inc:108 ../../templates/show_users.inc:112 +msgid "set to admin" +msgstr "" + +#: ../../templates/show_now_playing.inc:34 ../../templates/show_mpdplay.inc:68 +#, fuzzy +msgid "Now Playing" +msgstr "Effacer Lecture en cours" + +#: ../../templates/show_login_form.inc:40 +#: ../../templates/show_login_form.inc:54 +msgid "Login" +msgstr "Utilisateur" + +#: ../../templates/show_login_form.inc:50 +msgid "Remember Me" +msgstr "" + +#: ../../templates/show_access_list.inc:34 +msgid "Host Access to Your Catalog" +msgstr "" + +#: ../../templates/show_access_list.inc:43 +msgid "Add Entry" +msgstr "" + +#: ../../templates/show_access_list.inc:48 +msgid "Start Address" +msgstr "" + +#: ../../templates/show_access_list.inc:49 +msgid "End Address" +msgstr "" + +#: ../../templates/show_access_list.inc:65 +msgid "Revoke" +msgstr "" + +#: ../../templates/menu.inc:31 +msgid "Home" +msgstr "" + +#: ../../templates/menu.inc:34 +msgid "Playlists" +msgstr "" + +#: ../../templates/menu.inc:35 ../../templates/show_search.inc:38 +#: ../../templates/show_search.inc:84 +msgid "Search" +msgstr "" + +#: ../../templates/menu.inc:36 +msgid "Preferences" +msgstr "" + +#: ../../templates/menu.inc:57 ../../templates/menu.inc:60 +msgid "Admin" +msgstr "" + +#: ../../templates/menu.inc:69 ../../templates/menu.inc:76 +msgid "Account" +msgstr "" + +#: ../../templates/menu.inc:70 ../../templates/menu.inc:77 +msgid "Stats" +msgstr "" + +#: ../../templates/menu.inc:71 ../../templates/menu.inc:78 +#: ../../templates/menu.inc:83 +msgid "Logout" +msgstr "" + +#: ../../templates/show_search.inc:35 +msgid "Search Ampache" +msgstr "" + +#: ../../templates/show_search.inc:42 +msgid "Object Type" +msgstr "" + +#: ../../templates/show_search.inc:75 +msgid "Search Type" +msgstr "" + +#: ../../templates/show_mpdplay.inc:33 +msgid "MPD Play Control" +msgstr "" + +#: ../../templates/show_mpdplay.inc:52 +msgid "Loop" +msgstr "" + +#: ../../templates/show_mpdplay.inc:54 +msgid "Loop is off" +msgstr "" + +#: ../../templates/show_mpdplay.inc:55 +msgid "Loop is on" +msgstr "" + +#: ../../templates/show_mpdplay.inc:81 +msgid "Refresh the Playlist Window" +msgstr "" + +#: ../../templates/show_mpdplay.inc:81 +msgid "refresh now" +msgstr "" + +#: ../../templates/show_mpdplay.inc:89 +msgid "Server Playlist" +msgstr "" + +#: ../../templates/show_mpdplay.inc:122 +msgid "Click to shuffle (randomize) the playlist" +msgstr "" + +#: ../../templates/show_mpdplay.inc:122 +msgid "shuffle" +msgstr "" + +#: ../../templates/show_mpdplay.inc:123 +msgid "Click the clear the playlist" +msgstr "" + +#: ../../templates/show_mpdplay.inc:123 +msgid "clear" +msgstr "" + +#: ../../templates/show_artist.inc:31 +msgid "Albums by" +msgstr "" + +#: ../../templates/show_artist.inc:33 +#, fuzzy +msgid "Show All Songs By" +msgstr "Voir les chansons desactivees" + +#: ../../templates/show_artist.inc:34 +msgid "Play All Songs By" +msgstr "" + +#: ../../templates/show_artist.inc:35 +msgid "Play Random Songs By" +msgstr "" + +#: ../../templates/show_artist.inc:50 +#, fuzzy +msgid "Select" +msgstr "Effacer" + +#: ../../templates/show_artist.inc:52 +msgid "Cover" +msgstr "" + +#: ../../templates/show_artist.inc:53 +msgid "Album Name" +msgstr "" + +#: ../../templates/show_artist.inc:54 +msgid "Album Year" +msgstr "" + +#: ../../templates/show_artist.inc:55 +msgid "Total Tracks" +msgstr "" + +#~ msgid "Remove Dead Songs from DB" +#~ msgstr "Nettoyer la Base de donnees" diff --git a/locale/tr_TR/LC_MESSAGES/messages.mo b/locale/tr_TR/LC_MESSAGES/messages.mo Binary files differnew file mode 100644 index 00000000..a57d1e2b --- /dev/null +++ b/locale/tr_TR/LC_MESSAGES/messages.mo diff --git a/locale/tr_TR/LC_MESSAGES/messages.po b/locale/tr_TR/LC_MESSAGES/messages.po new file mode 100644 index 00000000..86389562 --- /dev/null +++ b/locale/tr_TR/LC_MESSAGES/messages.po @@ -0,0 +1,1866 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Ampache\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2005-02-05 10:43-0800\n" +"PO-Revision-Date: 2005-04-25 21:14+0100\n" +"Last-Translator: vireas <vireas at gmail.com>\n" +"Language-Team: TURKISH <dev@ampache.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=iso-8859-9\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Turkish\n" +"X-Poedit-Country: TURKEY\n" +"X-Poedit-SourceCharset: iso-8859-9\n" + +#: ../../docs/admin/users.php:76 +#: ../../docs/admin/users.php:123 +msgid "Error Username Required" +msgstr "Hata, kullanýcý ismi gerekli" + +#: ../../docs/admin/users.php:79 +#: ../../docs/admin/users.php:120 +msgid "Error Passwords don't match" +msgstr "Hata, parolalar uyuþmuyor" + +#: ../../docs/admin/users.php:137 +msgid "Are you sure you want to permanently delete" +msgstr "Tamamen silmek istediðinizden emin misiniz: " + +#: ../../docs/admin/users.php:144 +#: ../../templates/show_confirm_action.inc.php:29 +msgid "No" +msgstr "Hayýr" + +#: ../../docs/admin/users.php:146 +msgid "User Deleted" +msgstr "Kullanýcý silindi" + +#: ../../docs/admin/users.php:149 +msgid "Delete Error" +msgstr "Silme hatasý" + +#: ../../docs/admin/users.php:149 +msgid "Unable to delete last Admin User" +msgstr "Son yönetici silinemedi" + +#: ../../docs/admin/access.php:43 +msgid "Do you really want to delete this Access Record?" +msgstr "Bu giriþ izini gerçekten silmek mi istiyorsunuz?" + +#: ../../docs/admin/access.php:51 +msgid "Entry Deleted" +msgstr "Giriþ silindi" + +#: ../../docs/admin/access.php:51 +msgid "Your Access List Entry has been removed" +msgstr "Eriþim listesindeki giriþiniz silindi" + +#: ../../docs/admin/access.php:61 +msgid "Entry Added" +msgstr "Giriþ eklendi" + +#: ../../docs/admin/access.php:61 +msgid "Your new Access List Entry has been created" +msgstr "Eriþim listesine giriþiniz eklendi" + +#: ../../docs/admin/catalog.php:54 +#: ../../templates/catalog.inc:66 +msgid "Add to Catalog(s)" +msgstr "Kataloða ekle" + +#: ../../docs/admin/catalog.php:65 +#: ../../templates/catalog.inc:67 +msgid "Add to all Catalogs" +msgstr "Tüm kataloglara ekle" + +#: ../../docs/admin/catalog.php:75 +#: ../../templates/catalog.inc:73 +msgid "Update Catalog(s)" +msgstr "Kataloðu güncelleþtir" + +#: ../../docs/admin/catalog.php:86 +#: ../../templates/catalog.inc:74 +msgid "Update All Catalogs" +msgstr "Tüm kataloglarý güncelleþtir" + +#: ../../docs/admin/catalog.php:118 +#: ../../templates/catalog.inc:80 +msgid "Clean Catalog(s)" +msgstr "Kataloðu temizle" + +#: ../../docs/admin/catalog.php:148 +#: ../../templates/catalog.inc:81 +msgid "Clean All Catalogs" +msgstr "Tüm kataloglarý temizle" + +#: ../../docs/admin/catalog.php:196 +msgid "Now Playing Cleared" +msgstr "Þu anda çalýnanlar silindi" + +#: ../../docs/admin/catalog.php:196 +msgid "All now playing data has been cleared" +msgstr "Tüm þu anda çalýnanlar silindi" + +#: ../../docs/admin/catalog.php:201 +msgid "Do you really want to clear your catalog?" +msgstr "Kataloðunuzu gerçekten boþaltmak mý istiyorsunuz?" + +#: ../../docs/admin/catalog.php:208 +msgid "Do you really want to clear the statistics for this catalog?" +msgstr "Bu kataloðun istatistik bilgilerini gerçekten silmek mi istiyorsunuz?" + +#: ../../docs/admin/catalog.php:226 +msgid "Do you really want to delete this catalog?" +msgstr "Kataloðunuzu gerçekten silmek mi istiyorsunuz?" + +#: ../../docs/admin/song.php:70 +msgid "Songs Disabled" +msgstr "Edilgenleþtirilmiþ Þarkýlar" + +#: ../../docs/admin/song.php:70 +msgid "The requested song(s) have been disabled" +msgstr "Ýstenen þarký(lar) edilgenleþtirildi" + +#: ../../docs/admin/song.php:80 +msgid "Songs Enabled" +msgstr "Etkinlentirilmiþ Þarkýlar" + +#: ../../docs/admin/song.php:80 +msgid "The requested song(s) have been enabled" +msgstr "Ýstenen þarký(lar) etkinleþtirildi" + +#: ../../docs/localplay.php:79 +msgid "Unknown action requested" +msgstr "Bilinmeyen eylem istendi" + +#: ../../docs/upload.php:124 +msgid "The uploaded file exceeds the upload_max_filesize directive in php.ini" +msgstr "Gönderilen dosya daha önce php.ini'de belirlenen upload_max_filesize hattýný aþýyor." + +#: ../../docs/upload.php:127 +msgid "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form." +msgstr "Gönderilen dosya daha önce belirlenen MAX_FILE_SIZE hattýný aþýyor." + +#: ../../docs/upload.php:130 +msgid "The uploaded file was only partially uploaded." +msgstr "Gönderilen dosya kýsmen yüklendi." + +#: ../../docs/upload.php:133 +msgid "No file was uploaded." +msgstr "Hicbir dosya yüklenmedi." + +#: ../../docs/upload.php:136 +msgid "An Unknown Error has occured." +msgstr "Bilinmeyen bir hata oluþtu." + +#: ../../docs/upload.php:157 +msgid "Successfully-Quarantined" +msgstr "Baþarýyla karantinaya alýndý" + +#: ../../docs/upload.php:167 +msgid "Successfully-Cataloged" +msgstr "Baþarýyla kataloglaþtýrýldý" + +#: ../../docs/upload.php:229 +#: ../../templates/show_artist.inc:56 +#: ../../templates/show_access_list.inc:51 +#: ../../templates/show_albums.inc:45 +#: ../../templates/show_albums.inc:73 +#: ../../templates/show_artists.inc:43 +#: ../../templates/show_artists.inc:67 +#: ../../templates/show_songs.inc:42 +msgid "Action" +msgstr "Eylem" + +#: ../../docs/upload.php:230 +#: ../../templates/flag.inc:58 +#: ../../templates/list_flagged.inc:41 +msgid "Song" +msgstr "Þarký" + +#: ../../docs/upload.php:231 +#: ../../modules/class/song.php:275 +#: ../../templates/show_albums.inc:40 +#: ../../templates/show_albums.inc:70 +#: ../../templates/show_artists.inc:39 +#: ../../templates/show_artists.inc:62 +#: ../../templates/show_songs.inc:34 +msgid "Artist" +msgstr "Sanatçý" + +#: ../../docs/upload.php:232 +#: ../../modules/class/song.php:280 +#: ../../templates/show_albums.inc:38 +#: ../../templates/show_albums.inc:68 +#: ../../templates/show_songs.inc:35 +msgid "Album" +msgstr "Albüm" + +#: ../../docs/upload.php:233 +#: ../../modules/class/song.php:293 +#: ../../templates/show_songs.inc:40 +msgid "Genre" +msgstr "Kategori" + +#: ../../docs/upload.php:234 +#: ../../modules/class/song.php:262 +#: ../../templates/show_songs.inc:37 +msgid "Time" +msgstr "Süre" + +#: ../../docs/upload.php:235 +#: ../../modules/class/song.php:250 +#: ../../templates/show_songs.inc:39 +msgid "Bitrate" +msgstr "Ýkil hýzý" + +#: ../../docs/upload.php:236 +#: ../../templates/show_songs.inc:38 +msgid "Size" +msgstr "Boyut" + +#: ../../docs/upload.php:237 +msgid "Filename" +msgstr "Dosya adý" + +#: ../../docs/upload.php:238 +msgid "User" +msgstr "Kullanýcý" + +#: ../../docs/upload.php:239 +msgid "Date" +msgstr "Tarih" + +#: ../../docs/upload.php:267 +msgid "Unknown" +msgstr "Bilinmeyen" + +#: ../../docs/upload.php:289 +msgid "Add" +msgstr "Ekle" + +#: ../../docs/upload.php:290 +#: ../../modules/lib.php:1008 +#: ../../templates/catalog.inc:60 +#: ../../templates/show_users.inc:57 +msgid "Delete" +msgstr "Sil" + +#: ../../docs/upload.php:294 +msgid "Quarantined" +msgstr "Karantinaya alýnmýþ" + +#: ../../docs/play/index.php:46 +msgid "Session Expired: please log in again at" +msgstr "Oturumun süresi bitmiþ: lütfen yeniden giriþ yapýn" + +#: ../../docs/flag.php:35 +msgid "Flagging song completed." +msgstr "Þarký iþaretleme tamamlandý" + +#: ../../docs/playlist.php:71 +#: ../../templates/show_artist.inc:95 +#: ../../templates/show_songs.inc:151 +msgid "Play Selected" +msgstr "Seçilmiþleri çal" + +#: ../../docs/playlist.php:89 +#: ../../templates/show_songs.inc:152 +msgid "Flag Selected" +msgstr "Seçilmiþleri iþaretle" + +#: ../../docs/playlist.php:95 +#: ../../templates/show_songs.inc:153 +msgid "Edit Selected" +msgstr "Seçilmiþleri düzenle" + +#: ../../docs/playlist.php:125 +#: ../../modules/lib.php:1007 +#: ../../templates/show_artist.inc:103 +#: ../../templates/show_users.inc:51 +#: ../../templates/show_songs.inc:169 +msgid "Edit" +msgstr "Düzenle" + +#: ../../docs/playlist.php:128 +#: ../../modules/lib.php:1016 +#: ../../templates/show_artist.inc:79 +#: ../../templates/show_mpdplay.inc:45 +#: ../../templates/show_albums.inc:57 +#: ../../templates/show_artists.inc:54 +#: ../../templates/show_localplay.inc:41 +msgid "Play" +msgstr "Çal" + +#: ../../docs/playlist.php:140 +msgid "New Playlist" +msgstr "Yeni þarký listesi" + +#: ../../docs/playlist.php:198 +msgid "Playlist updated." +msgstr "Þarký listesi güncelendi." + +#: ../../docs/playlist.php:305 +msgid "No songs in this playlist." +msgstr "Bu listede þarký bulunamadý." + +#: ../../docs/artists.php:47 +msgid "All songs by" +msgstr "Tüm þarkýlarý" + +#: ../../docs/artists.php:56 +#: ../../docs/albums.php:103 +msgid "Starting Update from Tags" +msgstr "Etiketten güncelleþtirme baþlýyor" + +#: ../../docs/artists.php:61 +#: ../../docs/albums.php:108 +msgid "Update From Tags Compleate" +msgstr "Etiketten güncelleþtirme tamamlandý" + +#: ../../docs/artists.php:62 +#: ../../docs/albums.php:109 +#: ../../modules/class/catalog.php:615 +msgid "Return" +msgstr "Dön" + +#: ../../docs/artists.php:73 +#: ../../docs/artists.php:82 +#: ../../docs/artists.php:94 +#: ../../docs/artists.php:111 +msgid "<u>S</u>how artists starting with" +msgstr "Ýsmi bu harfle baþlayan sanatçýlarý göster" + +#: ../../docs/index.php:39 +msgid "Welcome to" +msgstr "Hoþgeldiniz" + +#: ../../docs/index.php:41 +msgid "you are currently logged in as" +msgstr "Kullanýcý adýnýz" + +#: ../../docs/index.php:72 +msgid "Most Popular Songs" +msgstr "En popüler þarkýlar" + +#: ../../docs/index.php:79 +msgid "Most Popular Artists" +msgstr "En popüler sanatçýlar" + +#: ../../docs/index.php:91 +msgid "Newest Album Additions" +msgstr "Son eklenen albümler" + +#: ../../docs/index.php:98 +msgid "Newest Artist Additions" +msgstr "Son eklenen sanatçýlar" + +#: ../../docs/albums.php:43 +msgid "Album Art Cleared" +msgstr "Albüm kapaðý silindi" + +#: ../../docs/albums.php:43 +msgid "Album Art information has been removed form the database" +msgstr "Albüm kapaðý ile ilgili bilgiler veritabanýndan silindi" + +#: ../../docs/albums.php:75 +msgid "Album Art Located" +msgstr "Albüm kapaðý bulundu" + +#: ../../docs/albums.php:75 +msgid "Album Art information has been located in Amazon. If incorrect, click \"Reset Album Art\" below to remove the artwork." +msgstr "Albüm kapaðý Amazon'da bulundu. Doðru olmadýðý takdirde \"Albüm kapaðý silinsin\" i týklayarak bilgileri silin." + +#: ../../docs/albums.php:83 +#: ../../docs/albums.php:93 +msgid "Get Art" +msgstr "Albüm kapaðý çaðýr" + +#: ../../docs/albums.php:87 +msgid "Album Art Not Located" +msgstr "Albüm kapaðý bulunamadý" + +#: ../../docs/albums.php:87 +msgid "Album Art could not be located at this time. This may be due to Amazon being busy, or the album not being present in their collection." +msgstr "Albüm kapaðý bulunamadý. Bu Amazon'un meþgul yada bu albümün orada olmayýþýndan ileri gelebilir." + +#: ../../docs/albums.php:123 +#: ../../docs/albums.php:138 +msgid "All Albums" +msgstr "Tüm albümler" + +#: ../../docs/albums.php:124 +#: ../../docs/albums.php:131 +msgid "<u>S</u>how all albums" +msgstr "Tüm albümleri göster" + +#: ../../docs/albums.php:130 +msgid "Albums with no artwork" +msgstr "Kapaksýz Albümler" + +#: ../../docs/albums.php:139 +#: ../../docs/albums.php:146 +#: ../../docs/albums.php:151 +msgid "<u>S</u>how only albums starting with" +msgstr "Ýsmi bu harfle baþlayan albümleri göster" + +#: ../../docs/albums.php:145 +msgid "Select a starting letter or Show all" +msgstr "Baþlangýç harfini seç yada hepsini görüntüle" + +#: ../../docs/amp-mpd.php:32 +msgid "Error Connecting" +msgstr "Baðlantý hatasý" + +#: ../../templates/show_user_registration.inc.php:28 +#: ../../templates/show_install_account.inc.php:60 +#: ../../templates/show_users.inc:39 +#: ../../templates/userform.inc:41 +msgid "Username" +msgstr "Kullanýcý adý" + +#: ../../templates/show_user_registration.inc.php:36 +#: ../../templates/userform.inc:49 +msgid "Full Name" +msgstr "Tam Ýsim" + +#: ../../templates/show_user_registration.inc.php:44 +#: ../../templates/show_user.inc.php:40 +#: ../../templates/userform.inc:56 +msgid "E-mail" +msgstr "Elektronik posta" + +#: ../../templates/show_user_registration.inc.php:52 +#: ../../templates/show_install_account.inc.php:64 +#: ../../templates/show_login_form.inc:44 +#: ../../templates/userform.inc:64 +msgid "Password" +msgstr "Parola" + +#: ../../templates/show_user_registration.inc.php:60 +#: ../../templates/show_user.inc.php:75 +#: ../../templates/userform.inc:73 +msgid "Confirm Password" +msgstr "Parola doðrulama" + +#: ../../templates/show_user_registration.inc.php:69 +msgid "Register User" +msgstr "Kullanýcý kaydý" + +#: ../../templates/show_confirmation.inc.php:30 +msgid "Continue" +msgstr "Devam" + +#: ../../templates/show_install_account.inc.php:35 +#: ../../templates/show_install.inc:34 +#: ../../templates/show_install_config.inc:35 +msgid "Ampache Installation" +msgstr "Ampache'nin Kuruluþu" + +#: ../../templates/show_install_account.inc.php:37 +#: ../../templates/show_install.inc:36 +#: ../../templates/show_install_config.inc:37 +msgid "This Page handles the installation of the ampache database and the creation of the ampache.cfg file. Before you continue please make sure that you have the following pre-requisits" +msgstr "Bu sayfa Ampache veritabanýnýn ve ampache.cfg'nin kurulmasýný saðlýyor. Devam etmeden önce asaðýda istenenleri yerine getiriniz" + +#: ../../templates/show_install_account.inc.php:40 +#: ../../templates/show_install.inc:39 +#: ../../templates/show_install_config.inc:40 +msgid "A MySQL Server with a username and password that can create/modify databases" +msgstr "Bir MySQL Sunucusu ve bunun veritabaný ekleyebilen ve düzenleyebilen kullanýcý giriþi ve parolasý" + +#: ../../templates/show_install_account.inc.php:41 +#: ../../templates/show_install_config.inc:41 +msgid "Your webserver configured so that your config directory is not visable to the web" +msgstr "Web Sunucunuz yapýlandýrma klasörünüzün görünmeyeceði þekilde ayarlanmýþ olmalý" + +#: ../../templates/show_install_account.inc.php:42 +#: ../../templates/show_install.inc:41 +#: ../../templates/show_install_config.inc:42 +msgid "Your webserver has read access to the /sql/ampache.sql file and the /config/ampache.cfg.dist file" +msgstr "Web Sunucunuz /sql/ampache.sql ve /config/ampache.cfg.dist dosyalarýný okuyabilmeli " + +#: ../../templates/show_install_account.inc.php:44 +#: ../../templates/show_install.inc:43 +#: ../../templates/show_install_config.inc:44 +msgid "Once you have ensured that you have the above requirements please fill out the information below. You will only be asked for the required config values. If you would like to make changes to your ampache install at a later date simply edit /config/ampache.cfg" +msgstr "Yukarýdaki istenenleri saðladýktan sonra aþaðýda istenen verileri giriniz. Yalnýz gerekli olanlar sorulacaktýr. Ýsterseniz daha sonra /config/ampache.cfg dosyasýna deðiþiklikler yapabilirsiniz" + +#: ../../templates/show_install_account.inc.php:49 +#: ../../templates/show_install.inc:47 +#: ../../templates/show_install_config.inc:49 +msgid "Step 1 - Creating and Inserting the Ampache Database" +msgstr "Ýlk Adým - Ampache veritabanýnýn kurulmasý ve doldurulmasý" + +#: ../../templates/show_install_account.inc.php:50 +#: ../../templates/show_install.inc:51 +#: ../../templates/show_install_config.inc:50 +msgid "Step 2 - Creating the Ampache.cfg file" +msgstr "Ýkinci Adým - amapche.cfg'nin kurulmasý" + +#: ../../templates/show_install_account.inc.php:51 +#: ../../templates/show_install.inc:52 +#: ../../templates/show_install_config.inc:54 +msgid "Step 3 - Setup Initial Account" +msgstr "Üçüncü Adým - Ýlk kullanýcýnýn kurulmasý" + +#: ../../templates/show_install_account.inc.php:53 +msgid "This step creates your initial Ampache admin account. Once your admin account has been created you will be directed to the login page" +msgstr "Bu adýmda ilk yönetici hesabý oluþturuluyor. Oluþturulduktan sonra giriþ sayfasýna yönlendirileceksiniz." + +#: ../../templates/show_install_account.inc.php:69 +msgid "Create Account" +msgstr "Hesap ekle" + +#: ../../templates/show_user.inc.php:31 +#: ../../templates/show_access_list.inc:47 +#: ../../templates/show_add_access.inc:40 +#: ../../templates/customize_catalog.inc:29 +msgid "Name" +msgstr "Ýsim" + +#: ../../templates/show_user.inc.php:48 +msgid "View Limit" +msgstr "Sýnýrlarý görüntüle" + +#: ../../templates/show_user.inc.php:56 +msgid "Update Profile" +msgstr "Profil güncelleme" + +#: ../../templates/show_user.inc.php:67 +msgid "Enter password" +msgstr "Parola giriþ" + +#: ../../templates/show_user.inc.php:83 +msgid "Change Password" +msgstr "Parola deðiþtirme" + +#: ../../templates/show_user.inc.php:91 +msgid "Clear Stats" +msgstr "Ýstatistikleri sil" + +#: ../../templates/show_confirm_action.inc.php:28 +msgid "Yes" +msgstr "Evet" + +#: ../../modules/class/stream.php:192 +msgid "Opened for writting" +msgstr "Yazmak için açýldý" + +#: ../../modules/class/stream.php:197 +msgid "Error, cannot write" +msgstr "Hata, yazýlamýyor" + +#: ../../modules/class/stream.php:208 +msgid "Error, cannot write song in file" +msgstr "Hata, þarký dosyaya yazýlamadý" + +#: ../../modules/class/stream.php:214 +msgid "Closed after write" +msgstr "Yazýldýktan sonra kapatýldý" + +#: ../../modules/class/album.php:126 +msgid "Various" +msgstr "Çeþitli" + +#: ../../modules/class/album.php:241 +#: ../../modules/class/catalog.php:267 +#: ../../modules/class/catalog.php:527 +msgid "Error: Unable to open" +msgstr "Hata: açýlamadý" + +#: ../../modules/class/catalog.php:289 +msgid "Error: Unable to change to directory" +msgstr "Hata: klasöre geçiþ yapýlamadý" + +#: ../../modules/class/catalog.php:312 +msgid "Error: Unable to get filesize for" +msgstr "Hata: dosya boyutu sorgulanamadý" + +#: ../../modules/class/catalog.php:332 +msgid "Added" +msgstr "Eklendi" + +#: ../../modules/class/catalog.php:342 +msgid "is not readable by ampache" +msgstr "Ampache tarafýndan okunamadý" + +#: ../../modules/class/catalog.php:402 +msgid "Found in ID3" +msgstr "ID3 içinde bulundu" + +#: ../../modules/class/catalog.php:406 +msgid "Found on Amazon" +msgstr "Amazon'da bulundu" + +#: ../../modules/class/catalog.php:410 +msgid "Found in Folder" +msgstr "Klasörde bulundu" + +#: ../../modules/class/catalog.php:414 +msgid "Found" +msgstr "Bulundu" + +#: ../../modules/class/catalog.php:417 +msgid "Not Found" +msgstr "Bulunamadý" + +#: ../../modules/class/catalog.php:425 +msgid "Searched" +msgstr "Arandý" + +#: ../../modules/class/catalog.php:578 +msgid "Starting Dump Album Art" +msgstr "Albüm kapaðý boþaltmasý baþlýyor" + +#: ../../modules/class/catalog.php:598 +msgid "Written" +msgstr "Yazýldý" + +#: ../../modules/class/catalog.php:607 +msgid "Error unable to open file for writting" +msgstr "Hata: dosya yazmak için açýlamadý" + +#: ../../modules/class/catalog.php:614 +msgid "Album Art Dump Complete" +msgstr "Albüm kapaðý boþaltmasý tamamlandý" + +#: ../../modules/class/catalog.php:681 +msgid "Starting Catalog Build" +msgstr "Katalog yapýlandýrma baþladý" + +#: ../../modules/class/catalog.php:686 +msgid "Running Remote Sync" +msgstr "Uzak anuyum sürüyor" + +#: ../../modules/class/catalog.php:696 +#: ../../modules/class/catalog.php:843 +msgid "Starting Album Art Search" +msgstr "Albüm kapaðý aramasý baþlýyor" + +#: ../../modules/class/catalog.php:706 +msgid "Catalog Finished" +msgstr "Katalog tamamlandý" + +#: ../../modules/class/catalog.php:706 +#: ../../modules/class/catalog.php:862 +msgid "Total Time" +msgstr "Genel Süre" + +#: ../../modules/class/catalog.php:706 +#: ../../modules/class/catalog.php:863 +msgid "Total Songs" +msgstr "Tüm Þarkýlar" + +#: ../../modules/class/catalog.php:707 +#: ../../modules/class/catalog.php:863 +msgid "Songs Per Seconds" +msgstr "Þarký/Saniye" + +#: ../../modules/class/catalog.php:741 +msgid "Updated" +msgstr "Güncelleþtirildi" + +#: ../../modules/class/catalog.php:748 +msgid "No Update Needed" +msgstr "Güncelleþtirme gereksiz" + +#: ../../modules/class/catalog.php:823 +msgid "Starting New Song Search on" +msgstr "Yeni þarký arama baþladý" + +#: ../../modules/class/catalog.php:823 +msgid "catalog" +msgstr "Katalog" + +#: ../../modules/class/catalog.php:827 +msgid "Running Remote Update" +msgstr "Uzak güncelleme sürüyor" + +#: ../../modules/class/catalog.php:862 +msgid "Catalog Update Finished" +msgstr "Katalog güncelleme tamamlandý" + +#: ../../modules/class/catalog.php:876 +#: ../../lib/mpd.php:31 +msgid "Error" +msgstr "Hata" + +#: ../../modules/class/catalog.php:876 +msgid "Unable to load XMLRPC library, make sure XML-RPC is enabled" +msgstr "XMLRPC kitaplýðý yüklenemedi, XML-RPC'nin etkinleþtirilmiþ olduðunu kontrol edin" + +#: ../../modules/class/catalog.php:908 +#: ../../modules/class/catalog.php:923 +msgid "Error connecting to" +msgstr "Hata, baðlanilamýyor: " + +#: ../../modules/class/catalog.php:908 +#: ../../modules/class/catalog.php:923 +msgid "Code" +msgstr "Kod" + +#: ../../modules/class/catalog.php:908 +#: ../../modules/class/catalog.php:923 +msgid "Reason" +msgstr "Neden" + +#: ../../modules/class/catalog.php:928 +msgid "Completed updating remote catalog(s)" +msgstr "Uzak katalog(lar) güncellendi" + +#: ../../modules/class/catalog.php:1016 +msgid "Checking" +msgstr "Saðlama yapýlýyor" + +#: ../../modules/class/catalog.php:1073 +msgid "Catalog Clean Done" +msgstr "Kalalog temizleme tamamlandý" + +#: ../../modules/class/catalog.php:1073 +msgid "files removed" +msgstr "dosya silindi" + +#: ../../modules/class/catalog.php:1313 +msgid "Updating the" +msgstr "Güncelleþtiriliyor" + +#: ../../modules/class/catalog.php:1313 +#: ../../templates/admin_menu.inc:35 +#: ../../templates/show_admin_index.inc:34 +msgid "Catalog" +msgstr "Katalog" + +#: ../../modules/class/catalog.php:1314 +msgid "songs found checking tag information." +msgstr "þarký etiket bilgisi taramasý sonucu bulundu." + +#: ../../modules/class/song.php:246 +msgid "Title" +msgstr "Þarký adý" + +#: ../../modules/class/song.php:246 +#: ../../modules/class/song.php:250 +#: ../../modules/class/song.php:254 +#: ../../modules/class/song.php:258 +#: ../../modules/class/song.php:262 +#: ../../modules/class/song.php:266 +#: ../../modules/class/song.php:270 +#: ../../modules/class/song.php:275 +#: ../../modules/class/song.php:280 +#: ../../modules/class/song.php:284 +#: ../../modules/class/song.php:288 +#: ../../modules/class/song.php:293 +msgid "updated to" +msgstr "güncelleþtirildi" + +#: ../../modules/class/song.php:254 +msgid "Rate" +msgstr "Oran" + +#: ../../modules/class/song.php:258 +msgid "Mode" +msgstr "Kip" + +#: ../../modules/class/song.php:266 +#: ../../templates/show_songs.inc:32 +#: ../../templates/show_songs.inc:36 +msgid "Track" +msgstr "Parça" + +#: ../../modules/class/song.php:270 +msgid "Filesize" +msgstr "Dosya boyutu" + +#: ../../modules/class/song.php:284 +#: ../../templates/show_albums.inc:43 +#: ../../templates/show_albums.inc:72 +msgid "Year" +msgstr "Yýl" + +#: ../../modules/class/song.php:288 +#: ../../templates/flag.inc:66 +#: ../../templates/list_flagged.inc:46 +msgid "Comment" +msgstr "Yorum" + +#: ../../modules/lib.php:51 +msgid "day" +msgstr "gün" + +#: ../../modules/lib.php:54 +msgid "days" +msgstr "gün" + +#: ../../modules/lib.php:58 +msgid "hour" +msgstr "saat" + +#: ../../modules/lib.php:61 +msgid "hours" +msgstr "saat" + +#: ../../modules/lib.php:78 +msgid "Catalog Statistics" +msgstr "Katalog istatistikleri" + +#: ../../modules/lib.php:81 +msgid "Total Users" +msgstr "Tüm Kullanýcýlar" + +#: ../../modules/lib.php:85 +msgid "Connected Users" +msgstr "Baðlanmýþ Kullanýcýlar" + +#: ../../modules/lib.php:89 +#: ../../templates/menu.inc:32 +#: ../../templates/show_artists.inc:42 +#: ../../templates/show_artists.inc:65 +msgid "Albums" +msgstr "Albümler" + +#: ../../modules/lib.php:93 +#: ../../templates/menu.inc:33 +msgid "Artists" +msgstr "Sanatçýlar" + +#: ../../modules/lib.php:97 +#: ../../templates/show_albums.inc:41 +#: ../../templates/show_albums.inc:71 +#: ../../templates/show_artists.inc:41 +#: ../../templates/show_artists.inc:64 +msgid "Songs" +msgstr "Þarkýlar" + +#: ../../modules/lib.php:101 +msgid "Catalog Size" +msgstr "Katalog boyutu" + +#: ../../modules/lib.php:105 +msgid "Catalog Time" +msgstr "Katalog süresi" + +#: ../../modules/lib.php:159 +msgid "Play Random Selection" +msgstr "Rasgele seçilenleri çal" + +#: ../../modules/lib.php:166 +msgid "Item count" +msgstr "Öðe sayýsý" + +#: ../../modules/lib.php:178 +#: ../../templates/show_albums.inc:58 +#: ../../templates/show_artists.inc:55 +msgid "All" +msgstr "Hepsi" + +#: ../../modules/lib.php:180 +msgid "From genre" +msgstr "Kategoriden" + +#: ../../modules/lib.php:190 +msgid "Favor Unplayed" +msgstr "Çalýnmamýþlardan" + +#: ../../modules/lib.php:191 +msgid "Full Albums" +msgstr "Tüm Albümler" + +#: ../../modules/lib.php:192 +msgid "Full Artist" +msgstr "Tüm Sanatçýlar" + +#: ../../modules/lib.php:201 +msgid "from catalog" +msgstr "Katalogdan" + +#: ../../modules/lib.php:212 +msgid "Play Random Songs" +msgstr "Rasgele þarký çal" + +#: ../../modules/lib.php:921 +msgid "Public" +msgstr "Genel" + +#: ../../modules/lib.php:922 +msgid "Your Private" +msgstr "Size özel" + +#: ../../modules/lib.php:923 +msgid "Other Private" +msgstr "Diðerlerine özel" + +#: ../../modules/lib.php:1004 +#: ../../templates/show_artist.inc:102 +#: ../../templates/show_songs.inc:168 +msgid "View" +msgstr "Görüntüle" + +#: ../../modules/lib.php:1022 +#: ../../templates/show_artist.inc:81 +#: ../../templates/show_albums.inc:61 +#: ../../templates/show_songs.inc:110 +#: ../../templates/show_album.inc:61 +msgid "Download" +msgstr "Ýndirme" + +#: ../../modules/lib.php:1033 +msgid "There are no playlists of this type" +msgstr "Bu çeþit bir þarký listesi bulunamadý" + +#: ../../modules/lib.php:1061 +msgid "Create a new playlist" +msgstr "Þarký listesi oluþtur" + +#: ../../modules/admin.php:45 +msgid "Manage Users" +msgstr "Kullanýcýlarý düzenle" + +#: ../../modules/admin.php:47 +msgid "Add a new user" +msgstr "Kullanýcý ekle" + +#: ../../lib/search.php:52 +#: ../../lib/search.php:68 +#: ../../lib/search.php:84 +#: ../../lib/search.php:100 +#: ../../lib/search.php:116 +#: ../../lib/search.php:133 +#: ../../lib/search.php:145 +#: ../../lib/search.php:161 +#: ../../lib/search.php:177 +msgid "No Results Found" +msgstr "Sonuç bulunamadý" + +#: ../../lib/ui.php:180 +#: ../../templates/show_artist.inc:100 +#: ../../templates/show_songs.inc:166 +msgid "Playlist" +msgstr "Þarký listesi" + +#: ../../lib/ui.php:180 +msgid "New" +msgstr "Yeni" + +#: ../../lib/ui.php:181 +msgid "View All" +msgstr "Hepsini görüntüle" + +#: ../../lib/ui.php:305 +msgid "Show w/o art" +msgstr "Kapaksýz göster" + +#: ../../lib/ui.php:307 +msgid "Show all" +msgstr "Tümünü görüntüle" + +#: ../../lib/preferences.php:199 +msgid "Enable" +msgstr "Etkinleþtir" + +#: ../../lib/preferences.php:200 +#: ../../templates/show_users.inc:63 +msgid "Disable" +msgstr "Edilgenleþtir" + +#: ../../lib/preferences.php:210 +#: ../../templates/add_catalog.inc:60 +msgid "Local" +msgstr "Yerel" + +#: ../../lib/preferences.php:211 +msgid "Stream" +msgstr "Akým" + +#: ../../lib/preferences.php:212 +msgid "IceCast" +msgstr "IceCast" + +#: ../../lib/preferences.php:213 +msgid "Downsample" +msgstr "Kaliteyi düþür (downsample)" + +#: ../../lib/preferences.php:214 +msgid "Music Player Daemon" +msgstr "Music Player Daemon" + +#: ../../lib/preferences.php:221 +msgid "M3U" +msgstr "M3U" + +#: ../../lib/preferences.php:222 +msgid "Simple M3U" +msgstr "Simple M3U" + +#: ../../lib/preferences.php:223 +msgid "PLS" +msgstr "PLS" + +#: ../../lib/preferences.php:224 +msgid "Asx" +msgstr "Asx" + +#: ../../lib/preferences.php:231 +msgid "English" +msgstr "Ýngilizce" + +#: ../../lib/preferences.php:232 +msgid "German" +msgstr "Almanca" + +#: ../../lib/preferences.php:233 +msgid "French" +msgstr "Fransýzca" + +#: ../../lib/Browser.php:867 +msgid "file" +msgstr "dosya" + +#: ../../lib/Browser.php:871 +msgid "File uploads not supported." +msgstr "Dosya yükleme desteði verilmiyor." + +#: ../../lib/Browser.php:889 +msgid "No file uploaded" +msgstr "Hicbir dosya yüklenmedi." + +#: ../../lib/Browser.php:896 +#, php-format +msgid "There was a problem with the file upload: No %s was uploaded." +msgstr "Dosya yüklemede sorun çýktý: %s yüklenemedi" + +#: ../../lib/Browser.php:901 +#, php-format +msgid "There was a problem with the file upload: The %s was larger than the maximum allowed size (%d bytes)." +msgstr "Dosya yüklemede sorun çýktý: %s müsaade edilen azami boyutu aþýyor (%d byte)" + +#: ../../lib/Browser.php:903 +#, php-format +msgid "There was a problem with the file upload: The %s was only partially uploaded." +msgstr "Gönderilen dosya (%s) yalnýz kýsmen yüklendi." + +#: ../../lib/mpd.php:31 +msgid "Could not add" +msgstr "Eklenemedi" + +#: ../../templates/flag.inc:43 +msgid "Flag song" +msgstr "Þarkýyý iþaretle" + +#: ../../templates/flag.inc:45 +msgid "Flag the following song as having one of the problems listed below. Site admins will then take the appropriate action for the flagged files." +msgstr "Þarkýyý aþaðýda gösterilen sorunlardan biriyle iliþkili olarak iþaretle. Site yöneticileri bu iþaretli dosyalar hakkýnda gereken iþlemleri yaparlar." + +#: ../../templates/flag.inc:62 +msgid "Reason to flag" +msgstr "Ýþaretleme nedeni" + +#: ../../templates/flag.inc:73 +msgid "Flag Song" +msgstr "Þarkýyý iþaretle" + +#: ../../templates/admin_menu.inc:33 +#: ../../templates/show_admin_index.inc:32 +msgid "Users" +msgstr "Kullanýcýlar" + +#: ../../templates/admin_menu.inc:34 +msgid "Mail Users" +msgstr "Kullanýcýlara mesaj" + +#: ../../templates/admin_menu.inc:36 +#: ../../templates/show_admin_index.inc:35 +msgid "Admin Preferences" +msgstr "Yönetici tercihleri" + +#: ../../templates/admin_menu.inc:37 +#: ../../templates/show_admin_index.inc:36 +#: ../../templates/catalog.inc:98 +msgid "Access Lists" +msgstr "Eriþim listesi" + +#: ../../templates/show_artist.inc:31 +msgid "Albums by" +msgstr "Albümler" + +#: ../../templates/show_artist.inc:33 +msgid "Show All Songs By" +msgstr "Tüm þarkýlarýný görüntüle: " + +#: ../../templates/show_artist.inc:34 +msgid "Play All Songs By" +msgstr "Tüm þarkýlarýný çal: " + +#: ../../templates/show_artist.inc:35 +msgid "Play Random Songs By" +msgstr "Rasgele þarkýlarýný çal: " + +#: ../../templates/show_artist.inc:37 +#: ../../templates/show_album.inc:58 +msgid "Update from tags" +msgstr "Etiketlerden güncelle" + +#: ../../templates/show_artist.inc:50 +msgid "Select" +msgstr "Seçim" + +#: ../../templates/show_artist.inc:52 +msgid "Cover" +msgstr "Kapak" + +#: ../../templates/show_artist.inc:53 +msgid "Album Name" +msgstr "Albüm adý" + +#: ../../templates/show_artist.inc:54 +msgid "Album Year" +msgstr "Albüm yýlý" + +#: ../../templates/show_artist.inc:55 +msgid "Total Tracks" +msgstr "Tüm parçalar" + +#: ../../templates/show_artist.inc:100 +#: ../../templates/show_songs.inc:166 +msgid "Add to" +msgstr "Ekle" + +#: ../../templates/show_mpdplay.inc:33 +msgid "MPD Play Control" +msgstr "MPD Kontrol" + +#: ../../templates/show_mpdplay.inc:43 +#: ../../templates/list_header.inc:69 +#: ../../templates/show_localplay.inc:39 +msgid "Prev" +msgstr "Önceki" + +#: ../../templates/show_mpdplay.inc:44 +#: ../../templates/show_localplay.inc:40 +msgid "Stop" +msgstr "Dur" + +#: ../../templates/show_mpdplay.inc:46 +#: ../../templates/show_localplay.inc:42 +msgid "Pause" +msgstr "Durakla" + +#: ../../templates/show_mpdplay.inc:47 +#: ../../templates/list_header.inc:92 +#: ../../templates/show_localplay.inc:43 +msgid "Next" +msgstr "Sonraki" + +#: ../../templates/show_mpdplay.inc:52 +msgid "Loop" +msgstr "Döngü" + +#: ../../templates/show_mpdplay.inc:54 +msgid "Loop is off" +msgstr "Döngü kapalý" + +#: ../../templates/show_mpdplay.inc:55 +msgid "Loop is on" +msgstr "Döngü açýk" + +#: ../../templates/show_mpdplay.inc:68 +#: ../../templates/show_now_playing.inc:34 +msgid "Now Playing" +msgstr "Þu anda çalan" + +#: ../../templates/show_mpdplay.inc:81 +msgid "Refresh the Playlist Window" +msgstr "Þarký Listesi görüntüsünü yenile" + +#: ../../templates/show_mpdplay.inc:81 +msgid "refresh now" +msgstr "þimdi yenile" + +#: ../../templates/show_mpdplay.inc:89 +msgid "Server Playlist" +msgstr "Sunucu þarký listesi" + +#: ../../templates/show_mpdplay.inc:122 +msgid "Click to shuffle (randomize) the playlist" +msgstr "Þarký Listesini karýþtýrmak (rasgele) icin týkla" + +#: ../../templates/show_mpdplay.inc:122 +msgid "shuffle" +msgstr "rasgele" + +#: ../../templates/show_mpdplay.inc:123 +msgid "Click the clear the playlist" +msgstr "Þarký listesini silmek için týkla" + +#: ../../templates/show_mpdplay.inc:123 +msgid "clear" +msgstr "sil" + +#: ../../templates/show_admin_index.inc:30 +msgid "Admin Section" +msgstr "Yönetici Kýsmý" + +#: ../../templates/show_admin_index.inc:32 +msgid "Create/Modify User Accounts for Ampache" +msgstr "Ampache kullanýcý hesabý ekle/güncelle" + +#: ../../templates/show_admin_index.inc:33 +msgid "Mail" +msgstr "Mesaj" + +#: ../../templates/show_admin_index.inc:33 +msgid "Mail your users to notfiy them of changes" +msgstr "Kullanýcýlara mail ile yenilikleri bildir" + +#: ../../templates/show_admin_index.inc:34 +msgid "Create/Update/Clean your catalog here" +msgstr "Buradan katalog ekle/güncelleþtir/sil" + +#: ../../templates/show_admin_index.inc:35 +msgid "Modify Site-wide preferences" +msgstr "Site genelinde geçerli olan tercihleri düzenle" + +#: ../../templates/show_admin_index.inc:36 +msgid "Modify Access List Permissions" +msgstr "Eriþim listesindeki izinleri düzenle" + +#: ../../templates/show_admin_index.inc:36 +msgid "Must have access_control=true in ampache.cfg" +msgstr "Ampache.cfg'de 'access_control=true' nun bulunmasý zorunludur" + +#: ../../templates/show_test.inc:29 +msgid "Ampache Debug" +msgstr "Ampache Hata Ayýklamasý" + +#: ../../templates/show_test.inc:30 +msgid "You've reached this page because a configuration error has occured. Debug Information below" +msgstr "Bir yapýlandýrma hatasý yüzünden bu sayfaya yönlendirildiniz. Hata ayýklamasýný asaðýda görebilirsiniz" + +#: ../../templates/show_test.inc:35 +msgid "STATUS" +msgstr "Durum" + +#: ../../templates/show_test.inc:39 +msgid "PHP Version" +msgstr "PHP Version" + +#: ../../templates/show_test.inc:54 +msgid "This tests to make sure that you are running a version of PHP that is known to work with Ampache." +msgstr "Bu test PHP'nin Ampache ile uyuþan bir sürümünün kullanýldýðýný tasdik ediyor." + +#: ../../templates/show_test.inc:58 +msgid "Mysql for PHP" +msgstr "Mysql for PHP" + +#: ../../templates/show_test.inc:73 +msgid "This test checks to see if you have the mysql extensions loaded for PHP. These are required for Ampache to work." +msgstr "Bu testler PHP'nin mysql uzantýlarýnýn varlýðýný kontrol ediyor. Bu uzantýlar olmazsa Ampache calýþmaz." + +#: ../../templates/show_test.inc:77 +msgid "PHP Session Support" +msgstr "PHP Session Support" + +#: ../../templates/show_test.inc:92 +msgid "This test checks to make sure that you have PHP session support enabled. Sessions are required for Ampache to work." +msgstr "Bu testler PHP'nin oturum desteðinin varlýðýný kontrol ediyor. Oturum desteði olmazsa Ampache calýþmaz." + +#: ../../templates/show_test.inc:96 +msgid "PHP ICONV Support" +msgstr "PHP ICONV desteði" + +#: ../../templates/show_test.inc:110 +msgid "This test checks to make sure you have Iconv support installed. Iconv support is not required for Ampache, but it is highly recommended" +msgstr "Bu testler PHP'nin ICONV desteðinin varlýðýný kontrol ediyor. ICONV Ampache'nin çalýþmasý icin gerekli olmasada tavsiyemizdir" + +#: ../../templates/show_test.inc:114 +#: ../../templates/show_install_config.inc:88 +msgid "Ampache.cfg Exists" +msgstr "Ampache.cfg bulundu" + +#: ../../templates/show_test.inc:129 +msgid "" +"This attempts to read /config/ampache.cfg If this fails either the ampache.cfg is not in the correct locations or\n" +"\tit is not currently readable by your webserver." +msgstr "Þimdi ampache.cfg okunmaya calýþýlacak. Eger baþarýsýz olunursa ya doðru yerde deðildir yada web sunucunuz tarafýndan okunamýyordur." + +#: ../../templates/show_test.inc:135 +#: ../../templates/show_install_config.inc:105 +msgid "Ampache.cfg Configured?" +msgstr "Ampache.cfg'yi yapýlandýrdýnýzmý ?" + +#: ../../templates/show_test.inc:152 +msgid "" +"This test makes sure that you have set all of the required config variables and that we are able to \n" +"\tcompleatly parse your config file" +msgstr "Bu test gerekli tüm yapýlandýrma deðiþkenlerinin ayarlandýðýný ve yapýlandýrma dosyasýnýn ayrýþtýrýlabileceðini doðrulamak içindir" + +#: ../../templates/show_test.inc:159 +msgid "Ampache.cfg Up to Date?" +msgstr "Ampache.cfg güncel mi?" + +#: ../../templates/show_test.inc:165 +msgid "DB Connection" +msgstr "Veritabaný baðlantýsý" + +#: ../../templates/show_test.inc:181 +msgid "This attempts to connect to your database using the values from your ampache.cfg" +msgstr "Þimdi ampache.cfg'deki verilerle veritabanýnýza ulaþmaya calýsýlacak" + +#: ../../templates/list_flagged.inc:42 +#: ../../templates/show_songs.inc:41 +msgid "Flag" +msgstr "Ýþaret" + +#: ../../templates/list_flagged.inc:43 +msgid "New Flag" +msgstr "Yeni iþaret" + +#: ../../templates/list_flagged.inc:44 +msgid "Flagged by" +msgstr "Ýþaretleyen" + +#: ../../templates/list_flagged.inc:45 +msgid "ID3 Update" +msgstr "ID3 Güncelleme" + +#: ../../templates/list_flagged.inc:69 +msgid "Accept" +msgstr "Kabul" + +#: ../../templates/list_flagged.inc:70 +msgid "Reject" +msgstr "Red" + +#: ../../templates/show_upload.inc:27 +msgid "Please Ensure All Files Are Tagged Correctly" +msgstr "Lütfen tüm dosyalarýn doðru etiketlendirilmesini saðlayýn" + +#: ../../templates/show_upload.inc:30 +msgid "Ampache relies on id3 tags to sort data. If your file is not tagged it may be deleted." +msgstr "Ampache sýralama için ID3 etiketlerini kullanýr. Dosyanýz etiketsizse silinebilir." + +#: ../../templates/show_upload.inc:34 +msgid "max_upload_size" +msgstr "max_upload_size (azami_yükleme_hacmi)" + +#: ../../templates/show_upload.inc:59 +#: ../../templates/menu.inc:39 +msgid "Upload" +msgstr "Yükleme" + +#: ../../templates/show_access_list.inc:34 +msgid "Host Access to Your Catalog" +msgstr "Kataloða makine eriþimi" + +#: ../../templates/show_access_list.inc:43 +msgid "Add Entry" +msgstr "Giriþ ekle" + +#: ../../templates/show_access_list.inc:48 +msgid "Start Address" +msgstr "Ýlk Adres" + +#: ../../templates/show_access_list.inc:49 +msgid "End Address" +msgstr "Son Adres" + +#: ../../templates/show_access_list.inc:50 +#: ../../templates/show_add_access.inc:58 +msgid "Level" +msgstr "Düzey" + +#: ../../templates/show_access_list.inc:65 +msgid "Revoke" +msgstr "Ýptal" + +#: ../../templates/add_catalog.inc:28 +msgid "Add a Catalog" +msgstr "Katalog ekle" + +#: ../../templates/add_catalog.inc:30 +msgid "In the form below enter either a local path (i.e. /data/music) or the URL to a remote Ampache installation (i.e http://theotherampache.com)" +msgstr "Aþaðidaki forma ya yerel bir yol girin (örn. /data/muzik) yada uzak bir Ampache Sunucusunun adresini (örn. http://benimampachem.com)" + +#: ../../templates/add_catalog.inc:36 +msgid "Catalog Name" +msgstr "Kalalog Ýsmi" + +#: ../../templates/add_catalog.inc:39 +#: ../../templates/customize_catalog.inc:32 +msgid "Auto-inserted Fields" +msgstr "Özdevimli Alanlar" + +#: ../../templates/add_catalog.inc:40 +#: ../../templates/customize_catalog.inc:33 +msgid "album name" +msgstr "albüm ismi" + +#: ../../templates/add_catalog.inc:41 +#: ../../templates/customize_catalog.inc:34 +msgid "artist name" +msgstr "sanatçý ismi" + +#: ../../templates/add_catalog.inc:42 +#: ../../templates/customize_catalog.inc:36 +msgid "id3 comment" +msgstr "id3 açýklamasý" + +#: ../../templates/add_catalog.inc:43 +#: ../../templates/customize_catalog.inc:37 +msgid "genre" +msgstr "kategori" + +#: ../../templates/add_catalog.inc:44 +#: ../../templates/customize_catalog.inc:38 +msgid "track number (padded with leading 0)" +msgstr "þarký numarasý (0 ile baþlar)" + +#: ../../templates/add_catalog.inc:45 +#: ../../templates/customize_catalog.inc:39 +msgid "song title" +msgstr "þarký adý" + +#: ../../templates/add_catalog.inc:46 +#: ../../templates/customize_catalog.inc:40 +msgid "year" +msgstr "yýl" + +#: ../../templates/add_catalog.inc:47 +#: ../../templates/customize_catalog.inc:41 +msgid "other" +msgstr "baþka" + +#: ../../templates/add_catalog.inc:53 +msgid "Path" +msgstr "Yol" + +#: ../../templates/add_catalog.inc:57 +msgid "Catalog Type" +msgstr "Katalog türü" + +#: ../../templates/add_catalog.inc:61 +msgid "Remote" +msgstr "uzaktan" + +#: ../../templates/add_catalog.inc:66 +msgid "ID3 Set Command" +msgstr "ID3 ayarlama komutu" + +#: ../../templates/add_catalog.inc:70 +msgid "Filename Pattern" +msgstr "Dosya adý deseni" + +#: ../../templates/add_catalog.inc:74 +#: ../../templates/customize_catalog.inc:58 +msgid "Folder Pattern" +msgstr "Klasör adý deseni" + +#: ../../templates/add_catalog.inc:74 +#: ../../templates/customize_catalog.inc:58 +msgid "(no leading or ending '/')" +msgstr "(baþýnda yada sonuna '/' olmaksýzýn)" + +#: ../../templates/add_catalog.inc:78 +msgid "Gather Album Art" +msgstr "Albüm kapaklarýný topla" + +#: ../../templates/add_catalog.inc:82 +msgid "ID3V2 Tags" +msgstr "ID3V2 Etiketleri" + +#: ../../templates/add_catalog.inc:85 +msgid "Amazon" +msgstr "Amazon" + +#: ../../templates/add_catalog.inc:88 +msgid "File Folder" +msgstr "Dosya klasörü" + +#: ../../templates/add_catalog.inc:98 +msgid "Add Catalog" +msgstr "Kataloðu ekle" + +#: ../../templates/show_preferences.inc:31 +msgid "Editing" +msgstr "Düzenleme" + +#: ../../templates/show_preferences.inc:31 +msgid "preferences" +msgstr "tercihler" + +#: ../../templates/show_preferences.inc:33 +msgid "Rebuild Preferences" +msgstr "Tercihleri yeniden yapýlandýr" + +#: ../../templates/show_preferences.inc:39 +msgid "Preference" +msgstr "Tercih" + +#: ../../templates/show_preferences.inc:40 +msgid "Value" +msgstr "Deðer" + +#: ../../templates/show_preferences.inc:42 +msgid "Type" +msgstr "Türü" + +#: ../../templates/show_preferences.inc:43 +msgid "Apply to All" +msgstr "Hersine uygula" + +#: ../../templates/show_preferences.inc:58 +msgid "Update Preferences" +msgstr "Tercihleri güncelle" + +#: ../../templates/show_preferences.inc:62 +msgid "Cancel" +msgstr "Ýptal" + +#: ../../templates/show_install.inc:40 +msgid "Your webserver configured so that <ampache_root>/docs is your webroot" +msgstr "Web sunucunuz <ampache_root>/docs 'u temel olarak kullanmalý" + +#: ../../templates/show_install.inc:49 +msgid "This step creates and inserts the Ampache database, as such please provide a mysql account with database creation rights. This step may take a while depending upon the speed of your computer" +msgstr "Bu adýmda Ampache veritabaný oluþturuluyor. Bunun için veritabaný kurma izni olan bir mysql giriþi gerekli. Bu adýmýn süresi bilgisayarýnýzýn hýzýna baðlýdýr " + +#: ../../templates/show_install.inc:58 +#: ../../templates/show_install_config.inc:64 +msgid "Desired Database Name" +msgstr "Düþünülen Veritabaný ismi" + +#: ../../templates/show_install.inc:62 +#: ../../templates/show_install_config.inc:68 +msgid "MySQL Hostname" +msgstr "MySQL makine ismi" + +#: ../../templates/show_install.inc:66 +msgid "MySQL Administrative Username" +msgstr "MySQL yönetici ismi" + +#: ../../templates/show_install.inc:70 +msgid "MySQL Administrative Password" +msgstr "MySQL yönetici parolasý" + +#: ../../templates/show_install.inc:75 +msgid "Insert Database" +msgstr "Veritabanýný ekle" + +#: ../../templates/catalog.inc:33 +msgid "Error: ICONV not found, ID3V2 Tags will not import correctly. See <a href=\"http://php.oregonstate.edu/iconv\">Iconv</a> for information on getting ICONV" +msgstr "Hata: ICONV bulunamadý, ID3V2 etiketleri doðru þekilde ithal edilemeyecek. Bu adresten <a href=\"http://php.oregonstate.edu/iconv\">Iconv</a> ICONV hakkýnda bilgi alabilirsiniz." + +#: ../../templates/catalog.inc:42 +msgid "Update Catalogs" +msgstr "Kataloglarý güncelle" + +#: ../../templates/catalog.inc:68 +msgid "Fast Add" +msgstr "Çabuk ekle" + +#: ../../templates/catalog.inc:75 +msgid "Fast Update" +msgstr "Çabuk güncelle" + +#: ../../templates/catalog.inc:88 +msgid "You don't have any catalogs." +msgstr "Sizin hiç kataloðunuz yok." + +#: ../../templates/catalog.inc:97 +msgid "Add a catalog" +msgstr "Katalog ekle" + +#: ../../templates/catalog.inc:99 +msgid "Show Duplicate Songs" +msgstr "Duble þarkýlarý görüntüle" + +#: ../../templates/catalog.inc:100 +msgid "Show Disabled Songs" +msgstr "Edilgenleþtirilmiþ þarkýlarý görüntüle" + +#: ../../templates/catalog.inc:101 +msgid "Clear Catalog Stats" +msgstr "Katalog istatistiklerini sil" + +#: ../../templates/catalog.inc:102 +msgid "Clear Now Playing" +msgstr "Þu anda çalanlarý sil" + +#: ../../templates/catalog.inc:103 +msgid "Dump Album Art" +msgstr "Albüm kapaðý boþalt" + +#: ../../templates/catalog.inc:104 +msgid "View flagged songs" +msgstr "Ýþaretlenmiþ þarkýlarý görüntüle" + +#: ../../templates/catalog.inc:105 +msgid "Catalog Tools" +msgstr "Katalog araçlarý" + +#: ../../templates/show_login_form.inc:40 +#: ../../templates/show_login_form.inc:54 +msgid "Login" +msgstr "Giriþ" + +#: ../../templates/show_login_form.inc:50 +msgid "Remember Me" +msgstr "Beni hatýrla" + +#: ../../templates/show_add_access.inc:31 +msgid "Add Access for a Host" +msgstr "Eriþim listesine makine ekle" + +#: ../../templates/show_add_access.inc:33 +msgid "Use the form below to add a host that you want to have access to your Ampache catalog." +msgstr "Aþaðýdaki formu kullanarak Ampache kataloðuna eriþmesini istediðiniz makineleri ekleyebilirsiniz." + +#: ../../templates/show_add_access.inc:46 +msgid "Start IP Address" +msgstr "Ýlk ÝP-Adresi" + +#: ../../templates/show_add_access.inc:52 +msgid "End IP Address" +msgstr "Son ÝP-Adresi" + +#: ../../templates/show_add_access.inc:72 +msgid "Add Host" +msgstr "Makine ekle" + +#: ../../templates/customize_catalog.inc:24 +msgid "Settings for catalog in" +msgstr "Katalog ayarlarý:" + +#: ../../templates/customize_catalog.inc:35 +msgid "catalog path" +msgstr "katalog yolu" + +#: ../../templates/customize_catalog.inc:45 +msgid "ID3 set command" +msgstr "ID3 ayarlama komutu" + +#: ../../templates/customize_catalog.inc:51 +msgid "Filename pattern" +msgstr "Dosya adý deseni" + +#: ../../templates/customize_catalog.inc:69 +msgid "Save Catalog Settings" +msgstr "Katalog ayarlarýný kaydet" + +#: ../../templates/menu.inc:31 +msgid "Home" +msgstr "Ýlk Sayfa" + +#: ../../templates/menu.inc:34 +msgid "Playlists" +msgstr "Þarký listeleri" + +#: ../../templates/menu.inc:35 +#: ../../templates/show_search.inc:38 +#: ../../templates/show_search.inc:84 +msgid "Search" +msgstr "Ara" + +#: ../../templates/menu.inc:36 +msgid "Preferences" +msgstr "Tercihler" + +#: ../../templates/menu.inc:57 +#: ../../templates/menu.inc:60 +msgid "Admin" +msgstr "Yönetici" + +#: ../../templates/menu.inc:69 +#: ../../templates/menu.inc:76 +msgid "Account" +msgstr "Hesap" + +#: ../../templates/menu.inc:70 +#: ../../templates/menu.inc:77 +msgid "Stats" +msgstr "Ýstatistikler" + +#: ../../templates/menu.inc:71 +#: ../../templates/menu.inc:78 +#: ../../templates/menu.inc:83 +msgid "Logout" +msgstr "Oturum Sonu" + +#: ../../templates/show_albums.inc:59 +#: ../../templates/show_artists.inc:56 +msgid "Random" +msgstr "Rasgele" + +#: ../../templates/show_users.inc:42 +msgid "Fullname" +msgstr "Tam Ýsim" + +#: ../../templates/show_users.inc:47 +msgid "Last Seen" +msgstr "Son giriþ" + +#: ../../templates/show_users.inc:54 +msgid "Prefs" +msgstr "Tercihler" + +#: ../../templates/show_users.inc:60 +msgid "Set Access" +msgstr "Eriþim Ayarý" + +#: ../../templates/show_users.inc:66 +msgid "On-line" +msgstr "Çevrimiçi" + +#: ../../templates/show_users.inc:88 +msgid "edit" +msgstr "düzenle" + +#: ../../templates/show_users.inc:93 +msgid "prefs" +msgstr "tercihler" + +#: ../../templates/show_users.inc:98 +msgid "delete" +msgstr "sil" + +#: ../../templates/show_users.inc:104 +#: ../../templates/show_users.inc:113 +#: ../../templates/show_users.inc:116 +msgid "set to user" +msgstr "Kullanýcý haline getir" + +#: ../../templates/show_users.inc:105 +#: ../../templates/show_users.inc:109 +#: ../../templates/show_users.inc:117 +msgid "disable" +msgstr "edilgenleþtir" + +#: ../../templates/show_users.inc:108 +#: ../../templates/show_users.inc:112 +msgid "set to admin" +msgstr "'admin' yap" + +#: ../../templates/userform.inc:25 +msgid "Adding a New User" +msgstr "Kullanýcý ekle" + +#: ../../templates/userform.inc:29 +msgid "Editing existing User" +msgstr "Kullanýcýyý düzenle" + +#: ../../templates/userform.inc:81 +msgid "User Access Level" +msgstr "Kullanýcý eriþim düzeyi" + +#: ../../templates/userform.inc:98 +msgid "Add User" +msgstr "Kullanýcý ekle" + +#: ../../templates/userform.inc:103 +msgid "Update User" +msgstr "Kullanýcý güncelle" + +#: ../../templates/show_songs.inc:33 +msgid "Song title" +msgstr "þarký adý" + +#: ../../templates/show_songs.inc:114 +msgid "Direct Link" +msgstr "Doðrudan Eriþim" + +#: ../../templates/show_songs.inc:133 +msgid "Total" +msgstr "Tüm" + +#: ../../templates/show_songs.inc:159 +msgid "Set Track Numbers" +msgstr "Parça numaralarýný iþle" + +#: ../../templates/show_songs.inc:160 +msgid "Remove Selected Tracks" +msgstr "Seçilmiþ parçalarý sil" + +#: ../../templates/show_album.inc:53 +msgid "Play Album" +msgstr "Albümü çal" + +#: ../../templates/show_album.inc:54 +msgid "Play Random from Album" +msgstr "Albümden rasgele çal" + +#: ../../templates/show_album.inc:55 +msgid "Reset Album Art" +msgstr "Albüm kapaðý silinsin" + +#: ../../templates/show_album.inc:56 +msgid "Find Album Art" +msgstr "Albüm kapaðý ara" + +#: ../../templates/show_search.inc:35 +msgid "Search Ampache" +msgstr "Ampache içinde ara" + +#: ../../templates/show_search.inc:42 +msgid "Object Type" +msgstr "Nesne türü" + +#: ../../templates/show_search.inc:75 +msgid "Search Type" +msgstr "Arama türü" + +#: ../../templates/show_install_config.inc:52 +msgid "This steps takes the basic config values, and first attempts to write them out directly to your webserver. If access is denied it will prompt you to download the config file. Please put the downloaded config file in /config" +msgstr "Bu adýmda temel veriler sorgulanýp, web sunucunuza ilk yazma denemesi yapýlacaktýr. Eðer baþarýsýz olunursa size sunulacak olan yapýlandýrma dosyasýný /config klasörüne kaydedin. " + +#: ../../templates/show_install_config.inc:60 +msgid "Web Path" +msgstr "Web Yolu" + +#: ../../templates/show_install_config.inc:72 +msgid "MySQL Username" +msgstr "MySQL kullanýcý adý" + +#: ../../templates/show_install_config.inc:76 +msgid "MySQL Password" +msgstr "MySQL parolasý" + +#: ../../templates/show_install_config.inc:81 +msgid "Write Config" +msgstr "Yapýlandýrmayý kaydet" + +#: ../../templates/show_install_config.inc:125 +msgid "Check for Config" +msgstr "Yapýlandýrma doðrula" + +#: ../../templates/show_localplay.inc:30 +msgid "Local Play Control" +msgstr "Yerel Çalma Düzeni" + +#: ../../templates/show_localplay.inc:35 +msgid "Playback" +msgstr "Çal" + +#: ../../templates/show_localplay.inc:49 +msgid "Volume" +msgstr "Ses" + +#: ../../templates/show_localplay.inc:53 +#: ../../templates/show_localplay.inc:54 +msgid "Increase Volume" +msgstr "Ses aç" + +#: ../../templates/show_localplay.inc:55 +#: ../../templates/show_localplay.inc:56 +msgid "Decrease Volume" +msgstr "Ses kapa" + +#: ../../templates/show_localplay.inc:62 +msgid "Clear queue" +msgstr "Kuyruðu sil" + diff --git a/localplay.php b/localplay.php new file mode 100644 index 00000000..7edeb3ae --- /dev/null +++ b/localplay.php @@ -0,0 +1,92 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Song Document + @discussion Actually play files from albums, artists or just given + a bunch of id's. + Special thanx goes to Mike Payson and Jon Disnard for the means + to do this. +*/ + +require('modules/init.php'); +/* If we are running a demo, quick while you still can! */ +if (conf('demo_mode')) { + exit(); +} + +$web_path = conf('web_path'); + +if($user->prefs['play_type'] != 'local_play') { + echo "You dont have local play enabled!"; + exit; +} + +switch($_REQUEST['submit']) +{ + case ' X ': + $action = "stop"; + break; + case ' > ': + $action = "play"; + break; + case ' = ': + $action = "pause"; + break; + case '|< ': + $action = "prev"; + break; + case ' >|': + $action = "next"; + break; + case (substr_count($_REQUEST['submit'],"+") == '1'): + $amount = trim(substr($_REQUEST['submit'],2,strlen($_REQUEST['submit']-2))); + $action = "volplus"; + break; + case (substr_count($_REQUEST['submit'],"-") == '1'): + $amount = trim(substr($_REQUEST['submit'],2,strlen($_REQUEST['submit']-2))); + $action = "volminus"; + break; + case 'clear': + $action = "clear"; + break; + case 'start': + $action = "start"; + break; + case 'kill': + $action = "kill"; + break; + default: + echo _("Unknown action requested") . ": '$_REQUEST[submit]'<br />"; + exit; +} +$systr = conf('localplay_'.$action); +$systr = str_replace("%AMOUNT%",$amount,$systr); +if (conf('debug')) { log_event($user->username,'localplay',"Exec: $systr"); } +@exec($systr, $output); +$web_path = conf('web_path'); +if($output) + print_r($output); +else + header("Location: $web_path"); + +?> diff --git a/login.php b/login.php new file mode 100644 index 00000000..eb27071f --- /dev/null +++ b/login.php @@ -0,0 +1,109 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/* + + Login our friendly users + +*/ + +$no_session = true; +require_once("modules/init.php"); +set_site_preferences(); + +// +// So we check for a username and password first +// +if ( $_POST['username'] && $_POST['password'] ) { + + if ($_POST['rememberme']) { + setcookie('amp_longsess', '1', time()+3600*24*30*120); + } + + /* If we are in demo mode let's force auth success */ + if (conf('demo_mode')) { + $auth['success'] = 1; + $auth['info']['username'] = "Admin- DEMO"; + $auth['info']['fullname'] = "Administrative User"; + $auth['info']['offset_limit'] = 25; + } + else { + $username = trim($_POST['username']); + $password = trim($_POST['password']); + $auth = authenticate($username, $password); + $user = new User($username); + if ($user->access === 'disabled') { + $auth['success'] = false; + $auth['error'] = "Error: User Disabled please contact Admin"; + } // if user disabled + } // if we aren't in demo mode +} + +// +// If we succeeded in authenticating, create a session +// +if ( ($auth['success'] == 1)) { + + // $auth->info are the fields specified in the config file + // to retrieve for each user + make_local_session_only($auth); + + // + // Not sure if it was me or php tripping out, + // but naming this 'user' didn't work at all + // + $_SESSION['userdata'] = $auth['info']; + // Make sure they are actually trying to get to this site + if (strstr($_POST['referrer'], conf('web_path')) AND !strstr($_POST['referrer'],"install.php") AND !strstr($_POST['referrer'],"login.php") AND !strstr($_POST['referrer'],"update.php")) { + header("Location: " . $_POST['referrer']); + exit(); + } // if we've got a referrer + header("Location: " . conf('web_path') . "/index.php"); + exit(); +} // auth success + + +?> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd"> +<html lang="<?php echo conf('lang'); ?>"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=<?php echo conf('site_charset'); ?>" /> +<title> <?php echo conf('site_title'); ?> </title> + +<?php show_template('style'); ?> + +<script language="javascript"> +function focus(){ document.login.username.focus(); } +</script> + +</head> +<body bgcolor="<?php echo conf('bg_color1'); ?>" onload="focus();"> + +<? + +require(conf('prefix') . "/templates/show_login_form.inc"); + +if (@is_readable(conf('prefix') . '/config/motd.php')) { + include conf('prefix') . '/config/motd.php'; +} + +?> +</body> +</html> diff --git a/logout.php b/logout.php new file mode 100644 index 00000000..d5e4fb81 --- /dev/null +++ b/logout.php @@ -0,0 +1,16 @@ +<?php +require_once("modules/init.php"); +// To end a legitimate session, just call logout. +setcookie("amp_longsess","",null); +logout(); +?> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-transitional.dtd"> +<html> +<head> +<title>Sample logout page</title> +</head> +<body> +Congrats, you are logged out. +<br /><a href="./login.php">login</a> +</body></html> diff --git a/modules/admin.php b/modules/admin.php new file mode 100644 index 00000000..6eb9da04 --- /dev/null +++ b/modules/admin.php @@ -0,0 +1,304 @@ +<?php +/* + + This contains all of the subroutines for handling any + administration function such as prefereneces, etc. + +*/ + +/* + * show_access_list + * + * Used in the access.inc template for getting information about + * remote servers that have access to use this Ampache server's + * catalog. + */ +function show_access_list () { + $dbh = dbh(); + + $sql = "SELECT * FROM access_list"; + $db_result = mysql_query($sql, $dbh); + + if ( mysql_num_rows($db_result) ) { + while ($host = mysql_fetch_object($db_result) ) { + + $ip = int2ip($host->ip); + + print("\t<tr><td bgcolor=\"" . conf('secondary_color') . "\">$host->name</td>". + "<td bgcolor=\"" . conf('secondary_color') . "\">$ip</td>". + "<td bgcolor=\"" . conf('secondary_color') . "\"><a href=\"" . conf('web_path') . "/access.php?action=delete_host&id=$host->id\">Delete</td></tr>\n"); + } + } + else { + print("\t<tr><td bgcolor=\"" . conf('secondary_color') . "\"colspan=\"3\">You don't have any hosts in your access list.</td></tr>\n"); + } +} // show_access_list() + + +/* + * show_manage_users + * + */ + +function show_manage_users () { + + echo "<table class=\"text-box\">\n<tr><td>\n"; + echo "<span class=\"header2\">" . _("Manage Users") . "</span><br />\n"; + echo "<p>Use the following tools to manage the users that access your site.</p>\n"; + echo "<ul>\n\t<li><a href=\"".conf('web_path') . "/admin/users.php?action=show_add_user\">" . _("Add a new user") . "</a></li\n</dl>\n"; + echo "</td></tr></table>"; + + show_users(); +} // show_manage_users() + + +/*! + @function show_user_form + @discussion shows the user form +*/ +function show_user_form ($id, $username, $fullname, $email, $access, $type, $error) { + + require(conf('prefix').'/templates/userform.inc'); + +} // show_user_form() + + +/* + * show_change_password + * + */ + +function show_change_password ($username) { + + $user = get_user($username); + + print("<form name=\"change_password\" method=\"post\" action=\"user.php\">"); + + print("<p style=\"font-size: 10px; font-weight: bold;\">Changing User Password</p>\n"); + + print("<table width=\"90%\">"); + + print("<tr>\n"); + print("<td>Enter password:</td>"); + print("<td><input type=password name=new_password_1 size=30 value=\"\"></td>"); + print("</tr>\n"); + + print("<tr>\n"); + print("<td>Enter password again:</td>"); + print("<td><input type=password name=new_password_2 size=30 value=\"\"></td>"); + print("</tr>\n"); + + print("</table>\n"); + print("<input type=submit name=\"action\" value=\"Change Password\">"); + print("</form>"); +} // show_change_password + +/* + * show_update_user_info + * + */ + +function show_update_user_info ($username) { + + $user = get_user($username); + + $user->offset_limit = abs($user->offset_limit); + + print("<form name=\"change_password\" method=\"post\" action=\"user.php\">"); + + print("<p style=\"font-size: 10px; font-weight: bold;\">Changing User Information for $user->fullname</p>\n"); + + print("<table width=\"90%\">"); + + print("<tr>\n"); + print("<td>Fullname:</td>"); + print("<td><input type=text name=new_fullname size=30 value=\"$user->fullname\"></td>"); + print("</tr>\n"); + + print("<tr>\n"); + print("<td>Email:</td>"); + print("<td><input type=text name=new_email size=30 value=\"$user->email\"></td>"); + print("</tr>\n"); + + print("<tr>\n"); + print("<td>View Limit:</td>"); + print("<td><input type=text name=new_offset size=5 value=\"$user->offset_limit\"></td>"); + print("</tr>\n"); + + print("</table>\n"); + print("<input type=submit name=\"action\" value=\"Update Profile\">"); + print("</form>"); +} // show_update_user_info() + +/* + * show_delete_stats + * + */ + +function show_delete_stats($username) { + print("<form name=\"clear_statistics\" method=\"post\" action=\"user.php\">"); + print("<br>"); + + if ( $username == 'all') { + print("<p style=\"font-size: 10px; font-weight: bold;\">Delete Your Personal Statistics</p>\n"); + } + else { + print("<p style=\"font-size: 10px; font-weight: bold;\">Delete Your Personal Statistics</p>\n"); + } + + print("<input type=submit name=\"action\" value=\"Clear Stats\">"); + print("</form>"); +} // show_delete_stats() + + +/* + * clear_catalog_stats() + * + * Use this to clear the stats for the entire Ampache server. + * + */ + +function clear_catalog_stats() { + $dbh = dbh(); + $sql = "DELETE FROM object_count"; + $result = mysql_query($sql, $dbh); + $sql = "UPDATE song SET played = 'false'"; + $result = mysql_query($sql, $dbh); +} // clear_catalog_stats + + +/* + * check_user_form + * + */ + +function check_user_form ($username, $fullname, $email, $pass1, $pass2, $type) { + global $dbh; + + $sql = "SELECT * FROM user WHERE username='$username'"; + $db_result = mysql_query($sql, $dbh); + + if ( mysql_num_rows($db_result) ) { + return "That username is already taken, please choose another."; + } + + if ( $type == 'new_user' ) { + if ( empty($username) ) { + return "Please fill in a username."; + } + elseif ( ($pass1 != $pass2) || (empty($pass1) || empty($pass2)) ) { + return "Sorry, your passwords do no match."; + } + } + elseif ( empty($fullname) ) { + return "Please fill in a full name."; + } + elseif ( empty($email) ) { + return "Please fill in an email address."; + } + elseif ( ($pass1 != $pass2) || (empty($pass1) || empty($pass2)) ) { + if ( $type == 'new_user' ) { + return "Sorry, your passwords do no match."; + } + } + + return false; +} // check_user_form() + +/* + * get_user + * + */ +function get_user_byid ($id) { + + + $sql = "SELECT * FROM user WHERE id='$id'"; + $db_result = mysql_query($sql, dbh()); + return (mysql_fetch_object($db_result)); +} // get_user_byid() + +function get_user ($username) { + + + $sql = "SELECT * FROM user WHERE username='$username'"; + $db_result = mysql_query($sql, dbh()); + + return (mysql_fetch_object($db_result)); +} // get_user() + +/* + * delete_user + * + */ + +function delete_user ($username) { + + // delete from the user table + $sql = "DELETE FROM user WHERE username='$username'"; + $db_result = mysql_query($sql, dbh()); + + // also delete playlists for user + $sql = "DELETE FROM playlist WHERE owner='$username'"; + $db_result = mysql_query($sql, dbh()); + + delete_user_stats('all'); + +} // delete_user() + +/* + * update_user + * + */ + +function update_user ($username, $fullname, $email, $access) +{ + $dbh = libglue_param(libglue_param('dbh_name')); + if(!$username || !$fullname || !$email || !$access) return 0; + $sql = "UPDATE user ". + "SET fullname='$fullname',". + "email='$email',". + "access='$access'". + "WHERE username='$username'"; + $db_result = mysql_query($sql, $dbh); + if($db_result) return 1; + else return 0; +} // update_user() + +/* + * update_user_info + * + * this for use by 'user' to update limited amounts of info + * + */ + +function update_user_info ($username, $fullname, $email,$offset) { + + $dbh = libglue_param(libglue_param('dbh_name')); + + $sql = "UPDATE user SET fullname='$fullname', email='$email', offset_limit='$offset' WHERE username='$username'"; + $db_result = mysql_query($sql, $dbh); + + // Update current session (so the views are updated) + $_SESSION['offset_limit'] = $offset; + + return ($db_result)?1:0; + +} // update_user_info() + + +/* + * set_user_password + * + */ + +function set_user_password ($username, $password1, $password2) { + + $dbh = libglue_param(libglue_param('dbh_name')); + if($password1 !== $password2) return 0; + + $sql = "UPDATE user SET password=PASSWORD('$password1') WHERE username='$username' LIMIT 1"; + $db_result = mysql_query($sql, $dbh); + return ($db_result)?1:0; +} // set_user_password() + +?> diff --git a/modules/amazon/AmazonSearchEngine.class.php b/modules/amazon/AmazonSearchEngine.class.php new file mode 100644 index 00000000..6a6f7b73 --- /dev/null +++ b/modules/amazon/AmazonSearchEngine.class.php @@ -0,0 +1,189 @@ +<?php
+/*
+
+ Copyright (c) 2001 - 2005 Ampache.org
+ All rights reserved.
+
+ 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+/*!
+ @header AmazonSearch Class
+ @discussion This class takes a token (amazon ID)
+ and then allows you to do a search using the REST
+ method. Currently it is semi-hardcoded to do music
+ searches and only return information abou the album
+ art
+*/
+class AmazonSearch {
+
+ var $base_url = "http://webservices.amazon.com/onca/xml?";
+ var $search;
+ var $token;
+ var $results=array(); // Array of results
+ var $_parser; // The XML parser
+ var $_grabtags; // Tags to grab the contents of
+ var $_sourceTag; // source tag don't ask
+ var $_subTag; // Stupid hack to make things come our right
+ var $_currentTag; // Stupid hack to make things come out right
+ var $_currentTagContents;
+
+ function AmazonSearch($token, $associates_id = 'none') {
+
+ $this->token = $token;
+ $this->associates_id = $associates_id;
+
+ $this->_grabtags = array(
+ 'ASIN', 'ProductName', 'Catalog', 'ErrorMsg',
+ 'Description', 'ReleaseDate', 'Manufacturer', 'ImageUrlSmall',
+ 'ImageUrlMedium', 'ImageUrlLarge', 'Author', 'Artist','Title','URL',
+ 'SmallImage','MediumImage','LargeImage');
+
+ } // AmazonSearch
+
+ /*!
+ @create_parser
+ @discussion this sets up an XML Parser
+ */
+ function create_parser() {
+ $this->_parser = xml_parser_create();
+
+ xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
+
+ xml_set_object($this->_parser, $this);
+
+ xml_set_element_handler($this->_parser, 'start_element', 'end_element');
+
+ xml_set_character_data_handler($this->_parser, 'cdata');
+
+ } // create_parser
+
+ /*!
+ @function search
+ @discussion do a full search on the url they pass
+ */
+ function run_search($url) {
+
+ /* Create the Parser */
+ $this->create_parser();
+
+ $snoopy = new Snoopy;
+ $snoopy->fetch($url);
+ $contents = $snoopy->results;
+
+
+ if (!xml_parse($this->_parser, $contents)) {
+ die(sprintf('XML error: %s at line %d',xml_error_string(xml_get_error_code($this->_parser)),xml_get_current_line_number($this->_parser)));
+ }
+
+ xml_parser_free($this->_parser);
+
+ } // search
+
+ /*!
+ @function search
+ @discussion takes terms and a type
+ */
+ function search($terms, $type='Music') {
+
+ $url = $this->base_url . "Service=AWSECommerceService&SubscriptionId=" . $this->token .
+ "&Operation=ItemSearch&Artist=" . urlencode($terms['artist']) . "&Title=" . urlencode($terms['album']) .
+ "&Keywords=" . urlencode($terms['keywords']) . "&SearchIndex=" . $type;
+
+ $this->run_search($url);
+
+ unset($this->results['ASIN']);
+
+ } // search
+
+ /*!
+ @function lookup
+ @discussion this takes a ASIN and looks up the
+ item in question, possible to pass array
+ of asin's
+ */
+ function lookup($asin, $type='Music') {
+
+ if (is_array($asin)) {
+ foreach ($asin as $key=>$value) {
+ $url = $this->base_url . "Service=AWSECommerceService&SubscriptionId=" . $this->token .
+ "&Operation=ItemLookup&ItemId=" . $key . "&ResponseGroup=Images";
+ $this->run_search($url);
+ }
+ } // if array of asin's
+ else {
+ $url = $this->base_url . "Service=AWSECommerceService&SubscriptionId=" . $this->token .
+ "&Operation=ItemLookup&ItemId=" . $asin . "&ResponseGroup=Images";
+ $this->run_search($url);
+ } // else
+
+ unset($this->results['ASIN']);
+
+ } // lookup
+
+ function start_element($parser, $tag, $attributes) {
+
+ if ($tag == "ASIN") {
+ $this->_sourceTag = $tag;
+ }
+ if ($tag == "SmallImage" || $tag == "MediumImage" || $tag == "LargeImage") {
+ $this->_subTag = $tag;
+ }
+
+ /* If it's in the tag list, don't grab our search results though */
+ if (strlen($this->_sourceTag)) {
+ $this->_currentTag = $tag;
+ }
+ else {
+ $this->_currentTag = '';
+ }
+
+
+ } // start_element
+
+ function cdata($parser, $cdata) {
+
+ $tag = $this->_currentTag;
+ $subtag = $this->_subTag;
+ $source = $this->_sourceTag;
+
+ switch ($tag) {
+ case 'URL':
+ $this->results[$source][$subtag] = trim($cdata);
+ break;
+ case 'ASIN':
+ $this->_sourceTag = trim($cdata);
+ break;
+ default:
+ if (strlen($tag)) {
+ $this->results[$source][$tag] = trim($cdata);
+ }
+ break;
+ } // end switch
+
+
+ } // cdata
+
+ function end_element($parser, $tag) {
+
+ /* Zero the tag */
+ $this->_currentTag = '';
+
+ } // end_element
+
+} // end AmazonSearch
+
+?>
diff --git a/modules/amazon/Snoopy.class.php b/modules/amazon/Snoopy.class.php new file mode 100644 index 00000000..f9e07e02 --- /dev/null +++ b/modules/amazon/Snoopy.class.php @@ -0,0 +1,1234 @@ +<?php + +/************************************************* + +Snoopy - the PHP net client +Author: Monte Ohrt <monte@ispi.net> +Copyright (c): 1999-2000 ispi, all rights reserved +Version: 1.2 + + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +You may contact the author of Snoopy by e-mail at: +monte@ispi.net + +Or, write to: +Monte Ohrt +CTO, ispi +237 S. 70th suite 220 +Lincoln, NE 68510 + +The latest version of Snoopy can be obtained from: +http://snoopy.sourceforge.net/ + +*************************************************/ + +class Snoopy +{ + /**** Public variables ****/ + + /* user definable vars */ + + var $host = "www.php.net"; // host name we are connecting to + var $port = 80; // port we are connecting to + var $proxy_host = ""; // proxy host to use + var $proxy_port = ""; // proxy port to use + var $proxy_user = ""; // proxy user to use + var $proxy_pass = ""; // proxy password to use + + var $agent = "Snoopy v1.2"; // agent we masquerade as + var $referer = ""; // referer info to pass + var $cookies = array(); // array of cookies to pass + // $cookies["username"]="joe"; + var $rawheaders = array(); // array of raw headers to send + // $rawheaders["Content-type"]="text/html"; + + var $maxredirs = 5; // http redirection depth maximum. 0 = disallow + var $lastredirectaddr = ""; // contains address of last redirected address + var $offsiteok = true; // allows redirection off-site + var $maxframes = 0; // frame content depth maximum. 0 = disallow + var $expandlinks = true; // expand links to fully qualified URLs. + // this only applies to fetchlinks() + // or submitlinks() + var $passcookies = true; // pass set cookies back through redirects + // NOTE: this currently does not respect + // dates, domains or paths. + + var $user = ""; // user for http authentication + var $pass = ""; // password for http authentication + + // http accept types + var $accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*"; + + var $results = ""; // where the content is put + + var $error = ""; // error messages sent here + var $response_code = ""; // response code returned from server + var $headers = array(); // headers returned from server sent here + var $maxlength = 500000; // max return data length (body) + var $read_timeout = 0; // timeout on read operations, in seconds + // supported only since PHP 4 Beta 4 + // set to 0 to disallow timeouts + var $timed_out = false; // if a read operation timed out + var $status = 0; // http request status + + var $temp_dir = "/tmp"; // temporary directory that the webserver + // has permission to write to. + // under Windows, this should be C:\temp + + var $curl_path = "/usr/local/bin/curl"; + // Snoopy will use cURL for fetching + // SSL content if a full system path to + // the cURL binary is supplied here. + // set to false if you do not have + // cURL installed. See http://curl.haxx.se + // for details on installing cURL. + // Snoopy does *not* use the cURL + // library functions built into php, + // as these functions are not stable + // as of this Snoopy release. + + /**** Private variables ****/ + + var $_maxlinelen = 4096; // max line length (headers) + + var $_httpmethod = "GET"; // default http request method + var $_httpversion = "HTTP/1.0"; // default http request version + var $_submit_method = "POST"; // default submit method + var $_submit_type = "application/x-www-form-urlencoded"; // default submit type + var $_mime_boundary = ""; // MIME boundary for multipart/form-data submit type + var $_redirectaddr = false; // will be set if page fetched is a redirect + var $_redirectdepth = 0; // increments on an http redirect + var $_frameurls = array(); // frame src urls + var $_framedepth = 0; // increments on frame depth + + var $_isproxy = false; // set if using a proxy server + var $_fp_timeout = 30; // timeout for socket connection + +/*======================================================================*\ + Function: fetch + Purpose: fetch the contents of a web page + (and possibly other protocols in the + future like ftp, nntp, gopher, etc.) + Input: $URI the location of the page to fetch + Output: $this->results the output text from the fetch +\*======================================================================*/ + + function fetch($URI) + { + + //preg_match("|^([^:]+)://([^:/]+)(:[\d]+)*(.*)|",$URI,$URI_PARTS); + $URI_PARTS = parse_url($URI); + if (!empty($URI_PARTS["user"])) + $this->user = $URI_PARTS["user"]; + if (!empty($URI_PARTS["pass"])) + $this->pass = $URI_PARTS["pass"]; + if (empty($URI_PARTS["query"])) + $URI_PARTS["query"] = ''; + + switch($URI_PARTS["scheme"]) + { + case "http": + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_connect($fp)) + { + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httprequest($URI,$fp,$URI,$this->_httpmethod); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httprequest($path, $fp, $URI, $this->_httpmethod); + } + + $this->_disconnect($fp); + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + $this->fetch($this->_redirectaddr); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + } + else + { + return false; + } + return true; + break; + case "https": + if(!$this->curl_path) + return false; + if(function_exists("is_executable")) + if (!is_executable($this->curl_path)) + return false; + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httpsrequest($URI,$URI,$this->_httpmethod); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httpsrequest($path, $URI, $this->_httpmethod); + } + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + $this->fetch($this->_redirectaddr); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + return true; + break; + default: + // not a valid protocol + $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n'; + return false; + break; + } + return true; + } + +/*======================================================================*\ + Function: submit + Purpose: submit an http form + Input: $URI the location to post the data + $formvars the formvars to use. + format: $formvars["var"] = "val"; + $formfiles an array of files to submit + format: $formfiles["var"] = "/dir/filename.ext"; + Output: $this->results the text output from the post +\*======================================================================*/ + + function submit($URI, $formvars="", $formfiles="") + { + unset($postdata); + + $postdata = $this->_prepare_post_body($formvars, $formfiles); + + $URI_PARTS = parse_url($URI); + if (!empty($URI_PARTS["user"])) + $this->user = $URI_PARTS["user"]; + if (!empty($URI_PARTS["pass"])) + $this->pass = $URI_PARTS["pass"]; + if (empty($URI_PARTS["query"])) + $URI_PARTS["query"] = ''; + + switch($URI_PARTS["scheme"]) + { + case "http": + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_connect($fp)) + { + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httprequest($URI,$fp,$URI,$this->_submit_method,$this->_submit_type,$postdata); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httprequest($path, $fp, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } + + $this->_disconnect($fp); + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + if(!preg_match("|^".$URI_PARTS["scheme"]."://|", $this->_redirectaddr)) + $this->_redirectaddr = $this->_expandlinks($this->_redirectaddr,$URI_PARTS["scheme"]."://".$URI_PARTS["host"]); + + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + if( strpos( $this->_redirectaddr, "?" ) > 0 ) + $this->fetch($this->_redirectaddr); // the redirect has changed the request method from post to get + else + $this->submit($this->_redirectaddr,$formvars, $formfiles); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + + } + else + { + return false; + } + return true; + break; + case "https": + if(!$this->curl_path) + return false; + if(function_exists("is_executable")) + if (!is_executable($this->curl_path)) + return false; + $this->host = $URI_PARTS["host"]; + if(!empty($URI_PARTS["port"])) + $this->port = $URI_PARTS["port"]; + if($this->_isproxy) + { + // using proxy, send entire URI + $this->_httpsrequest($URI, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } + else + { + $path = $URI_PARTS["path"].($URI_PARTS["query"] ? "?".$URI_PARTS["query"] : ""); + // no proxy, send only the path + $this->_httpsrequest($path, $URI, $this->_submit_method, $this->_submit_type, $postdata); + } + + if($this->_redirectaddr) + { + /* url was redirected, check if we've hit the max depth */ + if($this->maxredirs > $this->_redirectdepth) + { + if(!preg_match("|^".$URI_PARTS["scheme"]."://|", $this->_redirectaddr)) + $this->_redirectaddr = $this->_expandlinks($this->_redirectaddr,$URI_PARTS["scheme"]."://".$URI_PARTS["host"]); + + // only follow redirect if it's on this site, or offsiteok is true + if(preg_match("|^http://".preg_quote($this->host)."|i",$this->_redirectaddr) || $this->offsiteok) + { + /* follow the redirect */ + $this->_redirectdepth++; + $this->lastredirectaddr=$this->_redirectaddr; + if( strpos( $this->_redirectaddr, "?" ) > 0 ) + $this->fetch($this->_redirectaddr); // the redirect has changed the request method from post to get + else + $this->submit($this->_redirectaddr,$formvars, $formfiles); + } + } + } + + if($this->_framedepth < $this->maxframes && count($this->_frameurls) > 0) + { + $frameurls = $this->_frameurls; + $this->_frameurls = array(); + + while(list(,$frameurl) = each($frameurls)) + { + if($this->_framedepth < $this->maxframes) + { + $this->fetch($frameurl); + $this->_framedepth++; + } + else + break; + } + } + return true; + break; + + default: + // not a valid protocol + $this->error = 'Invalid protocol "'.$URI_PARTS["scheme"].'"\n'; + return false; + break; + } + return true; + } + +/*======================================================================*\ + Function: fetchlinks + Purpose: fetch the links from a web page + Input: $URI where you are fetching from + Output: $this->results an array of the URLs +\*======================================================================*/ + + function fetchlinks($URI) + { + if ($this->fetch($URI)) + { + + if(is_array($this->results)) + { + for($x=0;$x<count($this->results);$x++) + $this->results[$x] = $this->_striplinks($this->results[$x]); + } + else + $this->results = $this->_striplinks($this->results); + + if($this->expandlinks) + $this->results = $this->_expandlinks($this->results, $URI); + return true; + } + else + return false; + } + +/*======================================================================*\ + Function: fetchform + Purpose: fetch the form elements from a web page + Input: $URI where you are fetching from + Output: $this->results the resulting html form +\*======================================================================*/ + + function fetchform($URI) + { + + if ($this->fetch($URI)) + { + + if(is_array($this->results)) + { + for($x=0;$x<count($this->results);$x++) + $this->results[$x] = $this->_stripform($this->results[$x]); + } + else + $this->results = $this->_stripform($this->results); + + return true; + } + else + return false; + } + + +/*======================================================================*\ + Function: fetchtext + Purpose: fetch the text from a web page, stripping the links + Input: $URI where you are fetching from + Output: $this->results the text from the web page +\*======================================================================*/ + + function fetchtext($URI) + { + if($this->fetch($URI)) + { + if(is_array($this->results)) + { + for($x=0;$x<count($this->results);$x++) + $this->results[$x] = $this->_striptext($this->results[$x]); + } + else + $this->results = $this->_striptext($this->results); + return true; + } + else + return false; + } + +/*======================================================================*\ + Function: submitlinks + Purpose: grab links from a form submission + Input: $URI where you are submitting from + Output: $this->results an array of the links from the post +\*======================================================================*/ + + function submitlinks($URI, $formvars="", $formfiles="") + { + if($this->submit($URI,$formvars, $formfiles)) + { + if(is_array($this->results)) + { + for($x=0;$x<count($this->results);$x++) + { + $this->results[$x] = $this->_striplinks($this->results[$x]); + if($this->expandlinks) + $this->results[$x] = $this->_expandlinks($this->results[$x],$URI); + } + } + else + { + $this->results = $this->_striplinks($this->results); + if($this->expandlinks) + $this->results = $this->_expandlinks($this->results,$URI); + } + return true; + } + else + return false; + } + +/*======================================================================*\ + Function: submittext + Purpose: grab text from a form submission + Input: $URI where you are submitting from + Output: $this->results the text from the web page +\*======================================================================*/ + + function submittext($URI, $formvars = "", $formfiles = "") + { + if($this->submit($URI,$formvars, $formfiles)) + { + if(is_array($this->results)) + { + for($x=0;$x<count($this->results);$x++) + { + $this->results[$x] = $this->_striptext($this->results[$x]); + if($this->expandlinks) + $this->results[$x] = $this->_expandlinks($this->results[$x],$URI); + } + } + else + { + $this->results = $this->_striptext($this->results); + if($this->expandlinks) + $this->results = $this->_expandlinks($this->results,$URI); + } + return true; + } + else + return false; + } + + + +/*======================================================================*\ + Function: set_submit_multipart + Purpose: Set the form submission content type to + multipart/form-data +\*======================================================================*/ + function set_submit_multipart() + { + $this->_submit_type = "multipart/form-data"; + } + + +/*======================================================================*\ + Function: set_submit_normal + Purpose: Set the form submission content type to + application/x-www-form-urlencoded +\*======================================================================*/ + function set_submit_normal() + { + $this->_submit_type = "application/x-www-form-urlencoded"; + } + + + + +/*======================================================================*\ + Private functions +\*======================================================================*/ + + +/*======================================================================*\ + Function: _striplinks + Purpose: strip the hyperlinks from an html document + Input: $document document to strip. + Output: $match an array of the links +\*======================================================================*/ + + function _striplinks($document) + { + preg_match_all("'<\s*a\s.*?href\s*=\s* # find <a href= + ([\"\'])? # find single or double quote + (?(1) (.*?)\\1 | ([^\s\>]+)) # if quote found, match up to next matching + # quote, otherwise match up to next space + 'isx",$document,$links); + + + // catenate the non-empty matches from the conditional subpattern + + while(list($key,$val) = each($links[2])) + { + if(!empty($val)) + $match[] = $val; + } + + while(list($key,$val) = each($links[3])) + { + if(!empty($val)) + $match[] = $val; + } + + // return the links + return $match; + } + +/*======================================================================*\ + Function: _stripform + Purpose: strip the form elements from an html document + Input: $document document to strip. + Output: $match an array of the links +\*======================================================================*/ + + function _stripform($document) + { + preg_match_all("'<\/?(FORM|INPUT|SELECT|TEXTAREA|(OPTION))[^<>]*>(?(2)(.*(?=<\/?(option|select)[^<>]*>[\r\n]*)|(?=[\r\n]*))|(?=[\r\n]*))'Usi",$document,$elements); + + // catenate the matches + $match = implode("\r\n",$elements[0]); + + // return the links + return $match; + } + + + +/*======================================================================*\ + Function: _striptext + Purpose: strip the text from an html document + Input: $document document to strip. + Output: $text the resulting text +\*======================================================================*/ + + function _striptext($document) + { + + // I didn't use preg eval (//e) since that is only available in PHP 4.0. + // so, list your entities one by one here. I included some of the + // more common ones. + + $search = array("'<script[^>]*?>.*?</script>'si", // strip out javascript + "'<[\/\!]*?[^<>]*?>'si", // strip out html tags + "'([\r\n])[\s]+'", // strip out white space + "'&(quot|#34|#034|#x22);'i", // replace html entities + "'&(amp|#38|#038|#x26);'i", // added hexadecimal values + "'&(lt|#60|#060|#x3c);'i", + "'&(gt|#62|#062|#x3e);'i", + "'&(nbsp|#160|#xa0);'i", + "'&(iexcl|#161);'i", + "'&(cent|#162);'i", + "'&(pound|#163);'i", + "'&(copy|#169);'i", + "'&(reg|#174);'i", + "'&(deg|#176);'i", + "'&(#39|#039|#x27);'", + "'&(euro|#8364);'i", // europe + "'&a(uml|UML);'", // german + "'&o(uml|UML);'", + "'&u(uml|UML);'", + "'&A(uml|UML);'", + "'&O(uml|UML);'", + "'&U(uml|UML);'", + "'ß'i", + ); + $replace = array( "", + "", + "\\1", + "\"", + "&", + "<", + ">", + " ", + chr(161), + chr(162), + chr(163), + chr(169), + chr(174), + chr(176), + chr(39), + chr(128), + "ä", + "ö", + "ü", + "Ä", + "Ö", + "Ü", + "ß", + ); + + $text = preg_replace($search,$replace,$document); + + return $text; + } + +/*======================================================================*\ + Function: _expandlinks + Purpose: expand each link into a fully qualified URL + Input: $links the links to qualify + $URI the full URI to get the base from + Output: $expandedLinks the expanded links +\*======================================================================*/ + + function _expandlinks($links,$URI) + { + + preg_match("/^[^\?]+/",$URI,$match); + + $match = preg_replace("|/[^\/\.]+\.[^\/\.]+$|","",$match[0]); + $match = preg_replace("|/$|","",$match); + + $search = array( "|^http://".preg_quote($this->host)."|i", + "|^(?!http://)(\/)?(?!mailto:)|i", + "|/\./|", + "|/[^\/]+/\.\./|" + ); + + $replace = array( "", + $match."/", + "/", + "/" + ); + + $expandedLinks = preg_replace($search,$replace,$links); + + return $expandedLinks; + } + +/*======================================================================*\ + Function: _httprequest + Purpose: go get the http data from the server + Input: $url the url to fetch + $fp the current open file pointer + $URI the full URI + $body body contents to send if any (POST) + Output: +\*======================================================================*/ + + function _httprequest($url,$fp,$URI,$http_method,$content_type="",$body="") + { + $cookie_headers = ''; + if($this->passcookies && $this->_redirectaddr) + $this->setcookies(); + + $URI_PARTS = parse_url($URI); + if(empty($url)) + $url = "/"; + $headers = $http_method." ".$url." ".$this->_httpversion."\r\n"; + if(!empty($this->agent)) + $headers .= "User-Agent: ".$this->agent."\r\n"; + if(!empty($this->host) && !isset($this->rawheaders['Host'])) + $headers .= "Host: ".$this->host."\r\n"; + if(!empty($this->accept)) + $headers .= "Accept: ".$this->accept."\r\n"; + if(!empty($this->referer)) + $headers .= "Referer: ".$this->referer."\r\n"; + if(!empty($this->cookies)) + { + if(!is_array($this->cookies)) + $this->cookies = (array)$this->cookies; + + reset($this->cookies); + if ( count($this->cookies) > 0 ) { + $cookie_headers .= 'Cookie: '; + foreach ( $this->cookies as $cookieKey => $cookieVal ) { + $cookie_headers .= $cookieKey."=".urlencode($cookieVal)."; "; + } + $headers .= substr($cookie_headers,0,-2) . "\r\n"; + } + } + if(!empty($this->rawheaders)) + { + if(!is_array($this->rawheaders)) + $this->rawheaders = (array)$this->rawheaders; + while(list($headerKey,$headerVal) = each($this->rawheaders)) + $headers .= $headerKey.": ".$headerVal."\r\n"; + } + if(!empty($content_type)) { + $headers .= "Content-type: $content_type"; + if ($content_type == "multipart/form-data") + $headers .= "; boundary=".$this->_mime_boundary; + $headers .= "\r\n"; + } + if(!empty($body)) + $headers .= "Content-length: ".strlen($body)."\r\n"; + if(!empty($this->user) || !empty($this->pass)) + $headers .= "Authorization: Basic ".base64_encode($this->user.":".$this->pass)."\r\n"; + + //add proxy auth headers + if(!empty($this->proxy_user)) + $headers .= 'Proxy-Authorization: ' . 'Basic ' . base64_encode($this->proxy_user . ':' . $this->proxy_pass)."\r\n"; + + + $headers .= "\r\n"; + + // set the read timeout if needed + if ($this->read_timeout > 0) + socket_set_timeout($fp, $this->read_timeout); + $this->timed_out = false; + + fwrite($fp,$headers.$body,strlen($headers.$body)); + + $this->_redirectaddr = false; + unset($this->headers); + + while($currentHeader = fgets($fp,$this->_maxlinelen)) + { + if ($this->read_timeout > 0 && $this->_check_timeout($fp)) + { + $this->status=-100; + return false; + } + + if($currentHeader == "\r\n") + break; + + // if a header begins with Location: or URI:, set the redirect + if(preg_match("/^(Location:|URI:)/i",$currentHeader)) + { + // get URL portion of the redirect + preg_match("/^(Location:|URI:)[ ]+(.*)/",chop($currentHeader),$matches); + // look for :// in the Location header to see if hostname is included + if(!preg_match("|\:\/\/|",$matches[2])) + { + // no host in the path, so prepend + $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port; + // eliminate double slash + if(!preg_match("|^/|",$matches[2])) + $this->_redirectaddr .= "/".$matches[2]; + else + $this->_redirectaddr .= $matches[2]; + } + else + $this->_redirectaddr = $matches[2]; + } + + if(preg_match("|^HTTP/|",$currentHeader)) + { + if(preg_match("|^HTTP/[^\s]*\s(.*?)\s|",$currentHeader, $status)) + { + $this->status= $status[1]; + } + $this->response_code = $currentHeader; + } + + $this->headers[] = $currentHeader; + } + + $results = ''; + do { + $_data = fread($fp, $this->maxlength); + if (strlen($_data) == 0) { + break; + } + $results .= $_data; + } while(true); + + if ($this->read_timeout > 0 && $this->_check_timeout($fp)) + { + $this->status=-100; + return false; + } + + // check if there is a a redirect meta tag + + if(preg_match("'<meta[\s]*http-equiv[^>]*?content[\s]*=[\s]*[\"\']?\d+;[\s]*URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match)) + + { + $this->_redirectaddr = $this->_expandlinks($match[1],$URI); + } + + // have we hit our frame depth and is there frame src to fetch? + if(($this->_framedepth < $this->maxframes) && preg_match_all("'<frame\s+.*src[\s]*=[\'\"]?([^\'\"\>]+)'i",$results,$match)) + { + $this->results[] = $results; + for($x=0; $x<count($match[1]); $x++) + $this->_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host); + } + // have we already fetched framed content? + elseif(is_array($this->results)) + $this->results[] = $results; + // no framed content + else + $this->results = $results; + + return true; + } + +/*======================================================================*\ + Function: _httpsrequest + Purpose: go get the https data from the server using curl + Input: $url the url to fetch + $URI the full URI + $body body contents to send if any (POST) + Output: +\*======================================================================*/ + + function _httpsrequest($url,$URI,$http_method,$content_type="",$body="") + { + if($this->passcookies && $this->_redirectaddr) + $this->setcookies(); + + $headers = array(); + + $URI_PARTS = parse_url($URI); + if(empty($url)) + $url = "/"; + // GET ... header not needed for curl + //$headers[] = $http_method." ".$url." ".$this->_httpversion; + if(!empty($this->agent)) + $headers[] = "User-Agent: ".$this->agent; + if(!empty($this->host)) + $headers[] = "Host: ".$this->host; + if(!empty($this->accept)) + $headers[] = "Accept: ".$this->accept; + if(!empty($this->referer)) + $headers[] = "Referer: ".$this->referer; + if(!empty($this->cookies)) + { + if(!is_array($this->cookies)) + $this->cookies = (array)$this->cookies; + + reset($this->cookies); + if ( count($this->cookies) > 0 ) { + $cookie_str = 'Cookie: '; + foreach ( $this->cookies as $cookieKey => $cookieVal ) { + $cookie_str .= $cookieKey."=".urlencode($cookieVal)."; "; + } + $headers[] = substr($cookie_str,0,-2); + } + } + if(!empty($this->rawheaders)) + { + if(!is_array($this->rawheaders)) + $this->rawheaders = (array)$this->rawheaders; + while(list($headerKey,$headerVal) = each($this->rawheaders)) + $headers[] = $headerKey.": ".$headerVal; + } + if(!empty($content_type)) { + if ($content_type == "multipart/form-data") + $headers[] = "Content-type: $content_type; boundary=".$this->_mime_boundary; + else + $headers[] = "Content-type: $content_type"; + } + if(!empty($body)) + $headers[] = "Content-length: ".strlen($body); + if(!empty($this->user) || !empty($this->pass)) + $headers[] = "Authorization: BASIC ".base64_encode($this->user.":".$this->pass); + + for($curr_header = 0; $curr_header < count($headers); $curr_header++) + $cmdline_params .= " -H \"".$headers[$curr_header]."\""; + + if(!empty($body)) + $cmdline_params .= " -d \"$body\""; + + if($this->read_timeout > 0) + $cmdline_params .= " -m ".$this->read_timeout; + + $headerfile = tempnam($temp_dir, "sno"); + + $safer_URI = strtr( $URI, "\"", " " ); // strip quotes from the URI to avoid shell access + exec($this->curl_path." -D \"$headerfile\"".$cmdline_params." \"".$safer_URI."\"",$results,$return); + + if($return) + { + $this->error = "Error: cURL could not retrieve the document, error $return."; + return false; + } + + + $results = implode("\r\n",$results); + + $result_headers = file("$headerfile"); + + $this->_redirectaddr = false; + unset($this->headers); + + for($currentHeader = 0; $currentHeader < count($result_headers); $currentHeader++) + { + + // if a header begins with Location: or URI:, set the redirect + if(preg_match("/^(Location: |URI: )/i",$result_headers[$currentHeader])) + { + // get URL portion of the redirect + preg_match("/^(Location: |URI:)\s+(.*)/",chop($result_headers[$currentHeader]),$matches); + // look for :// in the Location header to see if hostname is included + if(!preg_match("|\:\/\/|",$matches[2])) + { + // no host in the path, so prepend + $this->_redirectaddr = $URI_PARTS["scheme"]."://".$this->host.":".$this->port; + // eliminate double slash + if(!preg_match("|^/|",$matches[2])) + $this->_redirectaddr .= "/".$matches[2]; + else + $this->_redirectaddr .= $matches[2]; + } + else + $this->_redirectaddr = $matches[2]; + } + + if(preg_match("|^HTTP/|",$result_headers[$currentHeader])) + $this->response_code = $result_headers[$currentHeader]; + + $this->headers[] = $result_headers[$currentHeader]; + } + + // check if there is a a redirect meta tag + + if(preg_match("'<meta[\s]*http-equiv[^>]*?content[\s]*=[\s]*[\"\']?\d+;[\s]+URL[\s]*=[\s]*([^\"\']*?)[\"\']?>'i",$results,$match)) + { + $this->_redirectaddr = $this->_expandlinks($match[1],$URI); + } + + // have we hit our frame depth and is there frame src to fetch? + if(($this->_framedepth < $this->maxframes) && preg_match_all("'<frame\s+.*src[\s]*=[\'\"]?([^\'\"\>]+)'i",$results,$match)) + { + $this->results[] = $results; + for($x=0; $x<count($match[1]); $x++) + $this->_frameurls[] = $this->_expandlinks($match[1][$x],$URI_PARTS["scheme"]."://".$this->host); + } + // have we already fetched framed content? + elseif(is_array($this->results)) + $this->results[] = $results; + // no framed content + else + $this->results = $results; + + unlink("$headerfile"); + + return true; + } + +/*======================================================================*\ + Function: setcookies() + Purpose: set cookies for a redirection +\*======================================================================*/ + + function setcookies() + { + for($x=0; $x<count($this->headers); $x++) + { + if(preg_match('/^set-cookie:[\s]+([^=]+)=([^;]+)/i', $this->headers[$x],$match)) + $this->cookies[$match[1]] = urldecode($match[2]); + } + } + + +/*======================================================================*\ + Function: _check_timeout + Purpose: checks whether timeout has occurred + Input: $fp file pointer +\*======================================================================*/ + + function _check_timeout($fp) + { + if ($this->read_timeout > 0) { + $fp_status = socket_get_status($fp); + if ($fp_status["timed_out"]) { + $this->timed_out = true; + return true; + } + } + return false; + } + +/*======================================================================*\ + Function: _connect + Purpose: make a socket connection + Input: $fp file pointer +\*======================================================================*/ + + function _connect(&$fp) + { + if(!empty($this->proxy_host) && !empty($this->proxy_port)) + { + $this->_isproxy = true; + + $host = $this->proxy_host; + $port = $this->proxy_port; + } + else + { + $host = $this->host; + $port = $this->port; + } + + $this->status = 0; + + if($fp = fsockopen( + $host, + $port, + $errno, + $errstr, + $this->_fp_timeout + )) + { + // socket connection succeeded + + return true; + } + else + { + // socket connection failed + $this->status = $errno; + switch($errno) + { + case -3: + $this->error="socket creation failed (-3)"; + case -4: + $this->error="dns lookup failure (-4)"; + case -5: + $this->error="connection refused or timed out (-5)"; + default: + $this->error="connection failed (".$errno.")"; + } + return false; + } + } +/*======================================================================*\ + Function: _disconnect + Purpose: disconnect a socket connection + Input: $fp file pointer +\*======================================================================*/ + + function _disconnect($fp) + { + return(fclose($fp)); + } + + +/*======================================================================*\ + Function: _prepare_post_body + Purpose: Prepare post body according to encoding type + Input: $formvars - form variables + $formfiles - form upload files + Output: post body +\*======================================================================*/ + + function _prepare_post_body($formvars, $formfiles) + { + settype($formvars, "array"); + settype($formfiles, "array"); + $postdata = ''; + + if (count($formvars) == 0 && count($formfiles) == 0) + return; + + switch ($this->_submit_type) { + case "application/x-www-form-urlencoded": + reset($formvars); + while(list($key,$val) = each($formvars)) { + if (is_array($val) || is_object($val)) { + while (list($cur_key, $cur_val) = each($val)) { + $postdata .= urlencode($key)."[]=".urlencode($cur_val)."&"; + } + } else + $postdata .= urlencode($key)."=".urlencode($val)."&"; + } + break; + + case "multipart/form-data": + $this->_mime_boundary = "Snoopy".md5(uniqid(microtime())); + + reset($formvars); + while(list($key,$val) = each($formvars)) { + if (is_array($val) || is_object($val)) { + while (list($cur_key, $cur_val) = each($val)) { + $postdata .= "--".$this->_mime_boundary."\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$key\[\]\"\r\n\r\n"; + $postdata .= "$cur_val\r\n"; + } + } else { + $postdata .= "--".$this->_mime_boundary."\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$key\"\r\n\r\n"; + $postdata .= "$val\r\n"; + } + } + + reset($formfiles); + while (list($field_name, $file_names) = each($formfiles)) { + settype($file_names, "array"); + while (list(, $file_name) = each($file_names)) { + if (!is_readable($file_name)) continue; + + $fp = fopen($file_name, "r"); + $file_content = fread($fp, filesize($file_name)); + fclose($fp); + $base_name = basename($file_name); + + $postdata .= "--".$this->_mime_boundary."\r\n"; + $postdata .= "Content-Disposition: form-data; name=\"$field_name\"; filename=\"$base_name\"\r\n\r\n"; + $postdata .= "$file_content\r\n"; + } + } + $postdata .= "--".$this->_mime_boundary."--\r\n"; + break; + } + + return $postdata; + } +} + +?> diff --git a/modules/catalog.php b/modules/catalog.php new file mode 100644 index 00000000..e9b8a1d7 --- /dev/null +++ b/modules/catalog.php @@ -0,0 +1,223 @@ +<?php +/* + + Contains all of the catalog (local & remote) functions. + + DEAD FILE (Old Crap) + +*/ + +/* + * get_catalogs() + * + * return an array of catalog objects + * + */ + +function get_catalogs () { + global $dbh, $settings; + + $sql = "SELECT * FROM catalog"; + $db_result = mysql_query($sql, $dbh); + + $catalogs = array(); + while ( $catalog = mysql_fetch_object($db_result) ) { + $catalogs[] = $catalog; + } + + return ($catalogs); +} // get_catalogs() + + +/* + * update_artist_info() + * + * this will update the song and album counters for the artist + */ + +function update_artist_info($artist_id) { + GLOBAL $dbh, $settings; + + // get the count of songs + $query = "SELECT count(id) FROM song WHERE artist='$artist_id'"; + $db_result = mysql_query($query, $dbh); + + $r = mysql_fetch_row($db_result); + $artist->songs = $r[0]; + + // get the count of albums + $query = "SELECT count(DISTINCT album) FROM song WHERE artist='$artist_id'"; + $db_result = mysql_query($query, $dbh); + + $r = mysql_fetch_row($db_result); + $artist->albums = $r[0]; + + // now update the artist table + $query = "UPDATE artist SET songs='$artist->songs',albums='$artist->albums' WHERE id='$artist_id'"; + $db_result = mysql_query($query, $dbh); +} // update_artist_info() + + +/* + * select_artist() + * + * given an artist name (string) it will return: + * false: if the artist name doesn't exist + * true : if the artist name does exist + * in the database + * + */ + +function select_artist($artist) { + GLOBAL $dbh, $settings; + + $artist = sql_escape($artist); + + $sql = "SELECT id FROM artist WHERE name = '$artist'"; + $db_result = mysql_query( $sql, $dbh ); + $r = mysql_fetch_row( $db_result ); + + if ( $r[0] ) { + return ($r[0]); + } + else { + return 0; + } +} // select_artist() + + +/* + * insert_artist() + * + * given an artist name (string) it will insert an entry + * into the database, defaulting the catalog to 0 + * + */ + +/* +function insert_artist($artist, $catalog = 0) { + GLOBAL $dbh, $settings; + + $artist = sql_escape($artist); + + $sql = "INSERT INTO artist (name,catalog) VALUES ('$artist', $catalog)"; + $db_result = mysql_query($sql, $dbh); + + return (mysql_insert_id($dbh)); +} // insert_artist() +*/ + +/* + * update_artist_name() + * + * let's change the album name + * + */ + +function update_artist_name ($artist, $new_name) { + global $dbh, $settings; + + $query = "UPDATE artist SET name='$new_name' WHERE id='$artist'"; + $db_result = mysql_query($query, $dbh); +} // update_artist_name() + + +/* + * delete_artist() + * + * given an artist id (int) this will delete the associated + * entry from the database + * + */ + +function delete_artist($artist) { + GLOBAL $dbh, $settings; + + $sql = "DELETE FROM artist WHERE id = $artist"; + $db_result = mysql_query($sql, $dbh); +} // delete_artist() + + +/* + * select_album() + * + * given an album name and artist id, this will return: + * false: if the album name and artist id don't match + * id : of the album name and artist id match + */ + +function select_album($album, $artist) { + GLOBAL $dbh, $settings; + + $album = sql_escape($album); + + $sql = "SELECT id FROM album + WHERE name = '$album' AND artist = $artist"; + $db_result = mysql_query($sql, $dbh); + + $r = mysql_fetch_row($db_result); + + if ( $r[0] ) { + return ($r[0]); + } + else { + return 0; + } +} // select_album() + + +/* + * update_album_name() + * + * let's change the album name + * + */ + +function update_album_name ($album, $new_name) { + global $dbh, $settings; + + $sql = "UPDATE album SET name='$new_name' WHERE id='$album'"; + $db_result = mysql_query($sql, $dbh); +} // update_album_name() + + +/* + * update_local_mp3($name, $type, $songs) + * + * This will update all of the $songs with a new name of type $type. Used + * mostly for updating artist/album names for your _local_ mp3s. This + * will write out new ID3 tags. + */ + +function update_local_mp3($name, $type, $songs) { + // THIS IS DEAD!!! + //FIXME: I'm dead Jim! +} // update_local_mp3 + + + +/* + * get_check_array() + * + * returns a single dimension array of the md5 hashes + * for all songs in local catalogs + * + */ + +function get_check_array ( ) { + global $settings, $dbh; + + $check_array = array(); + + $sql = "SELECT md5 FROM song"; + $db_result = mysql_query($sql, $dbh ); + + while ( $md5 = mysql_fetch_object( $db_result ) ) + { + $check_array[] = $md5->md5; + } + + return $check_array; +} // get_check_array() + +?> diff --git a/modules/class/access.php b/modules/class/access.php new file mode 100644 index 00000000..6e6afbf2 --- /dev/null +++ b/modules/class/access.php @@ -0,0 +1,189 @@ +<? +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Access Class +*/ + +class Access { + + /* Variables from DB */ + var $id; + var $name; + var $start; + var $end; + var $level; + + /*! + @function Access + @discussion Access class, for modifing access rights + @param $access_id The ID of access entry + */ + function Access($access_id = 0) { + + /* If we have passed an id then do something */ + if ($access_id) { + + /* Assign id for use in get_info() */ + $this->id = $access_id; + + /* Get the information from the db */ + if ($info = $this->get_info()) { + + /* Assign Vars */ + $this->name = $info->name; + $this->start = $info->start; + $this->end = $info->end; + $this->level = $info->level; + } // if info + + } // if access_id + + } //constructor + + /*! + @function get_info + @discussion get's the vars for $this out of the database + @param $this->id Taken from the object + */ + function get_info() { + + /* Grab the basic information from the catalog and return it */ + $sql = "SELECT * FROM access_list WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_object($db_results); + + return $results; + + } //get_info + + /*! + @function create + @discussion creates a new entry + */ + function create($name,$start,$end,$level) { + + $start = ip2int($start); + $end = ip2int($end); + $name = sql_escape($name); + $level = intval($level); + + $sql = "INSERT INTO access_list (`name`,`level`,`start`,`end`) VALUES ". + "('$name','$level','$start','$end')"; + $db_results = mysql_query($sql, dbh()); + + } // create + + /*! + @function delete + @discussion deletes $this access_list entry + */ + function delete($access_id=0) { + + if (!$access_id) { + $access_id = $this->id; + } + + $sql = "DELETE FROM access_list WHERE id='$access_id'"; + $db_results = mysql_query($sql, dbh()); + + } // delete + + /*! + @function check + @discussion check to see if they have rights + */ + function check($needed, $ip) { + + // They aren't using access control + // lets just keep on trucking + if (!conf('access_control')) { + return true; + } + + $ip = ip2int($ip); + + $sql = "SELECT id FROM access_list WHERE start<='$ip' AND end>='$ip' AND level>='$needed'"; + $db_results = mysql_query($sql, dbh()); + + // Yah they have access they can use the mojo + if (mysql_fetch_row($db_results)) { + return true; + } + + // No Access Sucks to be them. + else { + return false; + } + + } // check + + /*! + @function get_access_list + @discussion returns a full listing of all access + rules on this server + */ + function get_access_list() { + + $sql = "SELECT * FROM access_list"; + $db_results = mysql_query($sql, dbh()); + + + while ($r = mysql_fetch_object($db_results)) { + $obj = new Access(); + $obj->id = $r->id; + $obj->start = $r->start; + $obj->end = $r->end; + $obj->name = $r->name; + $obj->level = $r->level; + $results[] = $obj; + } // end while access list mojo + + return $results; + + } // get_access_list + + + /*! + @function get_level_name + @discussion take the int level and return a + named level + */ + function get_level_name() { + + if ($this->level == '75') { + return "Full Access"; + } + if ($this->level == '5') { + return "Demo"; + } + if ($this->level == '25') { + return "Stream"; + } + if ($this->level == '50') { + return "Stream/Download"; + } + + + } // get_level_name + +} //end of access class + +?> diff --git a/modules/class/album.php b/modules/class/album.php new file mode 100644 index 00000000..c95a8ff6 --- /dev/null +++ b/modules/class/album.php @@ -0,0 +1,463 @@ +<?php +/* + Copyright (c) 2004 + Ampache.org + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Album Class +*/ + +class Album { + + /* Variables from DB */ + var $id; + var $name; + var $year; + var $prefix; + + /*! + @function Album + @discussion Album class, for modifing a song. + @param $album_id The ID of the song + */ + function Album($album_id = 0) { + + /* If we have passed an id then do something */ + if ($album_id) { + + /* Assign id for use in get_info() */ + $this->id = $album_id; + + /* Get the information from the db */ + if ($info = $this->get_info()) { + + /* Assign Vars */ + $this->name = trim($info->prefix . " " . $info->album_name); + $this->songs = $info->song_count; + $this->artist_count = $info->artist_count; + $this->year = $info->year; + $this->artist = trim($info->artist_prefix . " " . $info->artist_name); + $this->artist_id = $info->art_id; + $this->album = $info->album_name; + + $this->prefix = $info->prefix; + } // if info + + } // if album_id + + } //constructor + + + /*! + @function get_info + @discussion get's the vars for $this out of the database + @param $this->id Taken from the object + */ + function get_info() { + + /* Grab the basic information from the catalog and return it */ + $sql = "SELECT COUNT(DISTINCT(song.artist)) as artist_count,album.prefix,album.year,album.name AS album_name,COUNT(song.id) AS song_count," . + "artist.name AS artist_name,artist.id AS art_id,artist.prefix AS artist_prefix ". + "FROM song,artist,album WHERE album.id='$this->id' AND song.album=album.id AND song.artist=artist.id GROUP BY song.album"; + + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_object($db_results); + + return $results; + + } //get_info + + /*! + @function get_songs + @discussion gets the songs for this album + */ + function get_songs($limit = 0) { + + $results = array(); + + $sql = "SELECT id FROM song WHERE album='$this->id' ORDER BY track, title"; + if ($limit) { $sql .= " LIMIT $limit"; } + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $results[] = new Song($r->id); + } + + return $results; + + } // get_songs + + + /*! + @function format_album + @dicussion reformats this object with f_name, f_songs and f_artist + that contain links etc... + */ + function format_album() { + + $web_path = conf('web_path'); + + /* Truncate the string if it's to long */ + $name = truncate_with_ellipse($this->name,conf('ellipse_threshold_album')); + + $this->f_name = "<a href=\"$web_path/albums.php?action=show&album=" . $this->id . "\" title=\"" . $this->name . "\">" . $name . "</a>"; + $this->f_songs = "<div align=\"center\">" . $this->songs . "</div>"; + if ($this->artist_count == '1') { + $this->f_artist = "<a href=\"$web_path/artists.php?action=show&artist=" . $this->artist_id . "\">" . $this->artist . "</a>"; + } + else { + $this->f_artist = _("Various"); + } + + if ($this->year == '0') { + $this->year = "N/A"; + } + + } // format_album + + /*! + @function get_art + @discussion get art wrapper function + */ + function get_art($fast = 0) { + + /* Check DB first */ + if ($image = $this->get_db_art()) { + return $image; + } + + /* Stop here if we are doing the fast art */ + if ($fast) { return false; } + + /* Create Base Vars */ + $album_art_order = array(); + + /* Attempt to retrive the album art order */ + $config_value = conf('album_art_order'); + $class_methods = get_class_methods('Album'); + + /* If it's not set */ + if (empty($config_value)) { + $album_art_order = array('id3','folder','amazon'); + } + elseif (!is_array($config_value)) { + $album_art_order = array_push($album_art_order,$config_value); + } + else { + $album_art_order = array_merge($album_art_order, conf('album_art_order')); + } + + foreach ($album_art_order AS $method) { + + $method_name = "get_" . $method . "_art"; + + if (in_array($method_name,$class_methods)) { + if ($this->{$method_name}()) { + return $this->get_db_art(); + } // if method finds the art + } // if the method exists + + } // end foreach + + return false; + + } // get_art + + /*! + @function get_id3_art + @discussion looks for art from the id3 tags + */ + function get_id3_art() { + + $songs = $this->get_songs(); + + // Foreach songs in this album + foreach ($songs as $song) { + // If we find a good one, stop looking + $getID3 = new getID3(); + $id3 = $getID3->analyze($song->file); + + if ($id3['format_name'] == "WMA") { + $image = $id3['asf']['extended_content_description_object']['content_descriptors']['13']; + } + else { + $image = $id3['id3v2']['APIC']['0']; + } + if ($image) { + $art = $image['data']; + $mime = $image['mime']; + + // Stick it in the db for next time + $sql = "UPDATE album SET art = '" . sql_escape($art) . "'," . + " art_mime = '" . sql_escape($mime) . "'" . + " WHERE id = '" . $this->id . "'"; + $db_result = mysql_query($sql, dbh()); + + return true; + } // end if image + } // end foreach + + return false; + + } // get_id3_art + + /*! + @function get_folder_art() + @discussion returns the album art from the folder of the mp3s + */ + function get_folder_art() { + + $songs = $this->get_songs(); + + /* See if we are looking for a specific filename */ + $preferred_filename = conf('album_art_preferred_filename'); + + /* Thanks to dromio for origional code */ + /* Added search for any .jpg, png or .gif - Vollmer */ + foreach($songs as $song) { + $dir = dirname($song->file); + + /* Open up the directory */ + $handle = @opendir($dir); + + if (!is_resource($handle)) { + echo "<font class=\"error\">" . _("Error: Unable to open") . " $dir</font><br />\n"; + if (conf('debug')) { log_event($GLOBALS['user']->username,' read ',"Error: Unable to open $dir for album art read"); } + } + + /* Recurse through this dir and create the files array */ + while ( FALSE !== ($file = @readdir($handle)) ) { + $extension = substr($file,strlen($file)-3,4); + + /* If it's an image file */ + if ($extension == "jpg" || $extension == "gif" || $extension == "png" || $extension == "jp2") { + + if ($file == $preferred_filename) { + $found = 1; + $album_art_filename = array('file' => $file, 'ext' => $extension); + break; + } + elseif (!$preferred_filename) { + $found = 1; + $album_art_filename = array('file' => $file, 'ext' => $extension); + break; + } + else { + $found = 1; + $album_art_filename = array('file' => $file, 'ext' => $extension); + } + + } // end if it's an image + + } // end while reading dir + @closedir($handle); + + if ($found) { + $handle = fopen($dir."/".$album_art_filename['file'], "rb"); + $mime = "image/" . $album_art_filename['ext']; + $art = ''; + while(!feof($handle)) { + $art .= fread($handle, 1024); + } + fclose($handle); + $sql = "UPDATE album SET art = '" . sql_escape($art) . "'," . + " art_mime = '" . sql_escape($mime) . "'" . + " WHERE id = '$this->id'"; + $db_results = mysql_query($sql, dbh()); + return true; + } // if found + } // end foreach songs + + return false; + + } // get_folder_art() + + /*! + @function get_db_art() + @discussion returns the album art from the db + */ + function get_db_art() { + + $sql = "SELECT art,art_mime FROM album WHERE id='$this->id' AND art_mime IS NOT NULL"; + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_object($db_results); + + return $results; + + } // get_db_art + + + /*! + @function get_amazon_art + @discussion searches amazon for the + album art + */ + function get_amazon_art() { + + return $this->find_art(); + + } // get_amazon_art + + /*! + @function get_random_songs + @discussion gets a random number, and + a random assortment of songs from this + album + */ + function get_random_songs() { + + $results = array(); + + $sql = "SELECT id FROM song WHERE album='$this->id' ORDER BY RAND() LIMIT " . rand(1,$this->songs); + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_array($db_results)) { + $results[] = $r[0]; + } + + return $results; + + } // get_random_songs + + /*! + @function clear_art + @discussion clears the album art from the DB + */ + function clear_art() { + + $sql = "UPDATE album SET art=NULL, art_mime=NULL WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + } // clear_art + + /*! + @function find_art + @discussion searches amazon or a url + for the album art + //FIXME: Rename this POS + */ + function find_art($coverurl = '') { + + // No coverurl specified search amazon + if (empty($coverurl)) { + $amazon = new AmazonSearch(conf('amazon_developer_key')); + // Prevent the script from timing out + set_time_limit(0); + $search_term = $this->artist . " " . $this->name; + $amazon->search(array('artist' => $this->artist, 'album' => $this->name, 'keywords' => $serch_term)); + // Only do the second search if the first actually returns something + if (count($amazon->results)) { + $amazon->lookup($amazon->results); + } + + /* Log this if we're doin debug */ + if (conf('debug')) { + log_event($_SESSION['userdata']['username'],' amazon-xml ',"Searched using $search_term with " . conf('amazon_developer_key') . " as key " . count($amazon->results) . " results found"); + } + + //FIXME: For now just pull the first one we find + foreach ($amazon->results as $key=>$value) { + $results = $value; + break; + } //FIXME: + + } // if no cover + // If we've specified a coverurl, create a fake Amazon array with it + else { + $results = array('LargeImage' => $coverurl); + } + + // If we have results of some kind + if (is_array($results)) { + + /* Recurse through the images found */ + $possible_keys = array("LargeImage","MediumImage","SmallImage"); + + foreach ($possible_keys as $key) { + if (strlen($results[$key])) { + break; + } + + + } // foreach + + // Rudimentary image type detection, only JPG and GIF allowed. + if (substr($results[$key], -4 == ".jpg")) { + $mime = "image/jpg"; + } + elseif (substr($results[$key], -4 == ".gif")) { + $mime = "image/gif"; + } + else { + return false; + } + + /* Create Snoopy Object and pull info */ + $snoopy = new Snoopy; + $snoopy->fetch($results[$key]); + $art = $snoopy->results; + + // Skip 1x1 size images + if (function_exists('ImageCreateFromString')) { + $im = @ImageCreateFromString($art); + if (@imagesx($im) == 1 || @imagesy($im) == 1 && $im) { + return false; + } + } + + // Push the image into the database + $sql = "UPDATE album SET art = '" . sql_escape($art) . "'," . + " art_mime = '" . sql_escape($mime) . "'" . + " WHERE id = '$this->id'"; + $db_results = mysql_query($sql, dbh()); + + return true; + + } // if we've got something + + /* Default to false */ + return false; + + } // find_art + + /*! + @function get_song_ids + @discussion returns a list of song_ids on the album + get_songs returns a list of song objects + */ + + // it seems get_songs really should call this, + // but I don't feel comfortable imposing that - RCR + function get_song_ids( $limit = 0 ) { + $results = array(); + $sql = "SELECT id FROM song WHERE album='$this->id' ORDER BY track, title"; + if ($limit) { $sql .= " LIMIT $limit"; } + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $results[] = $r->id; + } + return( $results ); + } // get_song_ids + +} //end of album class + +?> diff --git a/modules/class/artist.php b/modules/class/artist.php new file mode 100644 index 00000000..a0b4caa7 --- /dev/null +++ b/modules/class/artist.php @@ -0,0 +1,207 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Artist Class +*/ + +class Artist { + + /* Variables from DB */ + var $id; + var $name; + var $songs; + var $albums; + var $prefix; + + /*! + @function Artist + @discussion Song class, for modifing a song. + @param $song_id The ID of the song + */ + function Artist($song_id = 0) { + + /* If we have passed an id then do something */ + if ($song_id) { + + /* Assign id for use in get_info() */ + $this->id = $song_id; + + /* Get the information from the db */ + if ($info = $this->get_info()) { + + /* Assign Vars */ + $this->name = $info->name; + $this->prefix = $info->prefix; + } // if info + + } // if song_id + + } //constructor + + + /*! + @function get_info + @discussion get's the vars for $this out of the database + @param $this->id Taken from the object + */ + function get_info() { + + /* Grab the basic information from the catalog and return it */ + $sql = "SELECT * FROM artist WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_object($db_results); + + return $results; + + } //get_info + + /*! + @function get_albums + @discussion gets the albums for this artist + */ + function get_albums() { + + $results = array(); + + $sql = "SELECT DISTINCT(album.id) FROM song,album WHERE song.album=album.id AND song.artist='$this->id' ORDER BY album.name"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $results[] = new Album($r->id); + } + + return $results; + + } // get_albums + + /*! + @function get_songs + @discussion gets the songs for this artist + */ + function get_songs() { + + $sql = "SELECT song.id FROM song WHERE song.artist='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $results[] = new Song($r->id); + } + + return $results; + + } // get_songs + + /*! + @function get_random_songs + @discussion gets a random number, and + a random assortment of songs from this + album + */ + function get_random_songs() { + + $results = array(); + + $sql = "SELECT id FROM song WHERE artist='$this->id' ORDER BY RAND() LIMIT " . rand(1,$this->songs); + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_array($db_results)) { + $results[] = $r[0]; + } + + return $results; + + } // get_random_songs + + /*! + @function get_count + @discussion gets the album and song count of + this artist + */ + function get_count() { + + /* Define vars */ + $songs = 0; + $albums = 0; + + $sql = "SELECT COUNT(song.id) FROM song WHERE song.artist='$this->id' GROUP BY song.album"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_array($db_results)) { + $songs += $r[0]; + $albums++; + } + + /* Set Object Vars */ + $this->songs = $songs; + $this->albums = $albums; + + return true; + + } // get_count + + /*! + @function format_artist + @discussion this function takes an array of artist + information and reformats the relevent values + so they can be displayed in a table for example + it changes the title into a full link. + */ + function format_artist() { + + /* Combine prefix and name, trim then add ... if needed */ + $name = truncate_with_ellipse(trim($this->prefix . " " . $this->name)); + + $this->f_name = $this->name; + $this->full_name = trim($this->prefix . " " . $this->name); + //FIXME: This shouldn't be set like this, f_name should be like this + $this->link = "<a href=\"" . conf('web_path') . "/artists.php?action=show&artist=" . $this->id . "\" title=\"" . $this->full_name . "\">" . + $name . "</a>"; + $this->name = $this->link; + return $artist; + + } // format_artist + + + /*! + @function show_albums + @discussion displays the show albums by artist page + */ + function show_albums() { + + /* Set Vars */ + $web_path = conf('web_path'); + + + $albums = $this->get_albums(); + $this->format_artist(); + $artist = $this; + + require (conf('prefix') . "/templates/show_artist.inc"); + + } // show_albums + + +} //end of artist class + +?> diff --git a/modules/class/catalog.php b/modules/class/catalog.php new file mode 100644 index 00000000..9affc95a --- /dev/null +++ b/modules/class/catalog.php @@ -0,0 +1,1934 @@ +<? +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +/*! + @header Catalog Class + This class handles all actual work in regards to the catalog, it contains functions for creating/listing/updated the catalogs. +*/ + +class Catalog { + + var $name; + var $last_update; + var $last_add; + var $id3_set_command; + var $rename_pattern; + var $sort_pattern; + var $catalog_type; + + // Used in functions + var $albums = array(); + var $artists = array(); + var $genres = array(); + + /*! + @function Catalog + @discussion Catalog class constructor, pulls catalog information + @param $catalog_id The ID of the catalog you want to build information from + */ + function Catalog($catalog_id = 0) { + + /* If we have passed an id then do something */ + if ($catalog_id) { + /* Assign id for use in get_info() */ + $this->id = $catalog_id; + + /* Get the information from the db */ + $info = $this->get_info(); + + /* Assign Vars */ + $this->path = $info->path; + $this->name = $info->name; + $this->last_update = $info->last_update; + $this->last_add = $info->last_add; + $this->id3_set_command = $info->id3_set_command; + $this->rename_pattern = $info->rename_pattern; + $this->sort_pattern = $info->sort_pattern; + $this->catalog_type = $info->catalog_type; + } //catalog_id + + } //constructor + + + /*! + @function get_info + @discussion get's the vars for $this out of the database + @param $this->id Taken from the object + */ + function get_info() { + + /* Grab the basic information from the catalog and return it */ + $sql = "SELECT * FROM catalog WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_object($db_results); + + return $results; + + } //get_info + + + /*! + @function get_catalogs + @discussion Pull all the current catalogs + */ + function get_catalogs() { + + $sql = "SELECT id FROM catalog"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $results[] = new Catalog($r->id); + } + + return $results; + + } // get_catalogs + + + /*! + @function get_catalog_stats + @discussion Pulls information about number of songs etc for a specifc catalog, or all catalogs + calls many other internal functions, returns an object containing results + @param $catalog_id If set tells us to pull from a single catalog, rather than all catalogs + */ + function get_catalog_stats($catalog_id=0) { + + $results->songs = $this->count_songs($catalog_id); + $results->albums = $this->count_albums($catalog_id); + $results->artists = $this->count_artists($catalog_id); + $results->size = $this->get_song_size($catalog_id); + $results->time = $this->get_song_time($catalog_id); + + } // get_catalog_stats + + + /*! + @function get_song_time + @discussion Get the total amount of time (song wise) in all or specific catalog + @param $catalog_id If set tells ut to pick a specific catalog + */ + function get_song_time($catalog_id=0) { + + $sql = "SELECT SUM(song.time) FROM song"; + if ($catalog_id) { + $sql .= " WHERE catalog='$catalog_id'"; + } + + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_field($db_results); + + /* Do some conversion to get Hours Min Sec */ + + + return $results; + + } // get_song_time + + + /*! + @function get_song_size + @discussion Get the total size of songs in all or a specific catalog + @param $catalog_id If set tells us to pick a specific catalog + */ + function get_song_size($catalog_id=0) { + + $sql = "SELECT SUM(song.size) FROM song"; + if ($catalog_id) { + $sql .= " WHERE catalog='$catalog_id'"; + } + + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_field($db_results); + + /* Convert it into MB */ + $results = ($results / 1048576); + + return $results; + + } // get_song_size + + + /*! + @function count_artists + @discussion Count the number of artists in all catalogs or in a specific one + @param $catalog_id If set tells us to pick a specific catalog + */ + function count_artists($catalog_id=0) { + + $sql = "SELECT DISTINCT(song.artist) FROM song"; + if ($catalog_id) { + $sql .= " WHERE catalog='$catalog_id'"; + } + + $db_results = mysql_query($sql,dbh()); + + $results = mysql_num_rows($db_results); + + return $results; + + } // count_artists + + + /*! + @function count_albums + @discussion Count the number of albums in all catalogs or in a specific one + @param $catalog_id If set tells us to pick a specific catalog + */ + function count_albums($catalog_id=0) { + + $sql = "SELECT DISTINCT(song.album) FROM song"; + if ($catalog_id) { + $sql .=" WHERE catalog='$catalog_id'"; + } + + $db_results = mysql_query($sql, dbh()); + + $results = mysql_num_rows($db_results); + + return $results; + + } // count_albums + + + /*! + @function count_songs + @discussion Count the number of songs in all catalogs, or a specific one + @param $catalog_id If set tells us to pick a specific catalog + */ + function count_songs($catalog_id=0) { + + $sql = "SELECT count(*) FROM song"; + if ($catalog_id) { + $sql .= " WHERE catalog='$catalog_id'"; + } + + $db_results = mysql_query($sql, dbh()); + $results = mysql_fetch_field($db_results); + + return $results; + + } // count_songs + + /*! + @function add_file + @discussion adds a single file + */ + function add_file($filename) { + + $file_size = @filesize($filename); + $pattern = "/\.[" . conf('catalog_file_pattern') . "]$/i"; + + if ( preg_match($pattern ,$filename) && ($file_size > 0) && (!preg_match('/\.AppleDouble/', $filename)) ) { + if(!$this->check_local_mp3($filename,$gather_type)) { + $this->insert_local_song($filename,$file_size); + } + } // if valid file + + + } // add_file + + + /*! + @function add_files + @discussion Recurses throught $this->path and pulls out all mp3s and returns the full + path in an array. Passes gather_type to determin if we need to check id3 + information against the db. + @param $path The root path you want to start grabing files from + @param $gather_type=0 Determins if we need to check the id3 tags of the file or not + */ + function add_files($path,$gather_type=0,$parse_m3u=0) { + /* Strip existing escape slashes and then add them again + This is done because we keep adding to the dir (slashed) + (non slashed) + and a double addslashes would pooch things + */ + + /* Open up the directory */ + $handle = opendir(stripslashes($path)); + + if (!is_resource($handle)) { + if (conf('debug')) { log_event($_SESSION['userdata']['username'],'read',"Unable to Open $path",'ampache-catalog'); } + echo "<font class=\"error\">" . _("Error: Unable to open") . " $path</font><br />\n"; + } + + /* Recurse through this dir and create the files array */ + while ( false !== ( $file = readdir($handle) ) ) { + + // Fix Found by Naund + // Needed to protect from ' in filenames + $file = sql_escape($file); + + // Prevent the script from timing out + set_time_limit(0); + + /* if not .. or . */ + if ($file != "." AND $file != "..") { + + if (conf('debug')) { + log_event($_SESSION['userdata']['username'],'read',"Starting work on $file inside $path",'ampache-catalog'); + } + + /* Change the dir so is_dir works correctly */ + if (!@chdir(stripslashes($path))) { + if (conf('debug')) { log_event($_SESSION['userdata']['username'],'read',"Unable to chdir $path",'ampache-catalog'); } + echo "<font class=\"error\">" . _("Error: Unable to change to directory") . " $path</font><br />\n"; + } + + /* Create the new path */ + $full_file = stripslashes($path."/".$file); + $full_file = str_replace("//","/",$full_file); + + if (conf('no_symlinks')) { + if (is_link($full_file)) { $failed_check = 1; } + } + + /* If it's a dir run this function again! */ + if (is_dir($full_file) AND !$failed_check) { + $this->add_files($full_file,$gather_type,$parse_m3u); + unset($failed_check); + } //it's a directory + + /* If it's not a dir let's roll with it */ + else { + /* Get the file information */ + $file_size = @filesize($full_file); + + if (!$file_size && $file_size != '0') { + echo "<font class=\"error\">" . _("Error: Unable to get filesize for") . " $full_file <br />"; + if (conf('debug')) { log_event($GLOBALS['user']->username,' read ',"Error: Unable to get filesize for $full_file",'ampache-catalog'); } + } // if no filesize + + $pattern = "/\.(" . conf('catalog_file_pattern'); + if ($parse_m3u) { + $pattern .= "|m3u)$/i"; + } + else { + $pattern .= ")$/i"; + } + + /* see if this is an mp3 file and if it is greater than 0 bytes */ + if ( preg_match($pattern ,$file) && ($file_size > 0) && (!preg_match('/\.AppleDouble/', $file)) ) { + + if (is_readable($full_file)) { + + if (substr($file,-3,3) == 'm3u') { + if ($this->import_m3u($full_file)) { + echo " " . _("Added Playlist From") . " $file . . . .<br />\n"; + flush(); + } + } // if it's an m3u + + else { + + /* see if the current song is in the catalog */ + $found = $this->check_local_mp3($full_file,$gather_type); + + /* If not found then insert, gets id3 information + * and then inserts it into the database + */ + if (!$found) { + $this->insert_local_song($full_file,$file_size); + + /* Stupid little cutesie thing */ + $this->count++; + if ( !($this->count%conf('catalog_echo_count')) ) { + echo _("Added") . " $this->count. . . . <br />\n"; + flush(); + } //echos song count + + } // not found + + } // if it's not an m3u + + } // is readable + else { + // not readable, warn user + if (conf('debug')) { log_event($_SESSION['userdata']['username'],'read',"$full_file is not readable by ampache",'ampache-catalog'); } + echo "$full_file " . _("is not readable by ampache") . ".<br />\n"; + + } + + } //if it's a mp3 and is greater than 0 bytes + + else { + if (conf('debug')) { + log_event($_SESSION['userdata']['username'],'read',"$full_file ignored, non audio file or 0 bytes",'ampache-catalog'); + } + } // else not an audio file or 0 size + + } //else it's not a directory + + } //end if not . or .. + + } //end while + + if (conf('debug')) { + log_event($_SESSION['userdata']['username'],' closedir ',"Finished reading $path closing handle",'ampache-catalog'); + } + + /* Close the dir handle */ + @closedir($handle); + + } //add_files + + /*! + @function get_albums + @discussion This gets albums for all songs passed in an array + */ + function get_albums($songs=array()) { + + foreach ($songs as $song_id) { + $sql = "SELECT album FROM song WHERE id='$song_id'"; + $db_results = mysql_query($sql, dbh()); + $results = mysql_fetch_array($db_results); + $albums[] = new Album($results[0]); + } // files + + return $albums; + + } // get_albums + + /*! + @function get_album_art + @discussion This runs through all of the albums and trys to find the + art for them from the mp3s + //FIXME: Make the display a table so it all lines up + */ + function get_album_art($catalog_id=0,$methods=array()) { + + if (!$catalog_id) { $catalog_id = $this->id; } + + // Get all of the albums in this catalog + $albums = $this->get_catalog_albums($catalog_id); + + // Run through them an get the art! + foreach ($albums as $album) { + flush(); + if ($debug) { echo " " . $album->name . " -- "; } + + if ($methods['id3']) { + $found = $album->get_id3_art(); + if ($found && $debug) { echo _("Found in ID3") . "<br />\n"; } + } + if ($methods['amazon'] && !$found) { + $found = $album->get_amazon_art(); + if ($found && $debug) { echo _("Found on Amazon") . "<br />\n"; } + } + if ($methods['folder'] && !$found) { + $found = $album->get_folder_art(); + if ($found && $debug) { echo _("Found in Folder") . "<br />\n"; } + } + if (count($methods) == '0' && !$found) { + $found = $album->get_art(); + if ($found && $debug) { echo _("Found") . "<br />\n"; } + } + + if (!$found && $debug) { echo "<font class=\"error\">" . _("Not Found") . "</font><br />\n"; } + + if ($found) { $art_found++; } + + + /* Stupid little cutesie thing */ + $search_count++; + if ( !($search_count%conf('catalog_echo_count')) ) { + echo _("Searched") . " $search_count. . . . <br />\n"; + flush(); + } //echos song count + + + // Prevent the script from timing out + set_time_limit(0); + + unset($found); + + } // foreach albums + + echo "$art_found album's with art. . .<br />\n"; + flush(); + + } // get_album_art + + /*! + @function get_catalog_albums() + @discussion Returns an array of the albums from a catalog + */ + function get_catalog_albums($catalog_id=0) { + + $results = array(); + + /* Use $this->id if nothing is passed */ + if (!$catalog_id) { $catalog_id = $this->id; } + + $sql = "SELECT DISTINCT(album.id) FROM album,song WHERE song.catalog='$catalog_id' AND song.album=album.id"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $results[] = new Album($r->id); + } + + return $results; + + } // get_catalog_albums + + + /*! + @function get_catalog_files + @discussion Returns an array of song objects from a catalog + @param $catalog_id=0 Specify the catalog ID you want to get the files of + */ + function get_catalog_files($catalog_id=0) { + + $results = array(); + + /* Use $this->id if nothing passed */ + if (!$catalog_id) { $catalog_id = $this->id; } + + $sql = "SELECT id FROM song WHERE catalog='$catalog_id' AND status='enabled'"; + $db_results = mysql_query($sql, dbh()); + + $results = array(); // return an emty array instead of nothing if no objects + while ($r = mysql_fetch_object($db_results)) { + $results[] = new Song($r->id); + } //end while + + return $results; + + } //get_catalog_files + + + /*! + @function get_disabled + @discussion Gets an array of the disabled songs for all catalogs + and returns full song objects with them + */ + function get_disabled() { + global $conf; + + $results = array(); + + $sql = "SELECT id FROM song WHERE status='disabled'"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_array($db_results)) { + $results[] = new Song($r['id']); + } + + return $results; + + } // get_disabled + + + /*! + @function get_files + @discussion Get's an array of .mp3s and returns the filenames + @param $path Get files starting at root $path + */ + function get_files($path) { + + /* Set it as an empty array */ + $files = array(); + + $path = stripslashes($path); + + /* Open up the directory */ + $handle = @opendir($path); + + if (!is_resource($handle)) { echo "<font class=\"error\">" . _("Error: Unable to open") . " $path</font><br />\n"; } + + /* Change dir so we can tell if it's a directory */ + if (!@chdir($path)) { + echo "<font class=\"error\">Error: Unable to change to $path directory</font><br />\n"; + } + + /* Recurse through this dir and create the files array */ + while ( FALSE !== ($file = @readdir($handle)) ) { + + $full_file = stripslashes($path . "/" . $file); + $full_file = str_replace("//","/",$full_file); + + if (conf('no_symlinks')) { + if (is_link($full_file)) { $failed_check = true; } + } + + /* It's a dir */ + if (is_dir($full_file) AND $file != "." AND $file != ".." AND !$failed_check) { + /* Merge the results of the get_files with the current results */ + $files = array_merge($files,$this->get_files($full_file)); + } //isdir + + /* Get the file information */ + $file_info = filesize($full_file); + + $pattern = "/\.[" . conf('catalog_file_pattern') . "]$/i"; + + if ( preg_match($pattern ,$file) && ($file_info > 0) && (!preg_match("/\.AppleDouble/", $file)) ) { + $files[] = $full_file; + } //is mp3 of at least some size + + } //end while + + /* Close the dir handle */ + @closedir($handle); + + /* Return the files array */ + return $files; + + } //get_files + + /*! + @function dump_album_art (Added by Cucumber 20050216) + @discussion This runs through all of the albums and trys to dump the + art for them into the 'folder.jpg' file in the appropriate dir + */ + function dump_album_art($catalog_id=0,$methods=array()) { + if (!$catalog_id) { $catalog_id = $this->id; } + + // Get all of the albums in this catalog + $albums = $this->get_catalog_albums($catalog_id); + + echo "<br /><b>" . _("Starting Dump Album Art") . ". . .</b><br><br />\n"; + + // Run through them an get the art! + foreach ($albums as $album) { + flush(); + if ($image = $album->get_db_art()) { + /* Get the first song in the album */ + $songs = $album->get_songs(1); + $song = $songs[0]; + $dir = dirname($song->file); + $extension = substr($image->art_mime,strlen($image->art_mime)-3,3); + + $preferred_filename = conf('album_art_preferred_filename'); + if (!$preferred_filename) { $preferred_filename = "folder.$extension"; } + + $file = "$dir/$preferred_filename"; + if ($file_handle = @fopen($file,"w")) { + if (fwrite($file_handle, $image->art)) { + $i++; + if ( !($i%conf('catalog_echo_count')) ) { + echo _("Written") . " $i. . . <br />\n"; + flush(); + } //echos song count + if (conf('debug')) { log_event($_SESSION['userdata']['username'],'art_write',"$album->name Art written to $file"); } + } + fclose($file_handle); + } // end if fopen + else { + if (conf('debug')) { log_event($_SESSION['userdata']['username'],'art_write',"Unable to open $file for writting"); } + echo "<font class=\"error\">" . _("Error unable to open file for writting") . " [$file] </font><br />\n"; + } + + } // end if image + + } // end foreach + + echo "<br /><b>" . _("Album Art Dump Complete") . "</b> "; + echo "<a href=\"" . conf('web_path') . "/admin/catalog.php" . "\">[" . _("Return") . "]</a>"; + + flush(); + + } // dump_album_art + + /*! + @function update_last_update + @discussion updates the last_update of the catalog + */ + function update_last_update() { + + $date = time(); + $sql = "UPDATE catalog SET last_update='$date' WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + } // update_last_update + + + /*! + @function update_last_add + @discussion updates the last_add of the catalog + */ + function update_last_add() { + + $date = time(); + $sql = "UPDATE catalog SET last_add='$date' WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + } // update_last_add + + + /*! + @function new_catalog + @discussion The Main array for making a new catalog calls many other child functions within this class + @param $path Root path to start from for catalog + @param $name Name of the new catalog + */ + function new_catalog($path,$name, $id3cmd=0, $ren=0, $sort=0, $type=0,$gather_art=0,$parse_m3u=0,$art=array()) { + + /* Record the time.. time the catalog gen */ + $start_time = time(); + + /* Flush anything that has happened so they don't think it's locked */ + flush(); + + /* + * Step one Add this to the catalog table if it's not + * already there returns the new catalog_id + */ + $catalog_id = $this->check_catalog($path); + + if (!$catalog_id) { + $catalog_id = $this->create_catalog_entry($path,$name,$id3cmd, $ren, $sort, $type); + } + + /* Setup the $this with the new information */ + $this->id = $catalog_id; + $this->path = $path; + $this->name = $name; + $this->id3_set_command = ($id3cmd)?$id3cmd:''; + $this->rename_pattern = ($ren)?$ren:''; + $this->sort_pattern = ($sort)?$sort:''; + $this->catalog_type = $type; + + /* Fluf */ + echo _("Starting Catalog Build") . " [$name]<br />\n"; + flush(); + + + if ($this->catalog_type == 'remote') { + echo _("Running Remote Sync") . ". . .<br /><br />"; + flush(); + $this->get_remote_catalog($type=0); + return true; + } + /* Get the songs and then insert them into the db */ + $this->add_files($this->path,$type,$parse_m3u); + + /* Now Adding Album Art? */ + if ($gather_art) { + echo "<br />\n<b>" . _("Starting Album Art Search") . ". . .</b><br />\n"; + flush(); + $this->get_album_art(0,$art); + } // if we want to gather album art + + /* Do a little stats mojo here */ + $current_time = time(); + + $time_diff = $current_time - $start_time; + if ($time_diff) { $song_per_sec = intval($this->count/$time_diff); } + echo _("Catalog Finished") . ". . . " . _("Total Time") . " [" . date("i:s",$time_diff) . "] " . _("Total Songs") . " [" . $this->count . "] " . + _("Songs Per Seconds") . " [" . $song_per_sec . "]<br />\n"; + + return $catalog_id; + + } //new_catalog + + /*! + @function update_single_item + @discussion updates a single album,artist,song + */ + function update_single_item($type,$id) { + + $songs = array(); + + switch ($type) { + case 'album': + $album = new Album($id); + $songs = $album->get_songs(); + break; + case 'artist': + $artist = new Artist($id); + $songs = $artist->get_songs(); + break; + case 'song': + $songs[0] = new Song($id); + break; + } // end switch type + + foreach($songs as $song) { + + $info = $this->update_song_from_tags($song); + + if ($info['change']) { + echo "<dl>\n\t<li>"; + echo "<b>$song->file " . _("Updated") . "</b>\n"; + echo $info['text']; + echo "\t</li>\n</dl><hr align=\"left\" width=\"50%\" />"; + flush(); + } // if change + else { + echo"<dl>\n\t<li>"; + echo "<b>$song->file</b><br />" . _("No Update Needed") . "\n"; + echo "\t</li>\n</dl><hr align=\"left\" width=\"50%\" />"; + flush(); + } + } // foreach songs + + } // update_single_item + + /*! + @function update_song_from_tags + @discussion updates the song info based on tags + */ + function update_song_from_tags($song) { + + $info = new Audioinfo(); + $results = $info->Info($song->file); + + /* Find the correct key */ + $key = get_tag_type($results); + + /* Fill Missing Information */ + $results = $song->fill_info($results,$this->sort_pattern . "/" . $this->rename_pattern, $this->id, $key); + + /* Clean up the tags */ + $results = clean_tag_info($results,$key,$song->file); + + /* Setup the vars */ + $new_song = new Song(); + $new_song->file = $results['file']; + $new_song->title = $results['title']; + $new_song->year = $results['year']; + $new_song->comment = $results['comment']; + $new_song->bitrate = $results['bitrate']; + $new_song->rate = $results['rate']; + $new_song->mode = $results['mode']; + $new_song->size = $results['size']; + $new_song->time = $results['time']; + $new_song->track = $results['track']; + $artist = $results['artist']; + $album = $results['album']; + $genre = $results['genre']; + + /* Clean up Old Vars */ + unset($results,$key,$info); + + /* + * We have the artist/genre/album name need to check it in the tables + * If found then add & return id, else return id + */ + $new_song->artist = $this->check_artist($artist); + $new_song->f_artist = $artist; + $new_song->genre = $this->check_genre($genre); + $new_song->f_genre = $genre; + $new_song->album = $this->check_album($album,$new_song->year); + $new_song->f_album = $album . " - " . $new_song->year; + $new_song->title = $this->check_title($new_song->title,$new_song->file); + + $info = $song->compare_song_information($song,$new_song); + + if ($info['change']) { + $song->update_song($song->id,$new_song); + } + + return $info; + + } // update_song_from_tags + + /*! + @function add_to_catalog + @discussion this function adds new files to an + existing catalog + */ + function add_to_catalog($type=0) { + + echo _("Starting New Song Search on") . " <b>[$this->name]</b> " . _("catalog") . "<br /><br />\n"; + flush(); + + if ($this->catalog_type == 'remote') { + echo _("Running Remote Update") . ". . .<br /><br />"; + flush(); + $this->get_remote_catalog($type=0); + return true; + } + + /* Set the Start time */ + $start_time = time(); + + /* Get the songs and then insert them into the db */ + $this->add_files($this->path,$type); + + /* Do a little stats mojo here */ + $current_time = time(); + + if ($type != "fast_add") { + echo "<b>" . _("Starting Album Art Search") . ". . .</b><br />\n"; + flush(); + $this->get_album_art(); + } + + /* Update the Catalog last_update */ + $this->update_last_add(); + + $time_diff = $current_time - $start_time; + if ($time_diff) { + $song_per_sec = intval($this->count/$time_diff); + } + if (!$song_per_sec) { + $song_per_sec = "N/A"; + } + if (!$this->count) { + $this->count = 0; + } + + echo "\n<br />" . _("Catalog Update Finished") . "... " . _("Total Time") . " [" . date("i:s",$time_diff) . "] " . + _("Total Songs") . " [" . $this->count . "] " . _("Songs Per Seconds") . " [" . $song_per_sec . "]<br /><br />"; + + } // add_to_catalog + + + /*! + @function get_remote_catalog + @discussion get a remote catalog and runs update if needed + */ + function get_remote_catalog($type=0) { + + if (!class_exists('xmlrpc_client')) { + if (conf('debug')) { log_event($_SESSION['userdata']['username'],'xmlrpc',"Unable to load XMLRPC library"); } + echo "<font class=\"error\"><b>" . _("Error") . "</b>: " . _("Unable to load XMLRPC library, make sure XML-RPC is enabled") . "<br />\n"; + return false; + } + + // first, glean out the information from the path about the server and remote path + preg_match("/http:\/\/([^\/]+)\/*(.*)/", $this->path, $match); + $server = $match[1]; + $path = $match[2]; + + if ( ! $path ) { + $client = new xmlrpc_client("/server.php", $server, 80); + } + else { + $client = new xmlrpc_client("/$path/server.php", $server, 80); + } + + $f = new xmlrpcmsg('remote_server_query', array(new xmlrpcval( conf('web_path'), "string")) ); + //if (conf('debug')) { $client->setDebug(1); } + $response = $client->send($f); + $value = $response->value(); + + if ( !$response->faultCode() ) { + $data = old_xmlrpc_decode($value); + + // Print out the catalogs we are going to sync + //FIXME: We should add catalog level access control + foreach ($data as $vars) { + $catalog_name = $vars[0]; + print("<b>Reading Remote Catalog: $catalog_name</b> [$this->path]<br />\n"); + } + } + else { + $error_msg = _("Error connecting to") . " " . $server . " " . _("Code") . ": " . $response->faultCode() . " " . _("Reason") . ": " . $response->faultString(); + log_event($_SESSION['userdata']['username'],'xmlrpc',$error_msg); + echo "<p class=\"error\">$error_msg</p>"; + return; + } + + $f = new xmlrpcmsg('remote_song_query', array(new xmlrpcval( 'song', "string")) ); + $response = $client->send($f); + $value = $response->value(); + + if ( !$response->faultCode() ) { + $data = old_xmlrpc_decode($value); + $this->update_remote_catalog($data,$this->path); + } + else { + $error_msg = _("Error connecting to") . " " . $server . " " . _("Code") . ": " . $response->faultCode() . " " . _("Reason") . ": " . $response->faultString(); + log_event($_SESSION['userdata']['username'],'xmlrpc',$error_msg); + echo "<p class=\"error\">$error_msg</p>"; + } + + echo "<p>" . _("Completed updating remote catalog(s)") . ".</p><hr>\n"; + + + } // get_remote_catalog + + /*! + @function update_remote_catalog + @discussion actually updates from the remote data + */ + function update_remote_catalog($songs,$root_path) { + global $settings, $dbh, $artists; + + + /* + We need to check the incomming songs + to see which ones need to be added + */ + foreach ($songs as $song) { + + // Prevent a timeout + set_time_limit(0); + + $song = base64_decode($song); + + $data = explode("::", $song); + + $new_song->artist = $this->check_artist($data[0]); + $new_song->album = $this->check_album($data[1],$data[4]); + $new_song->title = $data[2]; + $new_song->comment = $data[3]; + $new_song->year = $data[4]; + $new_song->bitrate = $data[5]; + $new_song->rate = $data[6]; + $new_song->mode = $data[7]; + $new_song->size = $data[8]; + $new_song->time = $data[9]; + $new_song->track = $data[10]; + $new_song->genre = $this->check_genre($data[11]); + $new_song->file = $root_path . "/play/index.php?song=" . $data[12] . "uid=$md5_ip"; + $new_song->catalog = $this->id; + + if (!$song_id = $this->check_remote_song($new_song->file)) { + $this->insert_remote_song($new_song); + } + + } // foreach new Songs + + //FIXME: Delete Songs that were not updated (gone) + + // now delete invalid entries + $this->clean_albums(); + $this->clean_stats(); + $this->clean_artists(); + $this->clean_flagged(); + + } // update_remote_catalog + + + /*! + @function clean_catalog + @discussion Cleans the Catalog of files that no longer exist grabs from $this->id or $id passed + Doesn't actually delete anything, disables errored files, and returns them in an array + @param $catalog_id=0 Take the ID of the catalog you want to clean + @param $action=0 Delete/Disable, default is disable + */ + function clean_catalog($catalog_id=0,$action=0) { + + /* Define the Arrays we will need */ + $dead_files = array(); + + if (!$catalog_id) { $catalog_id = $this->id; } + + echo "Cleaning the <b>[" . $this->name . "]</b> Catalog...<br /><br />"; + flush(); + + /* Get all songs in this catalog */ + $sql = "SELECT id,file FROM song WHERE catalog='$catalog_id' AND status='enabled'"; + $db_results = mysql_query($sql, dbh()); + + /* Recurse through files, put @ to prevent errors poping up */ + while ($results = mysql_fetch_object($db_results)) { + + /* Remove slashes while we are checking for its existance */ + $results->file = stripslashes($results->file); + + /* Stupid little cutesie thing */ + $this->count++; + if ( !($this->count%conf('catalog_echo_count')) ) { + echo _("Checking") . " $this->count. . . . <br />\n"; + flush(); + } //echos song count + + /* Also check the file information */ + $file_info = @filesize($results->file); + + /* If it errors somethings splated, or the files empty */ + if (!file_exists($results->file) OR $file_info < 1) { + + /* Add Error */ + echo "<font class=\"error\">Error File Not Found or 0 Bytes: " . $results->file . "</font><br />"; + flush(); + + /* Add this file to the list for removal from the db */ + $dead_files[] = $results; + } //if error + + } //while gettings songs + + /* Incase there's been a snafo with a mount point on something + * don't actually delete from DB here, simply disable and list + */ + if (count($dead_files)) { + foreach ($dead_files as $data) { + + //FIXME: Until I fix the form, assume delete + //if ($action === 'delete_dead') { + $sql = "DELETE FROM song WHERE id='$data->id'"; + //} + // + //else { + // $sql = "UPDATE song SET status='disabled' WHERE id='$data->id'"; + //} + + $db_results = mysql_query($sql, dbh()); + + /* DB Error occured */ + if (!$db_results) { + /* Add Error */ + } //if error + + } //end foreach + + } // end if dead files + + /* Step two find orphaned Arists/Albums + * This finds artists and albums that no + * longer have any songs associated with them + */ + $this->clean_albums(); + $this->clean_artists(); + $this->clean_stats(); + $this->clean_playlists(); + $this->clean_flagged(); + + /* Return dead files, so they can be listed */ + echo "<b>" . _("Catalog Clean Done") . " [" . count($dead_files) . "] " . _("files removed") . "</b><br />\n"; + flush(); + return $dead_files; + + } //clean_catalog + + + /*! + @function clean_albums + @discussion This function cleans out unused albums + @param $this->id Depends on the current object + */ + function clean_albums() { + + /* Mysql 3.23 doesn't support our cool query so we have to do it a different way */ + if (preg_match("/^3\./",mysql_get_server_info())) { + $sql = "SELECT album.id FROM album LEFT JOIN song ON song.album = album.id WHERE song.id IS NULL"; + $db_results = mysql_query($sql, dbh()); + + $results = array(); + + while ($r = mysql_fetch_row($db_results)) { + $results[] = $r; + } + + foreach ($results as $dead) { + + $sql = "DELETE FROM album WHERE id='$dead[0]'"; + $db_results = mysql_query($sql,dbh()); + } + return true; + } + + /* Do a complex delete to get albums where there are no songs */ + $sql = "DELETE FROM album USING album LEFT JOIN song ON song.album = album.id WHERE song.id IS NULL"; + $db_results = mysql_query($sql, dbh()); + + } //clean_albums + + /*! + @function clean_flagged + @discussion This functions cleans ou unused flagged items + */ + function clean_flagged() { + + /* Mysql 3.23 doesn't support our cool query so we have to do it a different way */ + if (preg_match("/^3\./",mysql_get_server_info())) { + $sql = "SELECT flagged.id FROM flagged LEFT JOIN song ON song.id=flagged.song WHERE song.id IS NULL"; + $db_results = mysql_query($sql, dbh()); + + $results = array(); + + while ($r = mysql_fetch_row($db_results)) { + $results[] = $r; + } + + foreach ($results as $dead) { + $sql = "DELETE FROM flagged WHERE id='$dead[0]'"; + $db_results = mysql_query($sql, dbh()); + } + return true; + } + + /* Do a complex delete to get flagged items where the songs are now gone */ + $sql = "DELETE FROM flagged USING flagged LEFT JOIN song ON song.id = flagged.song WHERE song.id IS NULL"; + $db_results = mysql_query($sql, dbh()); + + } // clean_flagged + + + /*! + @function clean_artists + @discussion This function cleans out unused artists + @param $this->id Depends on the current object + */ + function clean_artists() { + + /* Mysql 3.23 doesn't support our cool query so we have to do it a different way */ + if (preg_match("/^3\./",mysql_get_server_info())) { + $sql = "SELECT artist.id FROM artist LEFT JOIN song ON song.artist = artist.id WHERE song.id IS NULL"; + $db_results = mysql_query($sql, dbh()); + + $results = array(); + + while ($r = mysql_fetch_row($db_results)) { + $results[] = $r; + } + + foreach ($results as $dead) { + + $sql = "DELETE FROM artist WHERE id='$dead[0]'"; + $db_results = mysql_query($sql,dbh()); + } + return true; + } + + + /* Do a complex delete to get artists where there are no songs */ + $sql = "DELETE FROM artist USING artist LEFT JOIN song ON song.artist = artist.id WHERE song.id IS NULL"; + $db_results = mysql_query($sql, dbh()); + + } //clean_artists + + /* + @function clean_playlists + @discussion cleans out dead files from playlists + @param $this->id depends on the current object + */ + function clean_playlists() { + + /* Mysql 3.23 doesn't support our cool query so we have to do it a different way */ + if (preg_match("/^3\./",mysql_get_server_infO())) { + $sql = "SELECT playlist_data.song FROM playlist_data LEFT JOIN song ON song.id = playlist_data.song WHERE song.file IS NULL"; + $db_results = mysql_query($sql, dbh()); + + $results = array(); + + while ($r = mysql_fetch_row($db_results)) { + $results[] = $r; + } + + foreach ($results as $dead) { + $sql = "DELETE FROM playlist_data WHERE song='$dead[0]'"; + $db_results = mysql_query($sql, dbh()); + } + return true; + } + + /* Do a complex delete to get playlist songs where there are no songs */ + $sql = "DELETE FROM playlist_data USING playlist_data LEFT JOIN song ON song.id = playlist_data.song WHERE song.file IS NULL"; + $db_results = mysql_query($sql, dbh()); + + } // clean_playlists + + /*! + @function clean_stats + @discussion This functions removes stats for songs/albums that no longer exist + @param $catalog_id The ID of the catalog to clean + */ + function clean_stats() { + + $version = mysql_get_server_info(); + + /* Mysql 3.23 doesn't support our cool query so we have to do it a different way */ + if (preg_match("/^3\./",$version)) { + $sql = "SELECT object_count.id FROM object_count LEFT JOIN song ON song.id = object_count.object_id WHERE object_type='song' AND song.id IS NULL"; + $db_results = mysql_query($sql, dbh()); + + $results = array(); + + while ($r = mysql_fetch_row($db_results)) { + $results[] = $r; + } + + foreach ($results as $dead) { + + $sql = "DELETE FROM object_count WHERE id='$dead[0]'"; + $db_results = mysql_query($sql,dbh()); + } + + } + // We assume this will be 4.0+ + else { + /* Crazy SQL Mojo to remove stats where there are no songs */ + $sql = "DELETE FROM object_count USING object_count LEFT JOIN song ON song.id=object_count.object_id WHERE object_type='song' AND song.id IS NULL"; + $db_results = mysql_query($sql, dbh()); + } + + /* Mysql 3.23 doesn't support our cool query so we have to do it a different way */ + if (preg_match("/^3\./",$version)) { + $sql = "SELECT object_count.id FROM object_count LEFT JOIN album ON album.id = object_count.object_id WHERE object_type='album' AND album.id IS NULL"; + $db_results = mysql_query($sql, dbh()); + + $results = array(); + + while ($r = mysql_fetch_row($db_results)) { + $results[] = $r; + } + + foreach ($results as $dead) { + + $sql = "DELETE FROM object_count WHERE id='$dead[0]'"; + $db_results = mysql_query($sql,dbh()); + } + } + // We assume 4.0+ Here + else { + /* Crazy SQL Mojo to remove stats where there are no albums */ + $sql = "DELETE FROM object_count USING object_count LEFT JOIN album ON album.id=object_count.object_id WHERE object_type='album' AND album.id IS NULL"; + $db_results = mysql_query($sql, dbh()); + } + + /* Mysql 3.23 doesn't support our cool query so we have to do it a different way */ + if (preg_match("/^3\./",$version)) { + $sql = "SELECT object_count.id FROM object_count LEFT JOIN artist ON artist.id = object_count.object_id WHERE object_type='artist' AND artist.id IS NULL"; + $db_results = mysql_query($sql, dbh()); + + $results = array(); + + while ($r = mysql_fetch_row($db_results)) { + $results[] = $r; + } + + foreach ($results as $dead) { + + $sql = "DELETE FROM object_count WHERE id='$dead[0]'"; + $db_results = mysql_query($sql,dbh()); + } + } + // We assume 4.0+ here + else { + /* Crazy SQL Mojo to remove stats where ther are no artists */ + $sql = "DELETE FROM object_count USING object_count LEFT JOIN artist ON artist.id=object_count.object_id WHERE object_type='artist' AND artist.id IS NULL"; + $db_results = mysql_query($sql, dbh()); + } + + + } // clean_stats + + + /*! + @function verify_catalog + @discussion This function compares the DB's information with the ID3 tags + @param $catalog_id The ID of the catalog to compare + */ + function verify_catalog($catalog_id=0,$gather_type=0) { + + /* Create and empty song for us to use */ + $total_updated = 0; + + /* Set it to this if they don't pass anything */ + if (!$catalog_id) { + $catalog_id = $this->id; + } + + /* First get the filenames for the catalog */ + $sql = "SELECT id FROM song WHERE catalog='$catalog_id' ORDER BY id"; + $db_results = mysql_query($sql, dbh()); + $number = mysql_num_rows($db_results); + + echo _("Updating the") . " <b>[ $this->name ]</b> " . _("Catalog") . "<br />\n"; + echo $number . " " . _("songs found checking tag information.") . "<br /><br />\n\n"; + flush(); + + /* Magical Fix so we don't run out of time */ + set_time_limit(0); + + /* Recurse through this catalogs files + * and get the id3 tage information, + * if it's not blank, and different in + * in the file then update! + */ + while ($results = mysql_fetch_object($db_results)) { + + $song = new Song($results->id); + + if (is_readable($song->file)) { + unset($skip); + + /* If they have specified fast_update check the file + filemtime to make sure the file has actually + changed + */ + if ($gather_type === "fast_update") { + $file_date = filemtime($song->file); + if ($file_date < $this->last_update) { $skip = true; } + } // if gather_type + + if ($song->update_time >= $this->last_update) { + $skip = true; + $song->update_utime($song->id,time()+86400); + } + + // if the file hasn't been modified since the last_update + if (!$skip) { + + $info = $this->update_song_from_tags($song); + $album_id = $song->album; + if ($info['change']) { + echo "<dl>\n\t<li>"; + echo "<b>$song->file " . _("Updated") . "</b>\n"; + echo $info['text']; + /* If we aren't doing a fast update re-gather album art */ + if ($gather_type !== "fast_update" AND !isset($searched_albums[$album_id])) { + $album = new Album($song->album); + $searched_albums[$album_id] = 1; + $found = $album->get_art(); + unset($album); + if ($found) { $is_found = _(" FOUND"); } + echo "<br /><b>" . _("Searching for new Album Art") . ". . .$is_found</b><br />\n"; + unset($found,$is_found); + } + elseif (isset($searched_albums[$album_id])) { + echo "<br /><b>" . _("Album Art Already Found") . ". . .</b><br />\n"; + } + echo "\t</li>\n</dl>\n<hr align=\"left\" width=\"50%\" />\n"; + flush(); + $total_updated++; + } + + unset($info); + + } // end skip + + /* Stupid little cutesie thing */ + $this->count++; + if ( !($this->count%conf('catalog_echo_count')) ) { + echo "Checked $this->count. . . . <br />\n"; + flush(); + } //echos song count + + } // end if file exists + + else { + echo "<dl>\n <li>"; + echo "<b>$song->file does not exist or is not readable</b>\n"; + echo " </li>\n</dl>\n<hr align=\"left\" width=\"50%\" />\n"; + // Should we remove it from catalog? + } + + + } //end foreach + + + /* After we have updated all the songs with the new information clear any empty albums/artists */ + $this->clean_albums(); + $this->clean_artists(); + $this->clean_stats(); + $this->clean_flagged(); + + // Update the last_update + $this->update_last_update(); + + echo "Update Finished. Checked $this->count. $total_updated songs updated.<br /><br />"; + + } //verify_catalog + + + /*! + @function create_catalog_entry + @discussion Creates a new catalog from path and type + @param $path The root path for this catalog + @param $name The name of the new catalog + */ + function create_catalog_entry($path,$name,$id3cmd=0,$ren=0,$sort=0, $type='local') { + + // Current time + $date = time(); + + $path = sql_escape($path); + $name = sql_escape($name); + + if($id3cmd && $ren && $sort) { + $sql = "INSERT INTO catalog (path,name,last_update,id3_set_command,rename_pattern,sort_pattern,catalog_type) " . + " VALUES ('$path','$name','$date', '$id3cmd', '$ren', '$sort','$type')"; + } + else { + $sql = "INSERT INTO catalog (path,name,last_update) VALUES ('$path','$name','$date')"; + } + + $db_results = mysql_query($sql, dbh()); + $catalog_id = mysql_insert_id(dbh()); + + return $catalog_id; + + } //create_catalog_entry + + + /*! + @function check_catalog + @discussion Checks for the $path already in the catalog table + @param $path The root path for the catalog we are checking + */ + function check_catalog($path) { + + $path = sql_escape($path); + + $sql = "SELECT id FROM catalog WHERE path='$path'"; + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_object($db_results); + + return $results->id; + + } //check_catalog + + + /*! + @function check_artist + @discussion Takes $artist checks if there then return id else insert and return id + @param $artist The name of the artist + */ + function check_artist($artist) { + + // Only get the var ones.. less func calls + $cache_limit = conf('artist_cache_limit'); + + /* Clean up the artist */ + $artist = trim($artist); + $artist = sql_escape($artist); + + + /* Ohh no the artist has lost it's mojo! */ + if (!$artist) { + $artist = "Unknown (Orphaned)"; + } + + // Remove the prefix so we can sort it correctly + preg_match("/^(The\s|An\s|A\s)(.*)/i",$artist,$matches); + + if (count($matches)) { + $artist = $matches[2]; + $prefix = $matches[1]; + } + + // Check to see if we've seen this artist before + if (isset($this->artists[$artist])) { + return $this->artists[$artist]; + } // if we've seen this artist before + + /* Setup the checking sql statement */ + $sql = "SELECT id FROM artist WHERE name LIKE '$artist' "; + $db_results = mysql_query($sql, dbh()); + + /* If it's found */ + if ($r = mysql_fetch_object($db_results)) { + $artist_id = $r->id; + } //if found + + /* If not found create */ + else { + + $sql = "INSERT INTO artist (name, prefix) VALUES ('$artist', '$prefix')"; + $db_results = mysql_query($sql, dbh()); + $artist_id = mysql_insert_id(dbh()); + + + if (!$db_results) { + echo "Error Inserting Artist:$artist <br />"; + flush(); + } + + } //not found + + if ($cache_limit) { + + $artist_count = count($this->artists); + if ($artist_count == $cache_limit) { + $this->artists = array_slice($this->artists,1); + } + if (conf('debug')) { log_event($_SESSION['userdata']['username'],'cache',"Adding $artist with $artist_id to Cache",'ampache-catalog'); } + $array = array($artist => $artist_id); + $this->artists = array_merge($this->artists, $array); + unset($array); + + } // if cache limit is on.. + + return $artist_id; + + } //check_artist + + + /*! + @function check_album + @disucssion Takes $album and checks if there then return id else insert and return id + @param $album The name of the album + */ + function check_album($album,$album_year=0) { + + /* Clean up the album name */ + $album = trim($album); + $album = sql_escape($album); + $album_year = intval($album_year); + + // Set it once to reduce function calls + $cache_limit = conf('album_cache_limit'); + + /* Ohh no the album has lost it's mojo */ + if (!$album) { + $album = "Unknown (Orphaned)"; + } + + // Remove the prefix so we can sort it correctly + preg_match("/^(The\s|An\s|A\s)(.*)/i",$album,$matches); + + if (count($matches)) { + $album = $matches[2]; + $prefix = $matches[1]; + } + + // Check to see if we've seen this album before + if (isset($this->albums[$album])) { + return $this->albums[$album]; + } + + /* Setup the Query */ + $sql = "SELECT id FROM album WHERE name LIKE '$album'"; + if ($album_year) { $sql .= " AND year='$album_year'"; } + $db_results = mysql_query($sql, dbh()); + + /* If it's found */ + if ($r = mysql_fetch_object($db_results)) { + $album_id = $r->id; + + } //if found + + /* If not found create */ + else { + + $sql = "INSERT INTO album (name, prefix,year) VALUES ('$album', '$prefix','$album_year')"; + $db_results = mysql_query($sql, dbh()); + $album_id = mysql_insert_id(dbh()); + + if (!$db_results) { + echo "Error Inserting Album:$album <br />"; + flush(); + } + + } //not found + + if ($cache_limit > 0) { + + $albums_count = count($this->albums); + + if ($albums_count == $cache_limit) { + $this->albums = array_slice($this->albums,1); + } + $array = array($album => $album_id); + $this->albums = array_merge($this->albums,$array); + unset($array); + + } // if cache limit is on.. + + return $album_id; + + } //check_album + + + /*! + @function check_genre + @discussion Finds the Genre_id from the text name + @param $genre The name of the genre + */ + function check_genre($genre) { + + if (!$genre) { + return false; + } + + if ($this->genres[$genre]) { + return $this->genres[$genre]; + } + + /* Look in the genre table */ + $genre = sql_escape($genre); + $sql = "SELECT id FROM genre WHERE name LIKE '$genre'"; + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_object($db_results); + + if (!$results->id) { + $sql = "INSERT INTO genre (name) VALUES ('$genre')"; + $db_results = mysql_query($sql, dbh()); + $results->id = mysql_insert_id(dbh()); + } + + $this->genres[$genre] = $results->id; + + return $results->id; + + } //check_genre + + + /*! + @function check_title + @discussion this checks to make sure something is + set on the title, if it isn't it looks at the + filename and trys to set the title based on that + */ + function check_title($title,$file=0) { + + if (strlen(trim($title)) < 1) { + preg_match("/.+\/(.*)\.....?$/",$file,$matches); + $title = sql_escape($matches[1]); + } + + return $title; + + + } //check_title + + + /*! + @function insert_local_song + @discussion Insert a song that isn't already in the database this + function is in here so we don't have to create a song object + @param $file The file name we are adding (full path) + @param $file_info The information of the file, size etc taken from stat() + */ + function insert_local_song($file,$file_info) { + + /* Create the Audioinfo object and get info */ + $audio_info = new Audioinfo(); + $song_obj = new Song(); + $results = $audio_info->Info($file); + $results['file'] = $file; + + $key = get_tag_type($results); + + /* Fill Empty info from filename/path */ + $results = $song_obj->fill_info($results,$this->sort_pattern . "/" . $this->rename_pattern,$this->id,$key); + + /* Clean Up the tags */ + $results = clean_tag_info($results,$key,$file); + + /* Set the vars here... so we don't have to do the '" . $blah['asd'] . "' */ + $title = sql_escape($results['title']); + $artist = $results['artist']; + $album = $results['album']; + $genre = $results['genre']; + $bitrate = $results['bitrate']; + $rate = $results['rate']; + $mode = $results['mode']; + $size = $results['size']; + $song_time = $results['time']; + $track = $results['track']; + $year = $results['year']; + $comment = $results['comment']; + $current_time = time(); + + /* + * We have the artist/genre/album name need to check it in the tables + * If found then add & return id, else return id + */ + $artist_id = $this->check_artist($artist); + $genre_id = $this->check_genre($genre); + $album_id = $this->check_album($album,$year); + $title = $this->check_title($title,$file); + $add_file = sql_escape($results['file']); + + $sql = "INSERT INTO song (file,catalog,album,artist,title,bitrate,rate,mode,size,time,track,genre,addition_time,year,comment)" . + " VALUES ('$add_file','$this->id','$album_id','$artist_id','$title','$bitrate','$rate','$mode','$size','$song_time','$track','$genre_id','$current_time','$year','$comment')"; + + $db_results = mysql_query($sql, dbh()); + + if (!$db_results) { + if (conf('debug')) { log_event($_SESSION['userdata']['username'],'insert',"Unable to insert $file -- $sql",'ampache-catalog'); } + echo "<span style=\"color: #F00;\">Error Adding $file </span><br />$sql<br />"; + flush(); + } + + /* Clear Variables */ + unset($results,$audio_info,$song_obj); + + } // insert_local_song + + /*! + @function insert_remote_song + @discussion takes the information gotten from XML-RPC and + inserts it into the local database. The filename + ends up being the url. + */ + function insert_remote_song($song) { + + $url = sql_escape($song->file); + $title = $this->check_title($song->title); + $title = sql_escape($title); + $comment = sql_escape($song->comment); + $current_time = time(); + + $sql = "INSERT INTO song (file,catalog,album,artist,title,bitrate,rate,mode,size,time,track,genre,addition_time,year,comment)" . + " VALUES ('$url','$song->catalog','$song->album','$song->artist','$title','$song->bitrate','$song->rate','$song->mode','$song->size','$song->time','$song->track','$song->genre','$current_time','$song->year','$comment')"; + $db_results = mysql_query($sql, dbh()); + + if (!$db_results) { + if (conf('debug')) { log_event($_SESSION['userdata']['username'],'insert',"Unable to Add Remote $url -- $sql",'ampache-catalog'); } + echo "<span style=\"color: #FOO;\">Error Adding Remote $url </span><br />$sql<br />\n"; + flush(); + } + + } // insert_remote_song + + + /*! + @function check_remote_song + @discussion checks to see if a remote song exists in the database or not + if it find a song it returns the UID + */ + function check_remote_song($url) { + + $url = sql_escape($url); + + $sql = "SELECT id FROM song WHERE file='$url'"; + $db_results = mysql_query($sql, dbh()); + + if ($r = mysql_fetch_object($db_results)) { + return $r->id; + } + + return false; + + } // check_remote_song + + + /*! + @function check_local_mp3 + @discussion Checks the song to see if it's there already returns true if found, false if not + @param $full_file The full file name that we are checking + @param $gather_type=0 If we need to check id3 tags or not + */ + function check_local_mp3($full_file, $gather_type=0) { + + if ($gather_type == 'fast_add') { + $file_date = filemtime($full_file); + if ($file_date < $this->last_add) { + return true; + } + } + + $full_file = sql_escape($full_file); + + $sql = "SELECT id FROM song WHERE file = '$full_file'"; + $db_results = mysql_query($sql, dbh()); + + //If it's found then return true + if (@mysql_fetch_row($db_results)) { + return true; + } + + return false; + + } //check_local_mp3 + + /*! + @function import_m3u + @discussion this takes m3u filename and then attempts + to create a Public Playlist based on the filenames + listed in the m3u + */ + function import_m3u($filename) { + + $m3u_handle = @fopen($filename,'r'); + + $data = @fread($m3u_handle,filesize($filename)); + + $results = explode("\n",$data); + + foreach ($results as $value) { + // Remove extra whitespace + $value = trim($value); + if (preg_match("/\.[A-Za-z0-9]{3}$/",$value)) { + $file[0] = str_replace("/","\\",$value); + $file[1] = str_replace("\\","/",$value); + /* Search for this filename, cause it's a audio file */ + $sql = "SELECT id FROM song WHERE file LIKE '%" . sql_escape($file[0]) . "' OR file LIKE '%" . sql_escape($file[1]) . "'"; + $db_results = mysql_query($sql, dbh()); + $song_id = mysql_result($db_results,'id'); + if ($song_id) { $songs[] = $song_id; } + } // if it's a file + + } // end foreach line + + if (conf('debug')) { log_event($GLOBALS['user']->username,' m3u_parse ',"Parsing $filename - Found: " . count($songs) . " Songs"); } + + if (count($songs)) { + $playlist = new Playlist(); + $playlist_name = "M3U - " . basename($filename); + $playlist->create_playlist($playlist_name,$GLOBALS['user']->id,'public'); + $playlist->add_songs($songs); + return true; + } + + return false; + + } // import_m3u + + /*! + @function delete_catalog + @discussion Deletes the catalog and everything assoicated with it + assumes $this + */ + function delete_catalog() { + + // Do some crazyness to delete all the songs in this catalog + // from playlists... + $sql = "SELECT playlist_data.song FROM song,playlist_data,catalog WHERE catalog.id=song.catalog AND playlist_data.song=song.id AND catalog.id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + $results = array(); + + while ($r = mysql_fetch_object($db_results)) { + $results[] = $r; + } + + foreach ($results as $r) { + // Clear Playlist Data + $sql = "DELETE FROM playlist_data WHERE song='$r->song'"; + $db_results = mysql_query($sql, dbh()); + + } // End Foreach + + // First remove the songs in this catalog + $sql = "DELETE FROM song WHERE catalog = '$this->id'"; + $db_results = mysql_query($sql, dbh()); + + // Next Remove the Catalog Entry it's self + $sql = "DELETE FROM catalog WHERE id = '$this->id'"; + $db_results = mysql_query($sql, dbh()); + + // Run the Aritst/Album Cleaners... + $this->clean_albums(); + $this->clean_artists(); + $this->clean_stats(); + $this->clean_playlists(); + $this->clean_flagged(); + + } // delete_catalog + + + /*! + @function remove_songs + @discussion removes all songs sent in $songs array from the + database, it doesn't actually delete them... + */ + function remove_songs($songs) { + + foreach($songs as $song) { + $sql = "DELETE FROM song WHERE id = '$song'"; + $db_results = mysql_query($sql, dbh()); + } + + } // remove_songs + +} //end of catalog class + +?> diff --git a/modules/class/error.php b/modules/class/error.php new file mode 100644 index 00000000..9283e29d --- /dev/null +++ b/modules/class/error.php @@ -0,0 +1,94 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Error handler requires error_results() function + +*/ + + +class Error { + + //Basic Componets + var $error_state=0; + + /* Generated values */ + var $errors = array(); + + /*! + @function error + @discussion this is the constructor for the error class + */ + function Error() { + + return true; + + } //constructor + + /*! + @function add_error + @discussion adds an error to the static array stored in + error_results() + */ + function add_error($name,$description) { + + $array = array($name=>$description); + + error_results($array); + $this->error_state = 1; + + return true; + + } // add_error + + + /*! + @function has_error + @discussion returns true if the name given has an error, + false if it doesn't + */ + function has_error($name) { + + $results = error_results($name); + + if (!empty($results)) { + return true; + } + + return false; + + } // has_error + + /*! + @function print_error + @discussion prints out the error for a name if it exists + */ + function print_error($name) { + + if ($this->has_error($name)) { + echo "<div class=\"fatalerror\">" . error_results($name) . "</div>\n"; + } + + } // print_error + +} //end error class +?> diff --git a/modules/class/playlist.php b/modules/class/playlist.php new file mode 100644 index 00000000..130537af --- /dev/null +++ b/modules/class/playlist.php @@ -0,0 +1,366 @@ +<? +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +*/ +/*! + @header Playlist Class + This class handles all actual work in regards to playlists. +*/ + +class Playlist { + + // Variables from DB + var $id; + var $name; + var $owner; + var $type; + var $time; + var $items; + + /*! + @function Playlist + @discussion Playlist class + @param $playlist_id The ID of the playlist + */ + function Playlist($playlist_id = 0) { + + /* If we have an id then do something */ + if ($playlist_id) { + // Assign id + $this->id = $playlist_id; + + // Get the information from the db + $this->refresh_object(); + } + + } + + + + /*! + @function refresh_object + @discussion Reads playlist information from the db and updates the Playlist object with it + */ + function refresh_object() { + + $dbh = dbh(); + + if ($this->id) { + $sql = "SELECT name, owner, type, date FROM playlist" . + " WHERE id = '$this->id'"; + $db_results = mysql_query($sql, $dbh); + + if ($r = mysql_fetch_object($db_results)) { + $this->name = $r->name; + $this->owner = $r->owner; + $this->type = $r->type; + $this->time = $r->date; + $this->items = array(); + + // Fetch playlist items + $sql = "SELECT song, track FROM playlist_data" . + " WHERE playlist = '$this->id'" . + " ORDER BY track"; + $db_results = mysql_query($sql, $dbh); + + while ($r = mysql_fetch_object($db_results)) { + $this->items[] = array("song_id" => $r->song, "track" => $r->track); + } + } + + return TRUE; + } + + return FALSE; + + } + + + /*! + @function create_playlist + @discussion Creates an empty playlist, given a name, owner_id, and type. + */ + function create_playlist($name, $owner_id, $type) { + + $dbh = dbh(); + + if (isset($name) && isset($owner_id) && isset($type) && $this->check_type($type)) { + $name = sql_escape($name); + $sql = "INSERT INTO playlist" . + " (name, owner, type)" . + " VALUES ('$name', '$owner_id', '$type')"; + $db_results = mysql_query($sql, $dbh); + if ($this->id = mysql_insert_id($dbh)) { + $this->refresh_object(); + return TRUE; + } + } + + return FALSE; + + } + + + /*! + @function delete + @discussion Deletes the playlist. + */ + function delete() { + + $dbh = dbh(); + + if ($this->id) { + $sql = "DELETE FROM playlist_data" . + " WHERE playlist = '$this->id'"; + $db_results = mysql_query($sql, $dbh); + + $sql = "DELETE FROM playlist" . + " WHERE id = '$this->id'"; + $db_results = mysql_query($sql, $dbh); + + // Clean up this object + foreach (get_object_vars($this) as $var) { + unset($var); + } + + return TRUE; + } + + return FALSE; + + } + + + /*! + @function update_track_numbers + @discussion Reads an array of song_ids and track numbers to update + */ + function update_track_numbers($changes) { + + $dbh = dbh(); + + if ($this->id && isset($changes) && is_array($changes)) { + foreach ($changes as $change) { + // Check for valid song_id + $sql = "SELECT count(*) FROM song WHERE id = '" . $change['song_id'] . "'"; + $db_results = mysql_query($sql, $dbh); + $r = mysql_fetch_row($db_results); + if ($r[0] == 1) { + $sql = "UPDATE playlist_data SET" . + " track = '" . $change['track'] . "'" . + " WHERE playlist = '$this->id'". + " AND song = '" . $change['song_id'] . "'"; + $db_results = mysql_query($sql, $dbh); + } + } + + // Refresh the playlist object + $this->refresh_object(); + + return TRUE; + } + + return FALSE; + + } + + + /*! + @function add_songs + @discussion Reads an array of song_ids to add to the playlist + */ + function add_songs($song_ids) { + + $dbh = dbh(); + + if ($this->id && isset($song_ids) && is_array($song_ids)) { + foreach ($song_ids as $song_id) { + $song = new Song($song_id); + if (isset($song->id)) { + $sql = "INSERT INTO playlist_data" . + " (playlist, song, track)" . + " VALUES ('$this->id', '$song->id', '$song->track')"; + $db_results = mysql_query($sql, $dbh); + } + } + + // Refresh the playlist object + $this->refresh_object(); + + return TRUE; + } + + return FALSE; + + } + + + /*! + @function remove_songs + @discussion Reads an array of song_ids to remove from the playlist + */ + function remove_songs($song_ids) { + + $dbh = dbh(); + + if ($this->id && isset($song_ids) && is_array($song_ids)) { + foreach ($song_ids as $song_id) { + $sql = "DELETE FROM playlist_data" . + " WHERE song = '$song_id'" . + " AND playlist = '$this->id'"; + $db_results = mysql_query($sql, $dbh); + } + + // Refresh the playlist object + $this->refresh_object(); + + return TRUE; + } + + return FALSE; + + } + + + /*! + @function check_type + @discussion Checks for a valid playlist type + */ + function check_type($type) { + + if (isset($type)) { + if ($type === 'public' || $type === 'private') { + return TRUE; + } + } + + return FALSE; + + } + + + /*! + @function update_type + @discussion Updates the playlist type + */ + function update_type($type) { + + $dbh = dbh(); + + if ($this->id && isset($type) && $this->check_type($type)) { + $sql = "UPDATE playlist SET type = '$type'" . + " WHERE id = '$this->id'"; + $db_results = mysql_query($sql, $dbh); + + // Refresh the playlist object + $this->refresh_object(); + + return TRUE; + } + + return FALSE; + + } + + + /*! + @function update_name + @discussion Updates the playlist name + */ + function update_name($name) { + + $dbh = dbh(); + + if ($this->id && isset($name)) { + $name = sql_escape($name); + $sql = "UPDATE playlist SET name = '$name'" . + " WHERE id = '$this->id'"; + $db_results = mysql_query($sql, $dbh); + + // Refresh the playlist object + $this->refresh_object(); + + return TRUE; + } + + return FALSE; + + } + + + /*! + @function get_songs + @discussion Returns an array of song_ids for the playlist + */ + function get_songs() { + + $song_ids = array(); + + if ($this->id && is_array($this->items)) { + foreach ($this->items as $item) { + $song_ids[] = $item['song_id']; + } + } + + return $song_ids; + + } // get_songs + + /*! + @function get_random_songs + @discussion gets a random set of the songs in this + playlist + */ + function get_random_songs() { + + $sql = "SELECT COUNT(song) FROM playlist_data WHERE playlist = '$this->id'"; + $db_results = mysql_query($sql, dbh()); + + $total_songs = mysql_fetch_row($db_results); + + $limit = rand(1,$total_songs[0]); + + // Fetch playlist items + $sql = "SELECT song, track FROM playlist_data" . + " WHERE playlist = '$this->id'" . + " ORDER BY RAND() LIMIT $limit"; + $db_results = mysql_query($sql, dbh()); + while ($r = mysql_fetch_object($db_results)) { + $song_ids[] = $r->song; + } + + return $song_ids; + } // get_random_songs + + /*! + @function show_import + @discussion shows the import from file template + */ + function show_import() { + + require (conf('prefix') . "/templates/show_import_playlist.inc.php"); + + } // show_import + + +} //end of playlist class + +?> diff --git a/modules/class/song.php b/modules/class/song.php new file mode 100644 index 00000000..f42f55a8 --- /dev/null +++ b/modules/class/song.php @@ -0,0 +1,657 @@ +<? +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Song Class +*/ + +class Song { + + /* Variables from DB */ + var $id; + var $file; + var $album; + var $artist; + var $title; + var $year; + var $comment; + var $bitrate; + var $rate; + var $mode; + var $size; + var $time; + var $track; + var $genre; + var $type; + var $mime; + var $played; + var $addition_time; + var $update_time; + + /*! + @function Song + @discussion Song class, for modifing a song. + @param $song_id The ID of the song + */ + function Song($song_id = 0) { + + /* If we have passed an id then do something */ + if ($song_id) { + + /* Assign id for use in get_info() */ + $this->id = $song_id; + + /* Get the information from the db */ + if ($info = $this->get_info()) { + + /* Assign Vars */ + $this->file = $info->file; + $this->album = $info->album; + $this->artist = $info->artist; + $this->title = $info->title; + $this->comment = $info->comment; + $this->year = $info->year; + $this->bitrate = $info->bitrate; + $this->rate = $info->rate; + $this->mode = $info->mode; + $this->size = $info->size; + $this->time = $info->time; + $this->track = $info->track; + $this->genre = $info->genre; + $this->addition_time = $info->addition_time; + $this->catalog = $info->catalog; + $this->played = $info->played; + $this->update_time = $info->update_time; + $this->flagid = $info->flagid; + $this->flaguser = $info->flaguser; + $this->flagtype = $info->flagtype; + $this->flagcomment = $info->flagcomment; + $this->status = $info->status; + + // Format the Type of the song + $this->format_type(); + } + + } + + } //constructor + + + /*! + @function get_info + @discussion get's the vars for $this out of the database + @param $this->id Taken from the object + */ + function get_info() { + + /* Grab the basic information from the catalog and return it */ + $sql = "SELECT song.id,file,catalog,album,song.comment,year,artist,". + "title,bitrate,rate,mode,size,time,track,genre,played,status,update_time,". + "addition_time,flagged.id as flagid,flagged.user as flaguser,flagged.type ". + "as flagtype,flagged.date as flagdate,flagged.comment as flagcomment FROM ". + "song LEFT JOIN flagged ON song.id = flagged.song WHERE song.id = '$this->id'"; + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_object($db_results); + + return $results; + + } //get_info + + /*! + @function format_type + @discussion gets the type of song we are trying to + play, used to set mime headers and to trick + players into playing them correctly + */ + function format_type() { + + preg_match('/\.([A-Za-z0-9]+)$/', $this->file,$results); + + $this->type = $results[1]; + + switch ($this->type) { + case "spx": + case "ogg": + $this->mime = "application/x-ogg"; + break; + case "wma": + case "WMA": + case "asf": + $this->mime = "audio/x-ms-wma"; + break; + case "mp3": + case "mpeg3": + $this->mime = "audio/mpeg"; + break; + case "rm": + $this->mime = "audio/x-realaudio"; + break; + case "flac"; + $this->mime = "audio/x-flac"; + break; + case 'aac': + case 'mp4': + case 'm4a': + $this->mime = "audio/mp4"; + break; + case 'mpc': + $this->mime = "audio/x-musepack"; + break; + default: + $this->mime = "audio/mpeg"; + break; + } + + } // get_type + /*! + @function get_album_songs + @discussion gets an array of song objects based on album + */ + function get_album_songs($album_id) { + + $sql = "SELECT id FROM song WHERE album='$album_id'"; + $db_results = mysql_query($sql, libglue_param(libglue_param('dbh_name'))); + + while ($r = mysql_fetch_object($db_results)) { + $results[] = new Song($r->id); + } + + return $results; + + } // get_album_songs + + /*! + @function get_album_name + @discussion gets the name of $this->album + */ + function get_album_name() { + + $sql = "SELECT name,prefix FROM album WHERE id='$this->album'"; + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_array($db_results); + + if ($results['prefix']) { + return $results['prefix'] . " " .$results['name']; + } + else { + return $results['name']; + } + + } // get_album_name + + /*! + @function get_artist_name + @discussion gets the name of $this->artist + */ + function get_artist_name() { + + $sql = "SELECT name,prefix FROM artist WHERE id='$this->artist'"; + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_array($db_results); + + if ($results['prefix']) { + return $results['prefix'] . " " . $results['name']; + } + else { + return $results['name']; + } + + } // get_album_name + + /*! + @function get_genre_name + @discussion gets the name of the genre + */ + function get_genre_name() { + + $sql = "SELECT name FROM genre WHERE id='$this->genre'"; + $db_results = mysql_query($sql, dbh()); + + $results = mysql_fetch_array($db_results); + + return $results['name']; + + } // get_genre_name + /*! + @function compare_song_information + @discussion this compares the new ID3 tags of a file against + the ones in the database to see if they have changed + it returns false if nothing has changes, or the true + if they have. + @param $song The origional song object + @param $new_song The new version of the song + */ + function compare_song_information($song,$new_song) { + + if ($song->title == "No Title Found") { $song->title = false; } + + + if (trim($song->title) != trim($new_song->title) && strlen($new_song->title) > 0) { + $array['change'] = true; + $array['text'] .= "<br />" . _("Title") . " [$song->title] " . _("updated to") . " [$new_song->title]\n"; + } // if title + if ($song->bitrate != $new_song->bitrate) { + $array['change'] = true; + $array['text'] .= "<br />" . _("Bitrate") . " [$song->bitrate] " . _("updated to") . " [$new_song->bitrate]\n"; + } // if bitrate + if ($song->rate != $new_song->rate) { + $array['change'] = true; + $array['text'] .= "<br />" . _("Rate") . " [$song->rate] " . _("updated to") . " [$new_song->rate]\n"; + } // if rate + if ($song->mode != $new_song->mode) { + $array['change'] = true; + $array['text'] .= "<br />" . _("Mode") . " [$song->mode] " . _("updated to") . " [$new_song->mode]\n"; + } // if mode + if ($song->time != $new_song->time) { + $array['change'] = true; + $array['text'] .= "<br />" . _("Time") . " [$song->time] " . _("updated to") . " [$new_song->time]\n"; + } // if time + if ($song->track != $new_song->track) { + $array['change'] = true; + $array['text'] .= "<br />" . _("Track") . " [$song->track] " . _("updated to") . " [$new_song->track]\n"; + } // if track + if ($song->size != $new_song->size) { + $array['change'] = true; + $array['text'] .= "<br />" . _("Filesize") . " [$song->size] " . _("updated to") . " [$new_song->size]\n"; + } // if artist + if ($song->artist != $new_song->artist) { + $array['change'] = true; + $name = $song->get_artist_name(); + $array['text'] .= "<br />" . _("Artist") . " [$name] " . _("updated to") . " [$new_song->f_artist]\n"; + } // if artist + if ($song->album != $new_song->album) { + $array['change'] = true; + $name = $song->get_album_name() . " - " . $song->year; + $array['text'] .= "<br />" . _("Album") . " [$name] " . _("updated to") . " [$new_song->f_album]\n"; + } // if album + if ($song->year != $new_song->year) { + $array['change'] = true; + $array['text'] .= "<br />" . _("Year") . " [$song->year] " . _("updated to") . " [$new_song->year]\n"; + } // if year + if (trim($song->comment) != trim($new_song->comment)) { + $array['change'] = true; + $array['text'] .= "<br />" . _("Comment") . " [$song->comment] " . _("updated to") . " [$new_song->comment]\n"; + } // if comment + if ($song->genre != $new_song->genre) { + $array['change'] = true; + $name = $song->get_genre_name(); + $array['text'] .= "<br />" . _("Genre") . " [$name] " . _("updated to") . " [$new_song->f_genre]\n"; + } // if genre + + return $array; + + } // compare_song_information + + /*! + @function update_song + @discussion this is the main updater for a song it actually + calls a whole bunch of mini functions to update + each little part of the song... lastly it updates + the "update_time" of the song + @param $song_id The id of the song we are updating + @param $new_song A object with the new song params + */ + function update_song($song_id, $new_song) { + + $this->update_title($new_song->title,$song_id); + $this->update_bitrate($new_song->bitrate,$song_id); + $this->update_rate($new_song->rate,$song_id); + $this->update_mode($new_song->mode,$song_id); + $this->update_size($new_song->size,$song_id); + $this->update_time($new_song->time,$song_id); + $this->update_track($new_song->track,$song_id); + $this->update_artist($new_song->artist,$song_id); + $this->update_genre($new_song->genre,$song_id); + $this->update_album($new_song->album,$song_id); + $this->update_year($new_song->year,$song_id); + $this->update_comment($new_song->comment,$song_id); + $this->update_played('false',$song_id); + $this->update_utime($song_id); + + } // update_song + + /*! + @function update_year + @discussion update the year tag + */ + function update_year($new_year,$song_id=0) { + + if ($_SESSION['userdata']['access'] === 'admin') { + $this->update_item('year',$new_year,$song_id); + } + } // update_year + + /*! + @function update_comment + @discussion updates the comment field + */ + function update_comment($new_comment,$song_id=0) { + + if ($_SESSION['userdata']['access'] === 'admin') { + $this->update_item('comment',$new_comment,$song_id); + } + } // update_comment + + /*! + @function update_title + @discussion updates the title field + */ + function update_title($new_title,$song_id=0) { + + if ($_SESSION['userdata']['access'] === 'admin') { + $this->update_item('title',$new_title,$song_id); + } + } // update_title + + /*! + @function update_bitrate + @discussion updates the bitrate field + */ + function update_bitrate($new_bitrate,$song_id=0) { + + if ($_SESSION['userdata']['access'] === 'admin') { + $this->update_item('bitrate',$new_bitrate,$song_id); + } + + } // update_bitrate + + /*! + @function update_rate + @discussion updates the rate field + */ + function update_rate($new_rate,$song_id=0) { + + if ($_SESSION['userdata']['access'] === 'admin') { + $this->update_item('rate',$new_rate,$song_id); + } + + } // update_rate + + /*! + @function update_mode + @discussion updates the mode field + */ + function update_mode($new_mode,$song_id=0) { + + if ($_SESSION['userdata']['access'] === 'admin') { + $this->update_item('mode',$new_mode,$song_id); + } + + } // update_mode + + /*! + @function update_size + @discussion updates the size field + */ + function update_size($new_size,$song_id=0) { + + if ($_SESSION['userdata']['access'] === 'admin') { + $this->update_item('size',$new_size,$song_id); + } + + } // update_size + + /*! + @function update_time + @discussion updates the time field + */ + function update_time($new_time,$song_id=0) { + + if ($_SESSION['userdata']['access'] === 'admin') { + $this->update_item('time',$new_time,$song_id); + } + + } // update_time + + /*! + @function update_track + @discussion this updates the track field + */ + function update_track($new_track,$song_id=0) { + + if ($_SESSION['userdata']['access'] === 'admin') { + $this->update_item('track',$new_track,$song_id); + } + + } // update_track + + /*! + @function update_artist + @discussion updates the artist field + */ + function update_artist($new_artist,$song_id=0) { + + if ($_SESSION['userdata']['access'] === 'admin') { + $this->update_item('artist',$new_artist,$song_id); + } + + } // update_artist + + /*! + @function update_genre + @discussion updates the genre field + */ + function update_genre($new_genre,$song_id=0) { + + if ($_SESSION['userdata']['access'] === 'admin') { + $this->update_item('genre',$new_genre,$song_id); + } + + } // update_genre + + /*! + @function update_album + @discussion updates the album field + */ + function update_album($new_album,$song_id=0) { + + if ($_SESSION['userdata']['access'] === 'admin') { + $this->update_item('album',$new_album,$song_id); + } + + } // update_album + + /*! + @function update_utime + @discussion sets a new update time + */ + function update_utime($song_id=0,$time=0) { + + if (!$time) { $time = time(); } + + if ($_SESSION['userdata']['access'] === 'admin') { + $this->update_item('update_time',$time,$song_id); + } + + } // update_utime + + /*! + @function update_played + @discussion sets the played flag + */ + function update_played($new_played,$song_id=0) { + + $this->update_item('played',$new_played,$song_id); + + } // update_played + + + /*! + @function update_enabled + @discussion sets the enabled flag + */ + function update_enabled($new_enabled,$song_id=0) { + + if ($_SESSION['userdata']['access'] === 'admin' || $_SESSION['userdata']['access'] === '100') { + $this->update_item('status',$new_enabled,$song_id); + } + + } // update_enabled + + /*! + @function update_item + @discussion this is a generic function that is called + by all the other update functions... + @param $field The field we are updating + @param $value The new value for said field + @param $song_id ID of the song, uses $this->id by default + */ + function update_item($field,$value,$song_id=0) { + + if (!$song_id) { $song_id = $this->id; } + + $value = sql_escape($value); + + $sql = "UPDATE song SET $field='$value' WHERE id='$song_id'"; + $db_results = mysql_query($sql, dbh()); + + $this->{$field} = $value; + + } //update_item + + + /*! + @function format_song + @discussion this takes a song object + and formats it for display + and returns the object cleaned up + */ + function format_song() { + + // Format the filename + preg_match("/^.*\/(.*?)$/",$this->file, $short); + $this->f_file = htmlspecialchars($short[1]); + + // Format the album name + $this->f_album_full = $this->get_album_name(); + $this->f_album = truncate_with_ellipse($this->f_album_full,conf('ellipse_threshold_album')); + + // Format the artist name + $this->f_artist_full = $this->get_artist_name(); + $this->f_artist = truncate_with_ellipse($this->f_artist_full,conf('ellipse_threshold_artist')); + + // Format the title + $this->f_title = truncate_with_ellipse($this->title,conf('ellipse_threshold_title')); + + // Create A link inclduing the title + $this->f_link = "<a href=\"" . conf('web_path') . "/song.php?action=m3u&song=" . $this->id . "\">$this->f_title</a>"; + + // Format the Bitrate + $this->f_bitrate = intval($this->bitrate/1000) . "-" . strtoupper($this->mode); + + // Format Genre + $this->f_genre = $this->get_genre_name(); + + // Format the Time + $min = floor($this->time/60); + $sec = sprintf("%02d", ($this->time%60) ); + $this->f_time = $min . ":" . $sec; + + // Format the size + $this->f_size = sprintf("%.2f",($this->size/1048576)); + + // Set style + if (preg_match("/id3/", $this->flagtype)) { $this->f_style = "style=\"color: #33c;\""; } + elseif (preg_match("/(mp3|del|sort|ren)/", $this->flagtype)) { $this->f_style = "style=\"color: #C00;\""; } + if ($this->status === 'disabled') { $this->f_style = "style=\"text-decoration: line-through;\""; } + + return true; + + } // format_song + + /*! + * @function get_rel_path + * @discussion returns the path of the song file stripped of the catalog path + * used for mpd playback + */ + function get_rel_path($file_path=0,$catalog_id=0) { + + if (!$file_path) { + $info = $this->get_info( ); + $file_path = $info->file; + } + if (!$catalog_id) { + $catalog_id = $info->catalog; + } + $catalog = new Catalog( $catalog_id ); + $info = $catalog->get_info( ); + $catalog_path = $info->path; + return( str_replace( $catalog_path . "/", "", $file_path ) ); + + } // get_rel_path + + + /*! + @function fill_info + @discussion this takes the $results from getid3 and attempts to fill + as much information as possible from the file name using the + pattern set in the current catalog + */ + function fill_info($results,$pattern,$catalog_id,$key) { + + $filename = $this->get_rel_path($results['file'],$catalog_id); + + if (!strlen($results[$key]['title'])) { + $results[$key]['title'] = $this->get_info_from_filename($filename,$pattern,"%t"); + } + if (!strlen($results[$key]['track'])) { + $results[$key]['track'] = $this->get_info_from_filename($filename,$pattern,"%T"); + } + if (!strlen($results[$key]['year'])) { + $results[$key]['year'] = $this->get_info_from_filename($filename,$pattern,"%y"); + } + if (!strlen($results[$key]['album'])) { + $results[$key]['album'] = $this->get_info_from_filename($filename,$pattern,"%A"); + } + if (!strlen($results[$key]['artist'])) { + $results[$key]['artist'] = $this->get_info_from_filename($filename,$pattern,"%a"); + } + if (!strlen($results[$key]['genre'])) { + $results[$key]['genre'] = $this->get_info_from_filename($filename,$pattern,"%g"); + } + + return $results; + + } // fill_info + + /*! + @function get_info_from_filename + @discussion get information from a filename based on pattern + */ + function get_info_from_filename($file,$pattern,$tag) { + + $preg_pattern = preg_replace("/$tag/","(.+)",$pattern); + $preg_pattern = preg_replace("/\%\w/",".+",$preg_pattern); + $preg_pattern = "/" . str_replace("/","\/",$preg_pattern) . "\..+/"; + + preg_match($preg_pattern,$file,$matches); + + return stripslashes($matches[1]); + + } // get_info_from_filename + +} //end of song class + +?> diff --git a/modules/class/stream.php b/modules/class/stream.php new file mode 100644 index 00000000..3472127a --- /dev/null +++ b/modules/class/stream.php @@ -0,0 +1,291 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Stream Class +*/ + +class Stream { + + /* Variables from DB */ + var $type; + var $web_path; + var $songs = array(); + var $sess; + + /*! + @function stream + @discussion constructor for the stream class + */ + function Stream($type='m3u', $song_ids=0) { + + $this->type = $type; + $this->songs = $song_ids; + $this->web_path = conf('web_path'); + + if (conf('force_http_play')) { + $port = conf('http_port'); + $this->web_path = preg_replace("/https/", "http",$this->web_path); + $this->web_path = preg_replace("/:\d+/",":$port",$this->web_path); + } + + $this->sess = session_id(); + $this->user_id = $_SESSION['userdata']['id']; + + } //constructor + + /*! + @function start + @discussion runs this and depending on the type passed it will + call the correct function + */ + function start() { + + $methods = get_class_methods('Stream'); + $create_function = "create_" . $this->type; + if (in_array($create_function,$methods)) { + $this->{$create_function}(); + } + // Assume M3u incase they've pooched the type + else { + $this->create_m3u(); + } + + } // start + + /*! + @function create_simplem3u + @discussion this creates a simple m3u + without any of the extended information + */ + function create_simple_m3u() { + + header("Cache-control: public"); + header("Content-Disposition: filename=playlist.m3u"); + header("Content-Type: audio/x-mpegurl;"); + foreach ($this->songs as $song_id) { + $song = new Song($song_id); + if ($song->type == ".flac") { $song->type = ".ogg"; } + if($GLOBALS['user']->prefs['play_type'] == 'downsample') { + $ds = $GLOBALS['user']->prefs['sample_rate']; + } + echo "$this->web_path/play/index.php?song=$song_id&uid=$this->user_id&sid=$this->sess&ds=$ds&stupidwinamp=." . $song->type . "\n"; + } // end foreach + + } // simple_m3u + + /*! + @function create_m3u + @discussion creates an m3u file + */ + function create_m3u() { + + // Send the client an m3u playlist + header("Cache-control: public"); + header("Content-Disposition: filename=playlist.m3u"); + header("Content-Type: audio/x-mpegurl;"); + echo "#EXTM3U\n"; + foreach($this->songs as $song_id) { + $song = new Song($song_id); + $song->format_song(); + if ($song->type == ".flac") { $song->type = ".ogg"; } + $song_name = $song->f_artist_full . " - " . $song->title . "." . $song->type; + echo "#EXTINF:$song->time,$song_name\n"; + $sess = $_COOKIE[libglue_param('sess_name')]; + if($GLOBALS['user']->prefs['play_type'] == 'downsample') { + $ds = $GLOBALS['user']->prefs['sample_rate']; + } + echo "$this->web_path/play/index.php?song=$song_id&uid=$this->user_id&sid=$this->sess&ds=$ds&name=/" . rawurlencode($song_name) . "\n"; + } // end foreach + + } // create_m3u + + /*! + @function create_pls + @discussion creates a pls file + */ + function create_pls() { + + // Send the client a pls playlist + header("Cache-control: public"); + header("Content-Disposition: filename=playlist.pls"); + header("Content-Type: audio/x-scpls;"); + echo "[Playlist]\n"; + echo "NumberOfEntries=" . count($this->songs) . "\n"; + foreach ($this->songs as $song_id) { + $i++; + $song = new Song($song_id); + $song->format_song(); + if ($song->type == ".flac") { $song->type = ".ogg"; } + $song_name = $song->f_artist_full . " - " . $song->title . "." . $song->type; + if($GLOBALS['user']->prefs['play_type'] == 'downsample') { + $ds = $GLOBALS['user']->prefs['sample_rate']; + } + $song_url = $this->web_path . "/play/index.php?song=$song_id&uid=$this->user_id&sid=$this->sess&ds=$ds&stupidwinamp=." . $song->type; + echo "File" . $i . "=$song_url\n"; + echo "Title" . $i . "=$song_name\n"; + echo "Length" . $i . "=-1\n"; + } // end foreach songs + echo "Version=2\n"; + + } // create_pls + + /*! + @function create_asx + @discussion creates an ASZ playlist (Thx Samir Kuthiala) + */ + function create_asx() { + + header("Cache-control: public"); + header("Content-Disposition: filename=playlist.asx"); + header("Content-Type: video/x-ms-asf;"); + + echo "<ASX version = \"3.0\" BANNERBAR=\"AUTO\">\n"; + echo "<TITLE>Ampache ASX Playlist</TITLE>"; + + foreach ($this->songs as $song_id) { + $song = new Song($song_id); + $song->format_song(); + $song_name = $song->f_artist_full . " - " . $song->title . "." . $song->type; + echo "<ENTRY>\n"; + echo "<TITLE>".$song->f_album_full ." - ". $song->f_artist_full ." - ". $song->title ."</TITLE>\n"; + echo "<AUTHOR>".$song->f_artist_full."</AUTHOR>\n"; + $sess = $_COOKIE[libglue_param('sess_name')]; + if ($GLOBALS['user']->prefs['play_type'] == 'downsample') { + $ds = $GLOBALS['user']->prefs['sample_rate']; + } + echo "<REF HREF = \"". conf('web_path') . "/play/index.php?song=$song_id&uid=$this->user_id&sid=$sess&ds=$ds&name=/" . rawurlencode($song_name) . "\" />\n"; + echo "</ENTRY>\n"; + + } // end foreach + + echo "</ASX>\n"; + + } // create_asx + + /*! + @function create_icecast2 + @discussion pushes an icecast stream + */ + function create_icecast2() { + + echo "ICECAST2<br>\n"; + + // Play the song locally using local play configuration + if (count($this->songs) > 0) { + echo "ICECAST2<br>\n"; + exec("killall ices"); + $filename = conf('icecast_tracklist'); + echo "$filename " . _("Opened for writting") . "<br>\n"; + + /* Open the file for writting */ + if (!$handle = @fopen($filename, "w")) { + log_event($_SESSION['userdata']['username'],"icecast","Fopen: $filename Failed"); + echo _("Error, cannot write") . " $filename<br>\n"; + exit; + } + + /* Foreach through songs */ + foreach($this->songs as $song_id) { + $song = new Song($song_id); + echo "$song->file<br>\n"; + $line = "$song->file\n"; + if (!fwrite($handle, $line)) { + log_event($_SESSION['userdata']['username'],"icecast","Fwrite: Unabled to write $line into $filename"); + echo _("Error, cannot write song in file") . " $song->file --> $filename"; + exit; + } // if write fails + + } // foreach songs + + echo $filename . " " . _("Closed after write") . "<br>\n"; + fclose($handle); + $cmd = conf('icecast_command'); + $cmd = str_replace("%FILE%", $filename, $cmd); + if (conf('debug')) { + log_event($_SESSION['userdata']['username'],"icecast","Exec: $cmd"); + } + exec($cmd); + exit; + + } // if songs + + + } // create_icecast2 + + /*! + @function create_local_play + @discussion pushes out localplay mojo + */ + function create_local_play() { + + foreach($this->songs as $song_id) { + $song = new Song($song_id); + $song->format_song(); + $song_name = $song->f_artist_full . " - " . $song->title . "." . $song->type; + $url = escapeshellarg("$this->web_path/play/?song=$song_id&uid=$this->user_id&sid=$this->sess&name=" . rawurlencode($song_name)); + $localplay_add = conf('localplay_add'); + $localplay_add = str_replace("%URL%", $url, $localplay_add); + if (conf('debug')) { + log_event($_SESSION['userdata']['username'],"localplay","Exec: $localplay_add"); + } + exec($localplay_add); + header("Location: " . conf('web_path') . "/index.php"); + } + + } // create_localplay + + /*! + @function create_mpd + @discussion function that passes information to + MPD + */ + function create_mpd() { + + /* Create the MPD object */ + $myMpd = @new mpd(conf('mpd_host'),conf('mpd_port'),conf('mpd_pass')); + + /* Add the files to the MPD playlist */ + addToPlaylist($myMpd,$this->songs); + + header ("Location: " . return_referer()); + + } // create_mpd + + + /*! + @function create_slim + @discussion this function passes the correct mojo to the slim + class which is in turn passed to the slimserver + */ + function create_slim() { + + + + + + } // create_slim + + +} //end of stream class + +?> diff --git a/modules/class/update.php b/modules/class/update.php new file mode 100644 index 00000000..bd776e70 --- /dev/null +++ b/modules/class/update.php @@ -0,0 +1,880 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/*! + @header Update Class + @discussion this class handles updating from one version of + maintain to the next. Versions are a 6 digit number + 220000 + ^ + Major Revision + + 220000 + ^ + Minor Revision + + The last 4 digits are a build number... + If Minor can't go over 9 Major can go as high as we want +*/ + +class Update { + + var $key; + var $value; + var $versions; // array containing version information + + /*! + @function Update + @discussion Constructor, pulls out information about + the desired key + */ + function Update ( $key=0 ) { + + if ($key) { + $info = $this->get_info(); + $this->key = $key; + $this->value = $info->value; + $this->versions = $this->populate_version(); + } + + } // constructor + + /*! + @function get_info + @discussion gets the information for the zone + */ + function get_info() { + global $conf; + + $sql = "SELECT * FROM update_info WHERE key='$this->key'"; + $db_results = mysql_query($sql, dbh()); + + return mysql_fetch_object($db_results); + + } //get_info + + /*! + @function get_version + @discussion this checks to see what version you are currently running + because we may not have the update_info table we have to check + for it's existance first. + */ + function get_version() { + + + /* Make sure that update_info exits */ + $sql = "SHOW TABLES LIKE 'update_info'"; + $db_results = mysql_query($sql, dbh()); + // If no table + if (!mysql_num_rows($db_results)) { + + $version = '310000'; + + } // if table isn't found + + else { + // If we've found the update_info table, let's get the version from it + $sql = "SELECT * FROM update_info WHERE `key`='db_version'"; + $db_results = mysql_query($sql, dbh()); + $results = mysql_fetch_object($db_results); + $version = $results->value; + } + + return $version; + + } // get_version + + /*! + @function format_version + @discussion make the version number pretty + */ + function format_version($data) { + + $new_version = substr($data,0,strlen($data) - 5) . "." . substr($data,strlen($data)-5,1) . " Build:" . + substr($data,strlen($data)-4,strlen($data)); + + return $new_version; + + } // format_version + + /*! + @function need_update + @discussion checks to see if we need to update + maintain at all + */ + function need_update() { + + $current_version = $this->get_version(); + + if (!is_array($this->versions)) { + $this->versions = $this->populate_version(); + } + + /* + Go through the versions we have and see if + we need to apply any updates + */ + foreach ($this->versions as $update) { + if ($update['version'] > $current_version) { + return true; + } + + } // end foreach version + + return false; + + } // need_update + + + /*! + @function populate_version + @discussion just sets an array the current differences + that require an update + */ + function populate_version() { + + /* Define the array */ + $version = array(); + + /* Version 3.2 Build 0001 */ + $update_string = "- Add update_info table to the database<br />" . + "- Add Now Playing Table<br />" . + "- Add album art columns to album table<br />" . + "- Compleatly Changed Preferences table<br />" . + "- Added Upload table<br />"; + $version[] = array('version' => '320001', 'description' => $update_string); + + $update_string = "- Add back in catalog_type for XML-RPC Mojo<br />" . + "- Add level to access list to allow for play/download/xml-rpc share permissions<br />" . + "- Changed access_list table to allow start-end (so we can set full ip ranges)<br />" . + "- Add default_play to preferences to allow quicktime/localplay/stream<br />" . + "- Switched Artist ID from 10 --> 11 to match other tables<br />"; + $version[] = array('version' => '320002', 'description' => $update_string); + + $update_string = "- Added a last_seen field user table to track users<br />" . + "- Made preferences table key/value based<br />"; + + $version[] = array('version' => '320003', 'description' => $update_string); + + $update_string = "- Added play_type to preferences table<br />" . + "- Removed multicast,downsample,localplay from preferences table<br />" . + "- Dropped old config table which was no longer needed<br />"; + + $version[] = array('version' => '320004', 'description' => $update_string); + + $update_string = "- Added type to preferences to allow for site/user preferences<br />"; + + $version[] = array('version' => '330000', 'description' => $update_string); + + $update_string = "- Added Year to album table<br />" . + "- Increased length of password field in User table<br />"; + + $version[] = array('version' => '330001', 'description' => $update_string); + + $update_string = "- Changed user.access to varchar from enum for more flexibility<br />" . + "- Added catalog.private for future catalog access control<br />" . + "- Added user_catalog table for future catalog access control<br />"; + + + $version[] = array('version' => '330002', 'description' => $update_string); + + $update_string = "- Added user_preferences table to once and for all fix preferences.<br />" . + "- Moved Contents of preferences into new table, and modifies old preferences table.<br />"; + + $version[] = array('version' => '330003', 'description' => $update_string); + + $update_string = "- Changed song comment from varchar255 in order to handle comments longer than 255 chr.<br />" . + "- Added Language and Playlist Type as a per user preference.<br />" . + "- Added Level to Catalog_User table for future use.<br />" . + "- Added gather_types to Catalog table for future use.<br />"; + + + $version[] = array('version' => '330004', 'description' => $update_string); + + $update_string = "- Added Theme config option.<br />"; + + $version[] = array('version' => '331000', 'description' => $update_string); + + $update_string = "- Added Elipse Threshold Preferences.<br />"; + + $version[] = array('version' => '331001', 'description' => $update_string); + + + return $version; + + } // populate_version + + /*! + @function display_update + @discussion This displays a list of the needed + updates to the database. This will actually + echo out the list... + */ + function display_update() { + + $current_version = $this->get_version(); + if (!is_array($this->versions)) { + $this->versions = $this->populate_version(); + } + + echo "<ul>\n"; + + foreach ($this->versions as $version) { + + if ($version['version'] > $current_version) { + $updated = true; + echo "<b>Version: " . $this->format_version($version['version']) . "</b><br />"; + echo $version['description'] . "<br />\n"; + } // if newer + + } // foreach versions + + echo "</ul>\n"; + + if (!$updated) { echo "<p align=\"center\">No Updates Needed [<a href=\"" . conf('web_path') . "\">Return]</a></p>"; } + } // display_update + + /*! + @function run_update + @discussion This function actually updates the db. + it goes through versions and finds the ones + that need to be run. Checking to make sure + the function exists first. + */ + function run_update() { + + /* Nuke All Active session before we start the mojo */ + $sql = "DELETE * FROM session"; + $db_results = mysql_query($sql, dbh()); + + + $methods = array(); + + $current_version = $this->get_version(); + + $methods = get_class_methods('Update'); + + if (!is_array($this->versions)) { + $this->versions = $this->populate_version(); + } + + foreach ($this->versions as $version) { + + + // If it's newer than our current version + // let's see if a function exists and run the + // bugger + if ($version['version'] > $current_version) { + $update_function = "update_" . $version['version']; + if (in_array($update_function,$methods)) { + $this->{$update_function}(); + } + + } + + } // end foreach version + + } // run_update + + /*! + @function set_version + @discussion sets a new version takes + a key and value + */ + function set_version($key,$value) { + + $sql = "UPDATE update_info SET value='$value' WHERE `key`='$key'"; + $db_results = mysql_query($sql, dbh()); + + } //set_version + + /*! + @function update_320001 + @discussion Migration function for 3.2 Build 0001 + */ + function update_320001() { + + // Add the update_info table to the database + $sql = "CREATE TABLE `update_info` (`key` VARCHAR( 128 ) NOT NULL ,`value` VARCHAR( 255 ) NOT NULL ,INDEX ( `key` ) )"; + $db_results = mysql_query($sql, dbh()); + + // Insert the first version info + $sql = "INSERT INTO update_info (`key`,`value`) VALUES ('db_version','320001')"; + $db_results = mysql_query($sql, dbh()); + + // Add now_playing table to database + $sql = "CREATE TABLE now_playing (" . + "id int(11) unsigned NOT NULL auto_increment, " . + "song_id int(11) unsigned NOT NULL default '0', " . + "user_id int(11) unsigned default NULL, " . + "start_time int(11) unsigned NOT NULL default '0', " . + "PRIMARY KEY (id) " . + ") TYPE=MyISAM"; + $db_results = mysql_query($sql, dbh()); + + // Add the upload table to the database + $sql = "CREATE TABLE upload ( id int(11) unsigned NOT NULL auto_increment, `user` int(11) unsigned NOT NULL," . + "`file` varchar(255) NOT NULL , `comment` varchar(255) NOT NULL , action enum('add','quarantine','delete') NOT NULL default 'quarantine', " . + "addition_time int(11) unsigned default '0', PRIMARY KEY (id), KEY action (`action`), KEY user (`user`) )"; + $db_results = mysql_query($sql, dbh()); + + /* + Ok we need to compleatly tweak the preferences table + first things first, nuke the damn thing so we can + setup our new mojo + */ + $sql = "DROP TABLE `preferences`"; + $db_results = mysql_query($sql, dbh()); + + $sql = "CREATE TABLE `preferences` (`id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT , `user` INT( 11 ) UNSIGNED NOT NULL ," . + "`download` ENUM( 'true', 'false' ) DEFAULT 'false' NOT NULL , `upload` ENUM( 'disabled', 'html', 'gui' ) DEFAULT 'disabled' NOT NULL ," . + "`downsample` ENUM( 'true', 'false' ) DEFAULT 'false' NOT NULL , `local_play` ENUM( 'true', 'false' ) DEFAULT 'false' NOT NULL ," . + "`multicast` ENUM( 'true', 'false' ) DEFAULT 'false' NOT NULL , `quarantine` ENUM( 'true', 'false' ) DEFAULT 'true' NOT NULL ," . + "`popular_threshold` INT( 11 ) UNSIGNED DEFAULT '10' NOT NULL , `font` VARCHAR( 255 ) DEFAULT 'Verdana, Helvetica, sans-serif' NOT NULL ," . + "`bg_color1` VARCHAR( 32 ) DEFAULT '#ffffff' NOT NULL , `bg_color2` VARCHAR( 32 ) DEFAULT '#000000' NOT NULL , `base_color1` VARCHAR( 32 ) DEFAULT '#bbbbbb' NOT NULL , " . + "`base_color2` VARCHAR( 32 ) DEFAULT '#dddddd' NOT NULL , `font_color1` VARCHAR( 32 ) DEFAULT '#222222' NOT NULL , " . + "`font_color2` VARCHAR( 32 ) DEFAULT '#000000' NOT NULL , `font_color3` VARCHAR( 32 ) DEFAULT '#ffffff' NOT NULL , " . + "`row_color1` VARCHAR( 32 ) DEFAULT '#cccccc' NOT NULL , `row_color2` VARCHAR( 32 ) DEFAULT '#bbbbbb' NOT NULL , " . + "`row_color3` VARCHAR( 32 ) DEFAULT '#dddddd' NOT NULL , `error_color` VARCHAR( 32 ) DEFAULT '#990033' NOT NULL , " . + "`font_size` INT( 11 ) UNSIGNED DEFAULT '10' NOT NULL , `upload_dir` VARCHAR( 255 ) NOT NULL , " . + "`sample_rate` INT( 11 ) UNSIGNED DEFAULT '32' NOT NULL , PRIMARY KEY ( `id` ), KEY user (`user`) )"; + $db_results = mysql_query($sql, dbh()); + + $sql = "INSERT INTO preferences (`user`,`font_size`) VALUES ('0','12')"; + $db_results = mysql_query($sql, dbh()); + + // Now we need to give everyone some preferences + $sql = "SELECT * FROM user"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $users[] = $r; + } + + foreach ($users as $user) { + $sql = "INSERT INTO preferences (`user`) VALUES ('$user->id')"; + $db_results = mysql_query($sql, dbh()); + } + + // Add album art columns to album table + $sql = "ALTER TABLE album ADD art MEDIUMBLOB, ADD art_mime VARCHAR(128)"; + $db_result = mysql_query($sql, dbh()); + + } // update_320001 + + /*! + @function update_320002 + @discussion update to alpha 2 + */ + function update_320002() { + + /* Add catalog_type back in for XML-RPC */ + $sql = "ALTER TABLE `catalog` ADD `catalog_type` ENUM( 'local', 'remote' ) DEFAULT 'local' NOT NULL AFTER `path`"; + $db_results = mysql_query($sql, dbh()); + + /* Add default_play to pick between stream/localplay/quicktime */ + $sql = "ALTER TABLE `preferences` ADD `default_play` VARCHAR( 128 ) DEFAULT 'stream' NOT NULL AFTER `popular_threshold`"; + $db_results = mysql_query($sql, dbh()); + + /* Should be INT(11) Why not eah? */ + $sql = "ALTER TABLE `artist` CHANGE `id` `id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT"; + $db_results = mysql_query($sql, dbh()); + + /* Add level to access_list so we can limit playback/download/xml-rpc share */ + $sql = "ALTER TABLE `access_list` ADD `level` SMALLINT( 3 ) UNSIGNED DEFAULT '5' NOT NULL"; + $db_results = mysql_query($sql, dbh()); + + /* Shouldn't be zero fill... not needed */ + $sql = "ALTER TABLE `user` CHANGE `offset_limit` `offset_limit` INT( 5 ) UNSIGNED DEFAULT '00050' NOT NULL"; + $db_results = mysql_query($sql, dbh()); + + /* Let's knock it up a notch 11.. BAM */ + $sql = "ALTER TABLE `user` CHANGE `id` `id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT"; + $db_results = mysql_query($sql, dbh()); + + /* Change IP --> Start */ + $sql = "ALTER TABLE `access_list` CHANGE `ip` `start` INT( 11 ) UNSIGNED NOT NULL"; + $db_results = mysql_query($sql, dbh()); + + /* Add End */ + $sql = "ALTER TABLE `access_list` ADD `end` INT( 11 ) UNSIGNED NOT NULL AFTER `start`"; + $db_results = mysql_query($sql, dbh()); + + /* Update Version */ + $this->set_version('db_version', '320002'); + + } // update_320002 + + + /*! + @function update_320003 + @discussion updates to the alpha 3 of 3.2 + */ + function update_320003() { + + /* Add last_seen to user table */ + $sql = "ALTER TABLE `user` ADD `last_seen` INT( 11 ) UNSIGNED NOT NULL"; + $db_results = mysql_query($sql, dbh()); + + /* + Load the preferences table into an array + so we can migrate it to the new format + */ + $sql = "SELECT * FROM preferences"; + $db_results = mysql_query($sql, dbh()); + + $results = array(); + + while ($r = mysql_fetch_object($db_results)) { + $results[$r->user]['download'] = $r->download; + $results[$r->user]['upload'] = $r->upload; + $results[$r->user]['downsample'] = $r->downsample; + $results[$r->user]['local_play'] = $r->local_play; + $results[$r->user]['multicast'] = $r->multicast; + $results[$r->user]['quarantine'] = $r->quarantine; + $results[$r->user]['popular_threshold'] = $r->popular_threshold; + $results[$r->user]['default_play'] = $r->default_play; + $results[$r->user]['font'] = $r->font; + $results[$r->user]['bg_color1'] = $r->bg_color1; + $results[$r->user]['bg_color2'] = $r->bg_color2; + $results[$r->user]['base_color1'] = $r->base_color1; + $results[$r->user]['base_color2'] = $r->base_color2; + $results[$r->user]['font_color1'] = $r->font_color1; + $results[$r->user]['font_color2'] = $r->font_color2; + $results[$r->user]['font_color3'] = $r->font_color3; + $results[$r->user]['row_color1'] = $r->row_color1; + $results[$r->user]['row_color2'] = $r->row_color2; + $results[$r->user]['row_color3'] = $r->row_color3; + $results[$r->user]['error_color'] = $r->error_color; + $results[$r->user]['font_size'] = $r->font_size; + $results[$r->user]['upload_dir'] = $r->upload_dir; + $results[$r->user]['sample_rate'] = $r->sample_rate; + + } // while preferences + + /* Drop the preferences table so we can start over */ + $sql = "DROP TABLE `preferences`"; + $db_results = mysql_query($sql, dbh()) or die('Query failed: ' . mysql_error()); + + /* Create the new preferences table */ + $sql = "CREATE TABLE `preferences` (`key` VARCHAR( 255 ) NOT NULL , `value` VARCHAR( 255 ) NOT NULL , `user` INT( 11 ) UNSIGNED NOT NULL)"; + $db_results = mysql_query($sql, dbh()); + + $sql = "ALTER TABLE `preferences` ADD INDEX ( `key` )"; + $db_results = mysql_query($sql, dbh()); + + $sql = "ALTER TABLE `preferences` ADD INDEX ( `user` )"; + $db_results = mysql_query($sql, dbh()); + + + $user = new User(); + + /* Populate the mofo! */ + foreach ($results as $key => $data) { + + $user->add_preference('download',$results[$key]['download'],$key); + $user->add_preference('upload',$results[$key]['upload'], $key); + $user->add_preference('downsample',$results[$key]['downsample'], $key); + $user->add_preference('local_play', $results[$key]['local_play'], $key); + $user->add_preference('multicast', $results[$key]['multicast'], $key); + $user->add_preference('quarantine', $results[$key]['quarantine'], $key); + $user->add_preference('popular_threshold',$results[$key]['popular_threshold'], $key); + $user->add_preference('font', $results[$key]['font'], $key); + $user->add_preference('bg_color1',$results[$key]['bg_color1'], $key); + $user->add_preference('bg_color2',$results[$key]['bg_color2'], $key); + $user->add_preference('base_color1',$results[$key]['base_color1'], $key); + $user->add_preference('base_color2',$results[$key]['base_color2'], $key); + $user->add_preference('font_color1',$results[$key]['font_color1'], $key); + $user->add_preference('font_color2',$results[$key]['font_color2'], $key); + $user->add_preference('font_color3',$results[$key]['font_color3'], $key); + $user->add_preference('row_color1',$results[$key]['row_color1'], $key); + $user->add_preference('row_color2',$results[$key]['row_color2'], $key); + $user->add_preference('row_color3',$results[$key]['row_color3'], $key); + $user->add_preference('error_color', $results[$key]['error_color'], $key); + $user->add_preference('font_size', $results[$key]['font_size'], $key); + $user->add_preference('upload_dir', $results[$key]['upload_dir'], $key); + $user->add_preference('sample_rate', $results[$key]['sample_rate'], $key); + + } // foreach preferences + + /* Update Version */ + $this->set_version('db_version', '320003'); + + } // update_320003 + + /*! + @function update_320004 + @discussion updates to the 320004 + version of the db + */ + function update_320004() { + + $results = array(); + + $sql = "SELECT * FROM preferences WHERE `key`='local_play' AND `value`='true'"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $results[$r->user] = 'local_play'; + } + + $sql = "SELECT * FROM preferences WHERE `key`='downsample' AND `value`='true'"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $results[$r->user] = 'downsample'; + } + + $sql = "SELECT * FROM preferences WHERE `key`='multicast' AND `value`='true'"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $results[$r->user] = 'multicast'; + } + + $sql = "SELECT DISTINCT(user) FROM preferences"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + if (!isset($results[$r->user])) { + $results[$r->user] = 'normal'; + } + } + + foreach ($results as $key => $value) { + $sql = "INSERT INTO preferences (`key`,`value`,`user`) VALUES ('play_type','$value','$key')"; + $db_results = mysql_query($sql, dbh()); + } + + $sql = "DELETE FROM preferences WHERE `key`='downsample'"; + $db_results = mysql_query($sql, dbh()); + + $sql = "DELETE FROM preferences WHERE `key`='local_play'"; + $db_results = mysql_query($sql, dbh()); + + $sql = "DELETE FROM preferences WHERE `key`='multicast'"; + $db_results = mysql_query($sql, dbh()); + + $sql = "DROP TABLE `config`"; + $db_results = mysql_query($sql, dbh()); + + /* Update Version */ + $this->set_version('db_version', '320004'); + + } // update_320004 + + /*! + @function update_330000 + @discussion updates to 3.3 Build 0 + */ + function update_330000() { + + /* Add Type to preferences */ + $sql = "ALTER TABLE `preferences` ADD `type` VARCHAR( 128 ) NOT NULL"; + $db_results = mysql_query($sql, dbh()); + + /* Set Type on current preferences */ + $sql = "UPDATE `preferences` SET type='user'"; + $db_results = mysql_query($sql, dbh()); + + /* Add New Preferences */ + $new_prefs[] = array('key' => 'local_length', 'value' => libglue_param('local_length')); + $new_prefs[] = array('key' => 'site_title', 'value' => conf('site_title')); + $new_prefs[] = array('key' => 'access_control', 'value' => conf('access_control')); + $new_prefs[] = array('key' => 'xml_rpc', 'value' => conf('xml_rpc')); + $new_prefs[] = array('key' => 'lock_songs', 'value' => conf('lock_songs')); + $new_prefs[] = array('key' => 'force_http_play', 'value' => conf('force_http_play')); + $new_prefs[] = array('key' => 'http_port', 'value' => conf('http_port')); + $new_prefs[] = array('key' => 'do_mp3_md5', 'value' => conf('do_mp3_md5')); + $new_prefs[] = array('key' => 'catalog_echo_count', 'value' => conf('catalog_echo_count')); + $new_prefs[] = array('key' => 'no_symlinks', 'value' => conf('no_symlinks')); + $new_prefs[] = array('key' => 'album_cache_limit', 'value' => conf('album_cache_limit')); + $new_prefs[] = array('key' => 'artist_cache_limit', 'value' => conf('artist_cache_limit')); + $new_prefs[] = array('key' => 'memory_limit', 'value' => conf('memory_limit')); + $new_prefs[] = array('key' => 'refresh_limit', 'value' => conf('refresh_interval')); + + foreach ($new_prefs as $pref) { + $sql = "INSERT INTO `preferences` (`key`,`value`,`type`) VALUES ('".$pref['key']."','".$pref['value']."','system')"; + $db_results = mysql_query($sql, dbh()); + } + + + /* Update Version */ + $this->set_version('db_version','330000'); + + + } // update_330000 + + + /*! + @function update_330001 + @discussion adds year to album and tweaks + the password field in session + */ + function update_330001() { + + /* Add Year to Album Table */ + $sql = "ALTER TABLE `album` ADD `year` INT( 4 ) UNSIGNED NOT NULL AFTER `prefix`"; + $db_results = mysql_query($sql, dbh()); + + /* Alter Password Field */ + $sql = "ALTER TABLE `user` CHANGE `password` `password` VARCHAR( 64 ) NOT NULL"; + $db_results = mysql_query($sql, dbh()); + + /* Update Version */ + $this->set_version('db_version', '330001'); + + } // update_330001 + + /*! + @function update_330002 + @discussion changes user.access from enum to a + varchr field + */ + function update_330002() { + + /* Alter user table */ + $sql = "ALTER TABLE `user` CHANGE `access` `access` VARCHAR( 64 ) NOT NULL"; + $db_results = mysql_query($sql, dbh()); + + /* Add private option to catalog */ + $sql = "ALTER TABLE `catalog` ADD `private` INT( 1 ) UNSIGNED DEFAULT '0' NOT NULL AFTER `enabled`"; + $db_results = mysql_query($sql, dbh()); + + /* Add new user_catalog table */ + $sql = "CREATE TABLE `user_catalog` ( `user` INT( 11 ) UNSIGNED NOT NULL , `catalog` INT( 11 ) UNSIGNED NOT NULL )"; + $db_results = mysql_query($sql, dbh()); + + /* Update Version */ + $this->set_version('db_version', '330002'); + + } // update_330002 + + /*! + @function update_330003 + @discussion adds user_preference and modifies the + existing preferences table + */ + function update_330003() { + + /* Add new user_preference table */ + $sql = "CREATE TABLE `user_preference` ( `user` INT( 11 ) UNSIGNED NOT NULL , `preference` INT( 11 ) UNSIGNED NOT NULL, `value` VARCHAR( 255 ) NOT NULL )"; + $db_results = mysql_query($sql, dbh()); + + /* Add indexes */ + $sql = "ALTER TABLE `user_preference` ADD INDEX ( `user` )"; + $db_results = mysql_query($sql, dbh()); + + $sql = "ALTER TABLE `user_preference` ADD INDEX ( `preference` )"; + $db_results = mysql_query($sql, dbh()); + + /* Pull and store all preference information */ + $sql = "SELECT * FROM preferences"; + $db_results = mysql_query($sql, dbh()); + + $results = array(); + + while ($r = mysql_fetch_object($db_results)) { + $results[] = $r; + } + + + /* Re-combobulate preferences table */ + + /* Drop the preferences table so we can start over */ + $sql = "DROP TABLE `preferences`"; + $db_results = mysql_query($sql, dbh()) or die('Query failed: ' . mysql_error()); + + /* Insert new preference table */ + $sql = "CREATE TABLE `preferences` ( `id` INT ( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT, `name` VARCHAR ( 128 ) NOT NULL, `value` VARCHAR ( 255 ) NOT NULL," . + " `description` VARCHAR ( 255 ) NOT NULL, `level` INT ( 11 ) UNSIGNED NOT NULL DEFAULT '100', `type` VARCHAR ( 128 ) NOT NULL, `locked` SMALLINT ( 1 ) NOT NULL Default '1'" . + ", PRIMARY KEY ( `id` ) )"; + $db_results = mysql_query($sql, dbh()) or die("Query failed: " . mysql_error()); + + /* Create Array of Preferences */ + $new_prefs = array(); + + $new_prefs[] = array('name' => 'download', 'value' => '0', 'description' => 'Allow Downloads', 'level' => '100', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'upload', 'value' => '0', 'description' => 'Allow Uploads', 'level' => '100', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'quarantine', 'value' => '1', 'description' => 'Quarantine All Uploads', 'level' => '100', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'popular_threshold', 'value' => '10', 'description' => 'Popular Threshold', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'font', 'value' => 'Verdana, Helvetica, sans-serif', 'description' => 'Interface Font', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'bg_color1', 'value' => '#ffffff', 'description' => 'Background Color 1', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'bg_color2', 'value' => '#000000', 'description' => 'Background Color 2', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'base_color1', 'value' => '#bbbbbb', 'description' => 'Base Color 1', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'base_color2', 'value' => '#dddddd', 'description' => 'Base Color 2', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'font_color1', 'value' => '#222222', 'description' => 'Font Color 1', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'font_color2', 'value' => '#000000', 'description' => 'Font Color 2', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'font_color3', 'value' => '#ffffff', 'description' => 'Font Color 3', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'row_color1', 'value' => '#cccccc', 'description' => 'Row Color 1', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'row_color2', 'value' => '#bbbbbb', 'description' => 'Row Color 2', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'row_color3', 'value' => '#dddddd', 'description' => 'Row Color 3', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'error_color', 'value' => '#990033', 'description' => 'Error Color', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'font_size', 'value' => '10', 'description' => 'Font Size', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'upload_dir', 'value' => '/tmp', 'description' => 'Upload Directory', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'sample_rate', 'value' => '32', 'description' => 'Downsample Bitrate', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'refresh_limit', 'value' => '0', 'description' => 'Refresh Rate for Homepage', 'level' => '100', 'locked' => '0', 'type' => 'system'); + $new_prefs[] = array('name' => 'local_length', 'value' => '900', 'description' => 'Session Expire in Seconds', 'level' => '100', 'locked' => '0', 'type' => 'system'); + $new_prefs[] = array('name' => 'site_title', 'value' => 'For The Love of Music', 'description' => 'Website Title', 'level' => '100', 'locked' => '0', 'type' => 'system'); + $new_prefs[] = array('name' => 'lock_songs', 'value' => '0', 'description' => 'Lock Songs', 'level' => '100', 'locked' => '1', 'type' => 'system'); + $new_prefs[] = array('name' => 'force_http_play', 'value' => '1', 'description' => 'Forces Http play regardless of port', 'level' => '100', 'locked' => '1', 'type' => 'system'); + $new_prefs[] = array('name' => 'http_port', 'value' => '80', 'description' => 'Non-Standard Http Port', 'level' => '100', 'locked' => '1', 'type' => 'system'); + $new_prefs[] = array('name' => 'catalog_echo_count', 'value' => '100', 'description' => 'Catalog Echo Interval', 'level' => '100', 'locked' => '0', 'type' => 'system'); + $new_prefs[] = array('name' => 'no_symlinks', 'value' => '0', 'description' => 'Don\'t Follow Symlinks', 'level' => '100', 'locked' => '0', 'type' => 'system'); + $new_prefs[] = array('name' => 'album_cache_limit', 'value' => '25', 'description' => 'Album Cache Limit', 'level' => '100', 'locked' => '0', 'type' => 'system'); + $new_prefs[] = array('name' => 'artist_cache_limit', 'value' => '50', 'description' => 'Artist Cache Limit', 'level' => '100', 'locked' => '0', 'type' => 'system'); + $new_prefs[] = array('name' => 'play_type', 'value' => 'stream', 'description' => 'Type of Playback', 'level' => '25', 'locked' => '0', 'type' => 'user'); + $new_prefs[] = array('name' => 'direct_link', 'value' => '1', 'description' => 'Allow Direct Links', 'level' => '100', 'locked' => '0', 'type' => 'user'); + + foreach ($new_prefs as $prefs) { + + $sql = "INSERT INTO preferences (`name`,`value`,`description`,`level`,`locked`,`type`) VALUES ('" . $prefs['name'] . "','" . $prefs['value'] ."','". $prefs['description'] ."','" . $prefs['level'] ."','". $prefs['locked'] ."','" . $prefs['type'] . "')"; + $db_results = mysql_query($sql, dbh()); + + } // foreach prefs + + + /* Re-insert Data into preferences table */ + + $user = new User(); + $users = array(); + + foreach ($results as $old_pref) { + // This makes sure that true/false yes no get turned into 0/1 + $temp_array = fix_preferences(array('old' => $old_pref->value)); + $old_pref->value = $temp_array['old']; + $user->add_preference($old_pref->key,$old_pref->value,$old_pref->user); + $users[$old_pref->user] = 1; + } // end foreach old preferences + + /* Fix missing preferences */ + foreach ($users as $userid => $data) { + $user->fix_preferences($userid); + } // end foreach user + + /* Update Version */ + $this->set_version('db_version', '330003'); + + } // update_330003 + + /*! + @function update_330004 + @discussion changes comment from varchar to text + and also adds a few preferences options and + adds the per db art functions + */ + function update_330004() { + + /* Change comment field in song */ + $sql = "ALTER TABLE `song` CHANGE `comment` `comment` TEXT NOT NULL"; + $db_results = mysql_query($sql, dbh()); + + /* Add Extra Preferences */ + $sql = "INSERT INTO `preferences` ( `id` , `name` , `value` , `description` , `level` , `type` , `locked` ) VALUES ('', 'lang', 'en_US', 'Language', '100', 'user', '0')"; + $db_results = mysql_query($sql, dbh()); + + $sql = "INSERT INTO `preferences` ( `id` , `name` , `value` , `description` , `level` , `type` , `locked` ) VALUES ('', 'playlist_type','m3u','Playlist Type','100','user','0')"; + $db_results = mysql_query($sql, dbh()); + + /* Add Gathertype to Catalog for future use */ + $sql = "ALTER TABLE `catalog` ADD `gather_types` VARCHAR( 255 ) NOT NULL AFTER `sort_pattern`"; + $db_results = mysql_query($sql, dbh()); + + /* Add level to user_catalog for future use */ + $sql = "ALTER TABLE `user_catalog` ADD `level` SMALLINT( 3 ) DEFAULT '25' NOT NULL AFTER `catalog`"; + $db_results = mysql_query($sql, dbh()); + + /* Fix existing preferences */ + $sql = "SELECT id FROM user"; + $db_results = mysql_query($sql, dbh()); + + $user = new User(0); + + while ($results = mysql_fetch_array($db_results)) { + $user->fix_preferences($results[0]); + } + + /* Update Version */ + $this->set_version('db_version', '330004'); + + } // update_330004 + + /*! + @function update_331000 + @discussion this updates is for 3.3.1 it adds + the theme preference. + */ + function update_331000() { + + + /* Add new preference */ + $sql = "INSERT INTO `preferences` (`id`,`name`,`value`,`description`,`level`,`type`,`locked`) VALUES ('','theme_name','classic','Theme','0','user','0')"; + $db_results = mysql_query($sql, dbh()); + + /* Fix existing preferecnes */ + $sql = "SELECT DISTINCT(user) FROM user_preference"; + $db_results = mysql_query($sql, dbh()); + + $user = new User(0); + + while ($results = mysql_fetch_array($db_results)) { + $user->fix_preferences($results[0]); + } + + /* Update Version */ + $this->set_version('db_version','331000'); + + } // update_331000 + + /*! + @function update_331001 + @discussion this adds a few more user preferences + */ + function update_331001() { + + /* Add new preference */ + $sql = "INSERT INTO `preferences` (`id`,`name`,`value`,`description`,`level`,`type`,`locked`) VALUES ('','ellipse_threshold_album','27','Album Ellipse Threshold','0','user','0')"; + $db_results = mysql_query($sql, dbh()); + + $sql = "INSERT INTO `preferences` (`id`,`name`,`value`,`description`,`level`,`type`,`locked`) VALUES ('','ellipse_threshold_artist','27','Artist Ellipse Threshold','0','user','0')"; + $db_results = mysql_query($sql, dbh()); + + $sql = "INSERT INTO `preferences` (`id`,`name`,`value`,`description`,`level`,`type`,`locked`) VALUES ('','ellipse_threshold_title','27','Title Ellipse Threshold','0','user','0')"; + $db_results = mysql_query($sql, dbh()); + + /* Fix existing preferecnes */ + $sql = "SELECT DISTINCT(user) FROM user_preference"; + $db_results = mysql_query($sql, dbh()); + + $user = new User(0); + + while ($results = mysql_fetch_array($db_results)) { + $user->fix_preferences($results[0]); + } + + /* Update Version */ + $this->set_version('db_version','331001'); + + } // update_331001 + +} // end update class + +?> diff --git a/modules/class/user.php b/modules/class/user.php new file mode 100644 index 00000000..e5c2771b --- /dev/null +++ b/modules/class/user.php @@ -0,0 +1,604 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/*! + @header User Object + View object that is thrown into their session + +*/ + + +class User { + + //Basic Componets + var $username; + var $id=0; + var $fullname; + var $access; + var $offset_limit=25; + var $email; + var $last_seen; + + function User($username=0,$uid=0) { + + if (!$username && !$uid) { + return true; + } + + $this->username = $username; + $this->id = $uid; + $info = $this->get_info(); + $this->username = $info->username; + $this->id = $info->id; + $this->id = $info->id; + $this->fullname = $info->fullname; + $this->access = $info->access; + $this->offset_limit = $info->offset_limit; + $this->email = $info->email; + $this->last_seen = $info->last_seen; + $this->set_preferences(); + + // Make sure the Full name is always filled + if (strlen($this->fullname) < 1) { $this->fullname = $this->username; } + + } // User + + + /*! + @function get_info + @dicussion gets the info! + */ + function get_info() { + + if ($this->username) { + $sql = "SELECT * FROM user WHERE username='$this->username'"; + } + else { + $sql = "SELECT * FROM user WHERE id='$this->id'"; + } + $db_results = mysql_query($sql, dbh()); + + return mysql_fetch_object($db_results); + + } // get_info + + /*! + @function get_preferences + @discussion gets the prefs for this specific + user and returns them as an array + */ + function get_preferences() { + + $sql = "SELECT preferences.name, preferences.description, preferences.type, user_preference.value FROM preferences,user_preference WHERE user_preference.user='$this->id' AND user_preference.preference=preferences.id AND preferences.type='user'"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $results[] = $r; + } + + return $results; + + } // get_preferences + + /*! + @function set_preferences + @discussion sets the prefs for this specific + user + */ + function set_preferences() { + + $sql = "SELECT preferences.name,user_preference.value FROM preferences,user_preference WHERE user_preference.user='$this->id' " . + "AND user_preference.preference=preferences.id AND preferences.type='user'"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + $this->prefs[$r->name] = $r->value; + } + } // get_preferences + + /*! + @function get_favorites + @discussion returns an array of your $type + favorites + */ + function get_favorites($type) { + + $sql = "SELECT * FROM object_count" . + " WHERE count > 0" . + " AND object_type = '$type'" . + " AND userid = '" . $this->id . "'" . + " ORDER BY count DESC LIMIT " . conf('popular_threshold'); + $db_result = mysql_query($sql, dbh()); + + $items = array(); + $web_path = conf('web_path'); + + while ($r = @mysql_fetch_object($db_result) ) { + /* If its a song */ + if ($type == 'song') { + $data = new Song($r->object_id); + $data->count = $r->count; + $data->format_song(); + $data->f_name = $data->f_link; + $items[] = $data; + } + /* If its an album */ + elseif ($type == 'album') { + $data = new Album($r->object_id); + $data->count = $r->count; + $data->format_album(); + $items[] = $data; + } + /* If its an artist */ + elseif ($type == 'artist') { + $data = new Artist($r->object_id); + $data->count = $r->count; + $data->format_artist(); + $data->f_name = $data->link; + $items[] = $data; + } + + } // end while + + return $items; + + } // get_favorites + + /*! + @function is_xmlrpc + @discussion checks to see if this is a valid + xmlrpc user + */ + function is_xmlrpc() { + + /* If we aren't using XML-RPC return true */ + if (!conf('xml_rpc')) { + return false; + } + + //FIXME: Ok really what we will do is check the MD5 of the HTTP_REFERER + //FIXME: combined with the song title to make sure that the REFERER + //FIXME: is in the access list with full rights + return true; + + } // is_xmlrpc + + /*! + @function is_logged_in + @discussion checks to see if $this user is logged in + */ + function is_logged_in() { + + $sql = "SELECT id FROM session WHERE username='$this->id'" . + " AND expire > ". time(); + $db_results = mysql_query($sql,dbh()); + + if (mysql_num_rows($db_results)) { + return true; + } + + return false; + + } // is_logged_in + + /*! + @function has_access + @discussion this function checkes to see if this user has access + to the passed action (pass a level requirement) + */ + function has_access($needed_level) { + + if ($this->access == "admin") { $level = 100; } + elseif ($this->access == "user") { $level = 25; } + else { $level = $this->access; } + + if (!conf('use_auth') || conf('demo_mode')) { return true; } + + if ($level >= $needed_level) { return true; } + + return false; + + } // has_access + + /*! + @function update_preference + @discussion updates a single preference if the query fails + it attempts to insert the preference instead + */ + function update_preference($preference_id, $value, $id=0) { + + if (!$id) { + $id = $this->id; + } + + $value = sql_escape($value); + //FIXME: + // Do a has_access check here... + + $sql = "UPDATE user_preference SET value='$value' WHERE user='$id' AND preference='$preference_id'"; + $db_results = @mysql_query($sql, dbh()); + + } // update_preference + + /*! + @function add_preference + @discussion adds a new preference + @param $key preference name + @param $value preference value + @param $id user is + */ + function add_preference($preference_id, $value, $id=0) { + + if (!$id) { + $id = $this->id; + } + + $value = sql_escape($value); + + if (!is_numeric($preference_id)) { + $sql = "SELECT id FROM preferences WHERE `name`='$preference_id'"; + $db_results = mysql_query($sql, dbh()); + $r = mysql_fetch_array($db_results); + $preference_id = $r[0]; + } // end if it's not numeric + + $sql = "INSERT user_preference SET `user`='$id' , `value`='$value' , `preference`='$preference_id'"; + $db_results = mysql_query($sql, dbh()); + + } // add_preference + + /*! + @function update_username + @discussion updates their username + */ + function update_username($new_username) { + + $new_username = sql_escape($new_username); + $sql = "UPDATE user SET username='$new_username' WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + } // update_username + + /*! + @function update_fullname + @discussion updates their fullname + */ + function update_fullname($new_fullname) { + + $new_fullname = sql_escape($new_fullname); + $sql = "UPDATE user SET fullname='$new_fullname' WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + } // update_username + + /*! + @function update_email + @discussion updates their email address + */ + function update_email($new_email) { + + $new_email = sql_escape($new_email); + $sql = "UPDATE user SET email='$new_email' WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + } // update_email + + /*! + @function update_offset + @discussion this updates the users offset_limit + */ + function update_offset($new_offset) { + + $new_offset = sql_escape($new_offset); + $sql = "UPDATE user SET offset_limit='$new_offset' WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + } // update_offset + + /*! + @function update_access + @discussion updates their access level + */ + function update_access($new_access) { + + /* Check for all disable */ + if ($new_access == 'disabled') { + $sql = "SELECT id FROM user WHERE access != 'disabled' AND id != '$this->id'"; + $db_results = mysql_query($sql,dbh()); + if (!mysql_num_rows($db_results)) { return false; } + } + + /* Prevent Only User accounts */ + if ($new_access == 'user') { + $sql = "SELECT id FROM user WHERE (access='admin' OR access='100') AND id != '$this->id'"; + $db_results = mysql_query($sql, dbh()); + if (!mysql_num_rows($db_results)) { return false; } + } + + $new_access = sql_escape($new_access); + $sql = "UPDATE user SET access='$new_access' WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + } // update_access + + /*! + @function update_last_seen + @discussion updates the last seen data for this user + */ + function update_last_seen() { + + $sql = "UPDATE user SET last_seen='" . time() . "' WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + } // update_last_seen + + /*! + @function update_user_stats + @discussion updates the playcount mojo for this + specific user + */ + function update_stats($song_id) { + + $song_info = new Song($song_id); + $user = $this->id; + $dbh = dbh(); + + if (!$song_info->file) { return false; } + + $time = time(); + + // Play count for this song + $sql = "UPDATE object_count" . + " SET date = '$time', count=count+1" . + " WHERE object_type = 'song'" . + " AND object_id = '$song_id' AND userid = '$user'"; + $db_result = mysql_query($sql, $dbh); + + $rows = mysql_affected_rows(); + if (!$rows) { + $sql = "INSERT INTO object_count (object_type,object_id,date,count,userid)" . + " VALUES ('song','$song_id','$time','1','$user')"; + $db_result = mysql_query($sql, $dbh); + } + + // Play count for this artist + $sql = "UPDATE object_count" . + " SET date = '$time', count=count+1" . + " WHERE object_type = 'artist'" . + " AND object_id = '" . $song_info->artist . "' AND userid = '$user'"; + $db_result = mysql_query($sql, $dbh); + + $rows = mysql_affected_rows(); + if (!$rows) { + $sql = "INSERT INTO object_count (object_type,object_id,date,count,userid)" . + " VALUES ('artist','".$song_info->artist."','$time','1','$user')"; + $db_result = mysql_query($sql, $dbh); + } + + // Play count for this album + $sql = "UPDATE object_count" . + " SET date = '$time', count=count+1" . + " WHERE object_type = 'album'" . + " AND object_id = '".$song_info->album."' AND userid = '$user'"; + $db_result = mysql_query($sql, $dbh); + + $rows = mysql_affected_rows(); + if (!$rows) { + $sql = "INSERT INTO object_count (object_type,object_id,date,count,userid)" . + "VALUES ('album','".$song_info->album."','$time','1','$user')"; + $db_result = mysql_query($sql, $dbh); + } + + + } // update_stats + + /*! + @function create + @discussion inserts a new user into ampache + */ + function create($username, $fullname, $email, $password, $access) { + + /* Lets clean up the fields... */ + $username = sql_escape($username); + $fullname = sql_escape($fullname); + $email = sql_escape($email); + + /* Now Insert this new user */ + $sql = "INSERT INTO user (username, fullname, email, password, access) VALUES" . + " ('$username','$fullname','$email',PASSWORD('$password'),'$access')"; + $db_results = mysql_query($sql, dbh()); + if (!$db_results) { return false; } + $user_id = mysql_insert_id(dbh()); + + /* Populates any missing preferences, in this case all of them */ + $this->fix_preferences($user_id); + + return $user_id; + + } // new + + /*! + @function update_password + @discussion updates a users password + */ + function update_password($new_password) { + + $sql = "UPDATE user SET password=PASSWORD('$new_password') WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + return true; + } // update_password + + + /*! + @function format_favorites + @discussion takes an array of objects and formats them corrrectly + and returns a simply array with just <a href values + */ + function format_favorites($items) { + + // The length of the longest item + $maxlen = strlen($items[0]->count); + + // Go through the favs + foreach ($items as $data) { + + // Make all number lengths equal + $len = strlen($data->count); + while ($len < $maxlen) { + $data->count = "0" . $data->count; + $len++; + } + + $results[] = "<li>[$data->count] - $data->f_name</li>\n"; + + } // end foreach items + + return $results; + + } // format_favorites + + /*! + @function fix_preferences + @discussion this makes sure that the specified user + has all the correct preferences. This function + should be run whenever a system preference is run + it's a cop out... FIXME! + */ + function fix_preferences($user_id = 0) { + + if (!$user_id) { + $user_id = $this->id; + } + + /* Get All Preferences */ + $sql = "SELECT * FROM user_preference WHERE user='$user_id'"; + $db_results = mysql_query($sql, dbh()); + + while ($r = mysql_fetch_object($db_results)) { + /* Check for duplicates */ + if (isset($results[$r->preference])) { + $r->value = sql_escape($r->value); + $sql = "DELETE FROM user_preference WHERE user='$user_id' AND preference='$r->preference' AND value='$r->value'"; + $delete_results = mysql_query($sql, dbh()); + } // duplicate + else { + $results[$r->preference] = $r; + } + } // while results + + /* + If we aren't the 0 user before we continue then grab the + 0 user's values + */ + if ($user_id != '0') { + $sql = "SELECT user_preference.preference,user_preference.value FROM user_preference,preferences " . + "WHERE user_preference.preference = preferences.id AND user_preference.user='0' AND preferences.type='user'"; + $db_results = mysql_query($sql, dbh()); + while ($r = mysql_fetch_object($db_results)) { + $zero_results[$r->preference] = $r->value; + } + } // if not user 0 + + + $sql = "SELECT * FROM preferences"; + if ($user_id != '0') { + $sql .= " WHERE type='user'"; + } + $db_results = mysql_query($sql, dbh()); + + + while ($r = mysql_fetch_object($db_results)) { + + /* Check if this preference is set */ + if (!isset($results[$r->id])) { + if (isset($zero_results[$r->id])) { + $r->value = $zero_results[$r->id]; + } + $sql = "INSERT INTO user_preference (`user`,`preference`,`value`) VALUES ('$user_id','$r->id','$r->value')"; + $insert_db = mysql_query($sql, dbh()); + } + } // while preferences + + } // fix_preferences + + + /*! + @function delete_stats + @discussion deletes the stats for this user + */ + function delete_stats() { + + $sql = "DELETE FROM object_count WHERE userid='" . $this->id . "'"; + $db_results = mysql_query($sql, dbh()); + + } // delete_stats + + /*! + @function delete + @discussion deletes this user and everything assoicated with it + */ + function delete() { + + /* + Before we do anything make sure that they aren't the last + admin + */ + if ($this->has_access(100)) { + $sql = "SELECT * FROM user WHERE (level='admin' OR level='100') AND id!='" . $this->id . "'"; + $db_results = mysql_query($sql, dbh()); + if (!mysql_num_rows($db_results)) { + return false; + } + } // if this is an admin check for others + + // Delete their playlists + $sql = "DELETE FROM playlist WHERE owner='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + // Delete any stats they have + $sql = "DELETE FROM object_count WHERE userid='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + // Delete their preferences + $sql = "DELETE FROM preferences WHERE user='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + // Delete the user itself + $sql = "DELETE FROM user WHERE id='$this->id'"; + $db_results = mysql_query($sql, dbh()); + + return true; + + } // delete + + /*! + @function is_online + @parameter delay how long since last_seen in seconds default of 20 min + @description calcs difference between now and last_seen + if less than delay, we consider them still online + */ + function is_online( $delay = 1200 ) { + return time() - $this->last_seen <= $delay; + } + +} //end class +?> diff --git a/modules/class/view.php b/modules/class/view.php new file mode 100644 index 00000000..f9de4ee6 --- /dev/null +++ b/modules/class/view.php @@ -0,0 +1,229 @@ +<? +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + $CVSHeader: ampache/modules/class/artist.php,v 1.1 2004/03/25 09:12:57 vollmerk Exp $ + $Source: /data/cvsroot/ampache/modules/class/artist.php,v $ + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header View Object of crappyness + View object that is thrown into their session + +*/ + + +class View { + + //Basic Componets + var $base_sql; + var $offset; + var $offset_limit; + var $sort_order; //asc or desc + var $sort_type; + var $action; + var $total_items; + + //generate a new view + function View($base_sql=0,$script=0,$sort_type=0,$total_items=0,$offset_limit=0) { + global $conf; + + // If we don't have a base sql, stop here + if (!is_string($base_sql)) { + return true; + } + + //Convert all 's into "s + $base_sql = str_replace("'",'"',$base_sql); + + $this->base_sql = $base_sql; + if ($offset_limit) { $this->offset_limit = $offset_limit; } + else { $this->offset_limit = $_SESSION['offset_limit']; } + if ($this->offset_limit < '1') { $this->offset_limit = '50'; } + $this->script = $script; + $this->sort_type = $sort_type; + $this->sort_order = "ASC"; + $this->offset = 0; + $this->total_items = $total_items; + + // Set the session + $_SESSION['view_offset_limit'] = $this->offset_limit; + $_SESSION['view_sort_type'] = $this->sort_type; + $_SESSION['view_offset'] = $this->offset; + $_SESSION['view_base_sql'] = $this->base_sql; + $_SESSION['view_sort_order'] = $this->sort_order; + $_SESSION['view_script'] = $this->script; + $_SESSION['view_total_items'] = $this->total_items; + $this->sql = $this->generate_sql(); + + } //constructor + + //takes all the parts and makes a full blown sql statement + function generate_sql() { + global $conf; + + $sql = $this->base_sql . " ORDER BY " . $this->sort_type ." ". $this->sort_order ." LIMIT " . $this->offset . "," . $this->offset_limit; + + return $sql; + + } //generate_sql + + //change the sort order from asc to desc or vise versa + function change_sort($new_sort=0) { + global $conf; + + if ($new_sort) { + $this->sort_order = $new_sort; + } + elseif ($this->sort_order == "DESC") { + $this->sort_order = "ASC"; + } + else { + $this->sort_order = "DESC"; + } + + $_SESSION['view_sort_order'] = $this->sort_order; + + $this->sql = $this->generate_sql(); + + return; + + } //change_sort + + //change the base sql + function change_sql($base_sql) { + global $conf; + + //Convert all 's into "s + $base_sql = str_replace("'",'"',$base_sql); + + $this->base_sql = $base_sql; + + $_SESSION['view_base_sql'] = $this->base_sql; + + $this->sql = $this->generate_sql(); + + } //change_sql + + //change offset + function change_offset($offset=0) { + global $conf; + + if (isset($offset)) { + $this->offset = $offset; + } + else { + $this->offset = $this->offset + $this->offset_limit; + } + + $_SESSION['view_offset'] = $this->offset; + + $this->sql = $this->generate_sql(); + + } //change_offset + + //change sort_type + function change_sort_type($sort_type) { + + $this->sort_type = $sort_type; + + $_SESSION['view_sort_type'] = $this->sort_type; + + $this->sql = $this->generate_sql(); + + } //change_sort_type + + /*! + @function change_offset_limit + @discussion changes the offset limit, sets the session + var and generates the sql statement + */ + function change_offset_limit($offset_limit) { + + $this->offset_limit = $offset_limit; + + $_SESSION['view_offset_limit'] = $this->offset_limit; + + $this->sql = $this->generate_sql(); + + } // change_offset_limit + + /*! + @function initialize + @discussion initializes the view object, checks $_REQUEST + for changes to the view object + */ + function initialize() { + + if ($_REQUEST['sort_type']) { + $this->change_sort_type($_REQUEST['sort_type']); + } + + if (isset($_REQUEST['offset'])) { + $this->change_offset($_REQUEST['offset']); + } + + if ($_REQUEST['base_sql']) { + $this->change_sql($_REQUEST['base_sql']); + } + + if (isset($_REQUEST['sort_order'])) { + $this->change_sort($_REQUEST['sort_order']); + } + + if ($_REQUEST['offset_limit']) { + $this->change_offset_limit($_REQUEST['offset_limit']); + } + + } // initialize + + + /*! + @function import_session_view + @discussion this imports the view from the session for use.. + this keeps us from having to globalize anything + wohoo! + */ + function import_session_view() { + + $this->sort_type = $_SESSION['view_sort_type']; + $this->offset = $_SESSION['view_offset']; + $this->base_sql = $_SESSION['view_base_sql']; + $this->sort_order = $_SESSION['view_sort_order']; + $this->script = $_SESSION['view_script']; + $this->total_items = $_SESSION['view_total_items']; + + + if ($_SESSION['view_offset_limit']) { + $this->offset_limit = $_SESSION['view_offset_limit']; + } + else { + $this->offset_limit = $_SESSION['offset_limit']; + } + + + $this->sql = $this->generate_sql(); + + } // import_session_view + + + +} //end class +?> diff --git a/modules/id3/audioinfo.class.php b/modules/id3/audioinfo.class.php new file mode 100755 index 00000000..04e42da9 --- /dev/null +++ b/modules/id3/audioinfo.class.php @@ -0,0 +1,463 @@ +<?php + +// +----------------------------------------------------------------------+ +// | PHP version 4.1.0 | +// +----------------------------------------------------------------------+ +// | Placed in public domain by Allan Hansen, 2002. Share and enjoy! | +// +----------------------------------------------------------------------+ +// | /demo/demo.audioinfo.class.php | +// | | +// | Example wrapper class to extract information from audio files | +// | through getID3(). | +// | | +// | getID3() returns a lot of information. Much of this information is | +// | not needed for the end-application. It is also possible that some | +// | users want to extract specific info. Modifying getID3() files is a | +// | bad idea, as modifications needs to be done to future versions of | +// | getID3(). | +// | | +// | Modify this wrapper class instead. This example extracts certain | +// | fields only and adds a new root value - encoder_options if possible. | +// | It also checks for mp3 files with wave headers. | +// +----------------------------------------------------------------------+ +// | Example code: | +// | $au = new AudioInfo(); | +// | print_r($au->Info('file.flac'); | +// +----------------------------------------------------------------------+ +// | Authors: Allan Hansen <ahØartemis*dk> | +// +----------------------------------------------------------------------+ +// + + + +/** +* getID3() settings +*/ + +require_once(conf('prefix') . "/modules/id3/getid3/getid3.php"); + + + + +/** +* Class for extracting information from audio files with getID3(). +*/ + +class AudioInfo { + + /** + * Private variables + */ + var $result = NULL; + var $info = NULL; + + + + + /** + * Constructor + */ + + function AudioInfo() { + + // Initialize getID3 engine + $this->getID3 = new getID3; + $this->getID3->option_md5_data = false; + $this->getID3->option_md5_data_source = false; + $this->getID3->encoding = 'UTF-8'; + } + + + + + /** + * Extract information - only public function + * + * @access public + * @param string file Audio file to extract info from. + */ + + function Info($file) { + + // Analyze file + $this->info = $this->getID3->analyze($file); + + // Exit here on error + if (isset($this->info['error'])) { + return array ('error' => $this->info['error']); + } + + // Init wrapper object + $this->result = array (); + $this->result['format_name'] = @$this->info['fileformat'].'/'.@$this->info['audio']['dataformat'].(isset($this->info['video']['dataformat']) ? '/'.@$this->info['video']['dataformat'] : ''); + $this->result['encoder_version'] = @$this->info['audio']['encoder']; + $this->result['encoder_options'] = NULL; + $this->result['bitrate_mode'] = @$this->info['audio']['bitrate_mode']; + $this->result['channels'] = @$this->info['audio']['channels']; + $this->result['sample_rate'] = @$this->info['audio']['sample_rate']; + $this->result['bits_per_sample'] = @$this->info['audio']['bits_per_sample']; + $this->result['playing_time'] = @$this->info['playtime_seconds']; + $this->result['avg_bit_rate'] = @$this->info['audio']['bitrate']; + $this->result['tags'] = @$this->info['tags']; + $this->result['comments'] = @$this->info['comments']; + $this->result['warning'] = @$this->info['warning']; + $this->result['md5'] = @$this->info['md5_data']; + //$this->result['full'] = @$this->info; + + // The vollmer way + if($this->info['fileformat'] === 'mp3' || $this->info['audio']['dataformat'] === "mp3") + { + if (isset($this->info['tags']['id3v1'])) { $this->info['id3v1']['comments'] = $this->info['tags']['id3v1']; } + if (isset($this->info['tags']['id3v2'])) { $this->info['id3v2']['comments'] = $this->info['tags']['id3v2']; } + + if ($this->info['id3v1']) { + if (function_exists('iconv')) { + $this->result['id3v1']['title'] = iconv("UTF-8", "ISO-8859-1", $this->info['id3v1']['comments']['title'][0]); + $this->result['id3v1']['artist'] = iconv("UTF-8", "ISO-8859-1", $this->info['id3v1']['comments']['artist'][0]); + $this->result['id3v1']['album'] = iconv("UTF-8", "ISO-8859-1", $this->info['id3v1']['comments']['album'][0]); + $this->result['id3v1']['comment'] = iconv("UTF-8", "ISO-8859-1", $this->info['id3v1']['comments']['comment'][0]); + $this->result['id3v1']['genre'] = iconv("UTF-8", "ISO-8859-1", $this->info['id3v1']['comments']['genre'][0]); + } + else { + $this->result['id3v1']['title'] = $this->info['id3v1']['comments']['title'][0]; + $this->result['id3v1']['artist'] = $this->info['id3v1']['comments']['artist'][0]; + $this->result['id3v1']['album'] = $this->info['id3v1']['comments']['album'][0]; + $this->result['id3v1']['comment'] = $this->info['id3v1']['comments']['comment'][0]; + $this->result['id3v1']['genre'] = $this->info['id3v1']['comments']['genre'][0]; + } // no iconv + $this->result['id3v1']['year'] = $this->info['id3v1']['comments']['year'][0]; + $this->result['id3v1']['track'] = $this->info['id3v1']['comments']['track'][0]; + + } + if ($this->info['id3v2']) { + if (function_exists('iconv')) { + $this->result['id3v2']['title'] = iconv("UTF-8", "ISO-8859-1", $this->info['id3v2']['comments']['title'][0]); + $this->result['id3v2']['artist'] = iconv("UTF-8", "ISO-8859-1", $this->info['id3v2']['comments']['artist'][0]); + $this->result['id3v2']['album'] = iconv("UTF-8", "ISO-8859-1", $this->info['id3v2']['comments']['album'][0]); + $this->result['id3v2']['comment'] = iconv("UTF-8", "ISO-8859-1", $this->info['id3v2']['comments']['comment'][0]); + } + else { + $this->result['id3v2']['title'] = $this->info['id3v2']['comments']['title'][0]; + $this->result['id3v2']['artist'] = $this->info['id3v2']['comments']['artist'][0]; + $this->result['id3v2']['album'] = $this->info['id3v2']['comments']['album'][0]; + $this->result['id3v2']['comment'] = $this->info['id3v2']['comments']['comment'][0]; + + } + $this->result['id3v2']['year'] = $this->info['id3v2']['comments']['year'][0]; + $this->result['id3v2']['genre'] = $this->info['id3v2']['comments']['genre'][0]; + $this->result['id3v2']['track'] = $this->info['id3v2']['comments']['track'][0]; + $this->result['id3v2']['genreid'] = $this->info['id3v2']['comments']['genreid'][0]; + } + } + elseif($this->info['fileformat'] === 'ogg') { + if (function_exists('iconv')) { + $this->result['ogg']['title'] = iconv("UTF-8",conf('site_charset') . "//TRANSLIT", $this->info['ogg']['comments']['title'][0]); + $this->result['ogg']['artist'] = iconv("UTF-8",conf('site_charset') . "//TRANSLIT", $this->info['ogg']['comments']['artist'][0]); + $this->result['ogg']['album'] = iconv("UTF-8",conf('site_charset') . "//TRANSLIT", $this->info['ogg']['comments']['album'][0]); + $this->result['ogg']['author'] = iconv("UTF-8",conf('site_charset') . "//TRANSLIT", $this->info['ogg']['comments']['author'][0]); + } + else { + $this->result['ogg']['title'] = $this->info['ogg']['comments']['title'][0]; + $this->result['ogg']['artist'] = $this->info['ogg']['comments']['artist'][0]; + $this->result['ogg']['album'] = $this->info['ogg']['comments']['album'][0]; + $this->result['ogg']['author'] = $this->info['ogg']['comments']['author'][0]; + } + + $this->result['ogg']['year'] = $this->info['ogg']['comments']['date'][0]; + $this->result['ogg']['track'] = $this->info['ogg']['comments']['tracknumber'][0]; + + } // if ogg + /* If it's a WMA */ + elseif($this->info['fileformat'] === 'asf') { + if (function_exists('iconv')) { + $this->result['asf']['title'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['asf']['title'][0]); + $this->result['asf']['artist'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['asf']['artist'][0]); + $this->result['asf']['album'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['asf']['album'][0]); + $this->result['asf']['comment'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['asf']['comment'][0]); + } // if iconv + else { + $this->result['asf']['title'] = $this->info['tags']['asf']['title'][0]; + $this->result['asf']['artist'] = $this->info['tags']['asf']['artist'][0]; + $this->result['asf']['album'] = $this->info['tags']['asf']['album'][0]; + $this->result['asf']['comment'] = $this->info['tags']['asf']['comment'][0]; + } + $this->result['asf']['track'] = $this->info['tags']['asf']['track'][0]; + $this->result['asf']['year'] = $this->info['tags']['asf']['year'][0]; + } // if wma + /* If it's a flac */ + elseif($this->info['fileformat'] === 'flac') { + if (function_exists('iconv')) { + $this->result['flac']['title'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['vorbiscomment']['title'][0]); + $this->result['flac']['artist'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['vorbiscomment']['artist'][0]); + $this->result['flac']['album'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['vorbiscomment']['album'][0]); + $this->result['flac']['comment'] = iconv("UTF-8","ISO-8859-1", $this->info['comments'][0]); + } + else { + $this->result['flac']['title'] = $this->info['tags']['vorbiscomment']['title'][0]; + $this->result['flac']['artist'] = $this->info['tags']['vorbiscomment']['artist'][0]; + $this->result['flac']['album'] = $this->info['tags']['vorbiscomment']['album'][0]; + $this->result['flac']['comment'] = $this->info['comments'][0]; + } + $this->result['flac']['track'] = $this->info['tags']['vorbiscomment']['tracknumber'][0]; + $this->result['flac']['year'] = $this->info['tags']['vorbiscomment']['date'][0]; + $this->result['flac']['genre'] = $this->info['tags']['vorbiscomment']['genre'][0]; + } // if flac + + elseif($this->info['fileformat'] === 'mp4') { + + if (function_exists('iconv')) { + $this->result['m4a']['title'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['quicktime']['title'][0]); + $this->result['m4a']['artist'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['quicktime']['artist'][0]); + $this->result['m4a']['album'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['quicktime']['album'][0]); + $this->result['m4a']['comment'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['quicktime']['comment'][0]); + } + else { + $this->result['m4a']['title'] = $this->info['tags']['quicktime']['title'][0]; + $this->result['m4a']['artist'] = $this->info['tags']['quicktime']['artist'][0]; + $this->result['m4a']['album'] = $this->info['tags']['quicktime']['album'][0]; + $this->result['m4a']['comment'] = $this->info['tags']['quicktime']['comment'][0]; + } + + $this->result['m4a']['year'] = $this->info['tags']['quicktime']['creation_date'][0]; + + + } // if m4a + + + elseif($this->info['fileformat'] === 'mpc') { + + if (function_exists('iconv')) { + $this->result['mpc']['title'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['ape']['title'][0]); + $this->result['mpc']['artist'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['ape']['artist'][0]); + $this->result['mpc']['album'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['ape']['album'][0]); + $this->result['mpc']['comment'] = iconv("UTF-8","ISO-8859-1", $this->info['tags']['ape']['comment'][0]); + } + else { + $this->result['mpc']['title'] = $this->info['tags']['ape']['title'][0]; + $this->result['mpc']['artist'] = $this->info['tags']['ape']['artist'][0]; + $this->result['mpc']['album'] = $this->info['tags']['ape']['album'][0]; + $this->result['mpc']['comment'] = $this->info['tags']['ape']['comment'][0]; + } + + $this->result['mpc']['year'] = $this->info['tags']['ape']['year'][0]; + $this->result['mpc']['track'] = $this->info['tags']['ape']['track'][0]; + $this->result['mpc']['genre'] = $this->info['tags']['ape']['genre'][0]; + + } // if mpc + + + // Post getID3() data handling based on file format + $method = @$this->info['fileformat'].'Info'; + if (@$this->info['fileformat'] && method_exists($this, $method)) { + $this->$method(); + } + + return $this->result; + } + + + + + + /** + * post-getID3() data handling for AAC files. + * + * @access private + */ + + function aacInfo() { + $this->result['format_name'] = 'AAC'; + } + + + + + /** + * post-getID3() data handling for Wave files. + * + * @access private + */ + + function riffInfo() { + if ($this->info['audio']['dataformat'] == 'wav') { + + $this->result['format_name'] = 'Wave'; + + } else if (ereg('^mp[1-3]$', $this->info['audio']['dataformat'])) { + + $this->result['format_name'] = strtoupper($this->info['audio']['dataformat']); + + } else { + + $this->result['format_name'] = 'riff/'.$this->info['audio']['dataformat']; + + } + } + + + + + /** + * * post-getID3() data handling for FLAC files. + * + * @access private + */ + + function flacInfo() { + $this->result['format_name'] = 'FLAC'; + } + + + + + + /** + * post-getID3() data handling for Monkey's Audio files. + * + * @access private + */ + + function macInfo() { + $this->result['format_name'] = 'Monkey\'s Audio'; + } + + + + + + /** + * post-getID3() data handling for Lossless Audio files. + * + * @access private + */ + + function laInfo() { + $this->result['format_name'] = 'La'; + } + + + + + + /** + * post-getID3() data handling for Ogg Vorbis files. + * + * @access private + */ + + function oggInfo() { + if ($this->info['audio']['dataformat'] == 'vorbis') { + + $this->result['format_name'] = 'Ogg Vorbis'; + + } else if ($this->info['audio']['dataformat'] == 'flac') { + + $this->result['format_name'] = 'Ogg FLAC'; + + } else if ($this->info['audio']['dataformat'] == 'speex') { + + $this->result['format_name'] = 'Ogg Speex'; + + } else { + + $this->result['format_name'] = 'Ogg '.$this->info['audio']['dataformat']; + + } + } + + + + + /** + * post-getID3() data handling for Musepack files. + * + * @access private + */ + + function mpcInfo() { + $this->result['format_name'] = 'Musepack'; + } + + + + + /** + * post-getID3() data handling for MPEG files. + * + * @access private + */ + + function mp3Info() { + $this->result['format_name'] = 'MP3'; + } + + + + + /** + * post-getID3() data handling for MPEG files. + * + * @access private + */ + + function mp2Info() { + $this->result['format_name'] = 'MP2'; + } + + + + + + /** + * post-getID3() data handling for MPEG files. + * + * @access private + */ + + function mp1Info() { + $this->result['format_name'] = 'MP1'; + } + + + + + /** + * post-getID3() data handling for WMA files. + * + * @access private + */ + + function asfInfo() { + $this->result['format_name'] = strtoupper($this->info['audio']['dataformat']); + } + + + + /** + * post-getID3() data handling for Real files. + * + * @access private + */ + + function realInfo() { + $this->result['format_name'] = 'Real'; + } + + + + + + /** + * post-getID3() data handling for VQF files. + * + * @access private + */ + + function vqfInfo() { + $this->result['format_name'] = 'VQF'; + } + +} + + +?> diff --git a/modules/id3/changelog.txt b/modules/id3/changelog.txt new file mode 100644 index 00000000..253f43b2 --- /dev/null +++ b/modules/id3/changelog.txt @@ -0,0 +1,2375 @@ +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// changelog.txt - part of getID3() // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + + » denotes a major feature addition/change + ¤ denotes a change in the returned structure +* Bugfix: denotes a fixed bug + +Version History +=============== + +1.7.2: [2004-10-18] Allan Hansen + * Bugfix: Large ID3v2 tags inside ASF not parsed properly under PHP5. + * Bugfix: Certain Wavpack3 files failed under PHP5 due to new + undocumented tmpfile() limit (same problem as above). + * Bugfix: New iTunes crashes PHP - temp fix - no tags on those + files. + ¤ Trim/unset wavpack encoder_options to match 2.0.0b2 output. + » Added support for WavPack v4.0+ (thanks ahØartemis*dk) + » Removed code for parsing EXE files (thanks ahØartemis*dk) + Removed file: module.misc.exe.php + * Bugfix: ['nsv']['NSVs']['framerate_index'] might be wrong + (thanks ahØartemis*dk) + * Bugfix: transparent color was wrong from truecolor PNG + (thanks ahØartemis*dk) + * Bugfix: Changed MPC SV7 header size from 30 to 28, this will + change hash values for MPC files (thanks ahØartemis*dk) + * Bugfix: Changed MPC SV4-6 header size from 28 to 8, this will + change hash values for MPC files (thanks ahØartemis*dk) + ¤ Commented-out unknown/unused values in NSV and ISO modules + (thanks ahØartemis*dk) + + +1.7.1b1: [July-26-2004] James Heinrich + » Added support for Apple Lossless Audio Codec + » Added support for RealAudio Lossless + » Added support for TTA v3 + » Added support for TIFF + New file: /getid3/module.graphic.tiff.php + » Modified iconv_fallback to work with UTF-8, UTF-16, UTF-16LE, + UTF-16BE and ISO-8859-1 even if iconv() and/or XML support is + not available. This means that iconv() is no longer required + for most users of getID3() + (thanks Jeremia, khleeØbitpass*com) + » Added support for Monkey's Audio v3.98+ (thanks ahØartemis*dk) + » Included new demo showing most-basic getID3() usage + New file: /demos/demo.basic.php + * Bugfix: LAME3.94+ presets cached incorrectly if multiple files + are scanned in one batch and first file is LAME3.93 or earlier + (thanks enoyandØyahoo*com) + * Bugfix: Added warning if compressed ID3v2 frame decompression + fails. (thanks Mike Billings) + * Bugfix: Assorted small fixes to ensure compatability with PHP5 + * Bugfix: ID3v1 genre "Blues" could not be written + (thanks Jeremia) + * Bugfix: ['bitrate_mode'] typo in module.audio-video.real.php + (thanks asukakenjiØusers*sourceforge*net) + * Bugfix: ['zip']['files'] is now populated with filenames even + if End Of Central Directory couldn't be parsed + * Bugfix: ['audio']['lossless'] was incorrect for FLAC + (thanks WaldoMonster) + * Bugfix: MD5 File was incorrect in directory browse mode for + /demo/getid3.browse.php + * Bugfix: PHP v5 compatability changes (float array keys, fread() + calls with zero data length) + (thanks getid3Øjsc*pp*ru) + * Bugfix: was dying if on compressed ID3v2 frames if + gzuncompress() function was unavailable + * Bugfix: ['vqf']['COMM'] was always empty + * Bugfix: MIDI playtime was missing for single-track MIDI files + * Bugfix: removed � characters from ['comments_html'] + (thanks p*quaedackersØplanet*nl) + * Bugfix: improved MIDI playtime accuracy + (thanks joelØoneporpoise*com) + * Bugfix: BMP subtypes 4 and 5 were not being identified + * Bugfix: frame_rate in AVI was incorrectly truncated to integer + * Bugfix: FLAC cuesheet track index was incorrect + (thanks tetsuo*yokozukaØoperamail*com) + ¤ ['quicktime']['display_scale'] now contains the playback scale + multiplier for QuickTime movies - a movie set to playback at + double-size will have "2" here. Other values are "1" and "0.5" + ¤ Added LAME preset guessing for --preset medium with v3.90.3 + (thanks phwipØfish*co*uk) + ¤ Added $encoding_id3v1 to allow for ID3v1 encodings other than + the standard ISO-8859-1 + ¤ Default AVI video bitrate_mode is now 'vbr' + (thanks eltoderØpisem*net) + Force getID3() to abort if Shorten files have ID3 or APE tags + (thanks ahØartemis*dk) + Editable textbox for parent directory in demo.browse.php + (thanks eltoderØpisem*net) + + +1.7.0-hotfix [2004-03-17] Allan Hansen + (hotfix version released by Allan Hansen) + * Bugfix: PHP 4.1.x compatiblity - fgets($fp) => fgets($fp, 1024) + * Bugfix: Added default charset to TextEncodingNameLookup() in + module.tag.id3v2.php + Ø Removed option_no_iconv + iconv() support is only a requirement for WMA/WMW/ASF, and for + destination encodings other than ISO-8859-1 and UTF-8, iconv is + not needed otherwise. New 'iconv_req' in GetFileFormatArray() + only set for WMA/WMV/ASF. analyze() now refuses to analyse + WMA/ASF file if iconv is not present. + iconv_fallback() only dies on internal errors not missing iconv() + + +1.7.0: [January-19-2004] James Heinrich + » Added support for RIFF/CDXA files (MPEG video in RIFF container + format (thanks chrisØdigitekdesign*com) + » Added support for TTA v2 (thanks ahØartemis*dk) + ¤ ID3v2 unsynchronisation scheme disabled by default because most + tag-reading programs cannot read unsynchronised tags. Can be + overridden by setting id3v2_use_unsynchronisation to true. + (thanks mikeØdelusion*org) + ¤ extention.*.php renamed to extension.*.php + (thanks tp62Øcornell*edu) + ¤ /demo/demo.check.php renamed to /demo/demo.browse.php + ¤ Added id3v2_paddedlength configuration parameter to WriteTags() + and renamed tag_language to id3v2_tag_language + ¤ MPEG audio layers are now represented as 1, 2 or 3 instead of + 'I', 'II', or 'III' + ¤ Added [audio][wformattag] and [video][fourcc] for WAV and AVI + ¤ Added [audio][streams] which contains one entry for each audio + stream present in the file (usually only one). The data is a + copy of what is usually found in [audio]. If there are multiple + audio streams then [audio] will contain a sum of the bitrates + of all audio streams, and the data format of the first stream + (if streams are of different data types) + ¤ Added BruteForce mode to mp3 scanning. Disabled by default as + it is extremely slow and only files that are broken enough to + not really play will gain any benefit from this. + ¤ Suppress '--resample xxxxx' appended to encoder options for mp3 + with low-quality presets for default sampling frequencies + ¤ Enhanced LAME preset guessing for pre-3.93 with a better lookup + table, --resample/--lowpass guessing (thanks phwipØfish*co*uk) + ¤ RIFF files with non-MP3 contents no longer have + [audio][encoder_options] set + ¤ Added [audio][encoder_options] to audio formats where possible + (including LiteWave, LPAC, OptimFROG, TTA) + ¤ Moved [quantization] and [max_prediction_order] from + [lpac][flags] to just [lpac] + ¤ WavPack flags are now parsed into [wavpack][flags] + * Bugfix: APEtags with ReplayGain information stored with comma- + seperated decimal values (ie "0,95" instead of "0.95") were + giving wrong peak and gain values + * Bugfix: Filesize > 2GB not always detected correctly + * Bugfix: Some ID3v2 frames had data key unset incorrectly + (thanks chrisØdigitekdesign*com) + * Bugfix: Warnings on empty-strings-only comments + * Bugfix: ID3v2 tag writing may have had incorrect padding length + if padded length less than current ID3v2 tag length and + merge_existing_data is false (thanks mikeØdelusion*org) + * Bugfix: hash_data() for SHA1 was broken under Windows + * Bugfix: BigEndian2Float()/LittleEndian2Float() were broken + * Bugfix: LAME header calculated track peaks were incorrect for + LAME3.94a15 and earlier + * Bugfix: AVIs with VBR MP3 audio data reported incorrect bitrate + and bitrate_mode + * Bugfix: AVIs sometimes had incorrect or missing video and total + bitrates + * Bugifx: AVIs sometimes had incorrect ['avdataend'] and + therefore also incorrect data hashes (md5_data, sha1_data) + * Bugfix: ID3v1 genreid no longer returned for Unknown genre + * Bugfix: ID3v1 SCMPX genres were broken + Modified LAME header parsing to correctly process peak track + value for LAME3.94a16+ (thanks Gabriel) + md5_file() and sha1_file() now work under Windows in PHP < 4.2.0 + and 4.3.0 respectively with helper apps + Default md5_data() tempfile location is now system temp directory + instead of same directory as file (thanks towbØtiscali*de) + Improved list of RIFF ['INFO'] comment key translations + More helpful error message when GETID3_HELPERAPPSDIR has spaces + /demo/demo.browse.php now autogets both MD5 and SHA1 hashes for + files < 50MB + Replaced PHP_OS comparisons with GETID3_OS_ISWINDOWS define + (thanks necroticØusers*sourceforge*net) + + +1.7.0b5: [December-29-2003] James Heinrich + » Windows only: Various binary files are now required for some + file formats, especially for tag writing, as well as md5sum + (and other) calculations. These binaries are now stored in the + directory defined as GETID3_HELPERAPPSDIR in getid3.php + (default is /helperapps/ parallel to /getid3/). + Note: This directory must not have any spaces in the pathname. + All neccesary files are available as a seperate download. + See /helperapps/readme.txt for more information + New file: /helperapps/readme.txt + » Unified tag-writing interface for all tag formats + New file: /getid3/write.php + /getid3/write.apetag.php + /getid3/write.id3v1.php + /getid3/write.id3v2.php + /getid3/write.lyrics3.php + /getid3/write.metaflac.php + /getid3/write.vorbiscomment.php + » Added support for Shorten - requires shorten binary (head.exe + is also required under Windows). + New file: /getid3/module.audio.shorten.php + » Added support for RKAU + New file: /getid3/module.audio.rkau.php + » Added (minimal) support for SZIP + New file: /getid3/module.archive.szip.php + » Added MySQL caching extention (thanks ahØartemis*dk) + New file: /getid3/extention.cache.mysql.php + » Added DBM caching extention (thanks ahØartemis*dk) + New file: /getid3/extention.cache.dbm.php + » Added sha1_data hash option (thanks ahØartemis*dk) + » Added option to allow getID3() to skip ID3v2 without parsing it + for faster scanning when ID3v2 data is not required. If you + want to enable this feature delete /getid3/module.tag.id3v2.php + (thanks ahØartemis*dk) + ¤ 8-bit WAV data now calculates MD5 checksums as normal, not + converting to signed data as before, so stored md5_data_source + in FLAC files will no longer match md5_data for the equivalent + decoded 8-bit WAV. A warning will be generated for 8-bit FLAC + files + ¤ Added option_no_iconv option to allow getID3() to work + partially without iconv() support enabled in PHP + (thanks ahØartemis*dk) + ¤ All '*_ascii' keys removed for ASF/WMA/WMV files + ¤ All 'ascii*' keys removed for ID3v2 tags + ¤ Ogg filetypes now return MIME of "application/ogg" instead of + the previous "application/x-ogg" + (thanks blakewattersØusers*sourceforge*net) + ¤ Force contents of ['id3v2']['comments'] to UTF-8 format from + whatever encoding each frame may have (text encoding can vary + from frame to frame in ID3v2) + ¤ MP3Gain information from APE tags suppressed from ['tags'] and + parsed into ['replay_gain'] + ¤ ReplayGain information (all formats) changed from "Radio" and + "Audiophile" to "Track" and "Album" respectively + ¤ ['volume'] and ['max_noclip_gain'] are now available in both + ['replay_gain']['track'] and ['replay_gain']['album'] for all + formats that calculate ReplayGain. + ¤ ['video']['total_frames'] is available for AVIs + ¤ All parsed ID3v2 frame data is now in ['id3v2'][XXXX][#] + (previously some frame types would have numeric array keys if + multiple instances of that frame type were allowed and other + frame types would not) + ¤ ASF/WMA "WM/Picture" images are now parsed in the same manner + as ID3v2 with the image (ex JPEG) data returned in [data] + rather than [value] + * Bugfix: Optional tag processing options were being ignored (ie + ID3v1 still processed even if option_tag_id3v1 == false) + (thanks ahØartemis*dk) + * Bugfix: fixed MultiByteCharString2HTML() for UTF-8 + * Bugfix: Quicktime files not always reporting video frame_rate + * Bugfix: False ID3v1 synch patterns in APE or Lyrics3 tags are + now detected and incorrect ID3v1 data not returned + (thanks sebastian_maresØusers*sourceforge*net for the idea) + * Bugfix: WMA9 Lossless now reported as lossless + * Bugfix: two typos in ID3v1 genre list + * Bugfix: MPEG-2/2.5 ABR/VBR MP3 files had doubled playtime + * Bugfix: MPEG-2/2.5 LayerII (ie MP2: 24/22.05/16kHz) files were + not detected due to incorrect frame length calculation + * Bugfix: MPEG LayerI files were not detected due to incorrect + frame length calculation (must be multiple of slot length) + Added alternative md5_data via system call - twice as fast. Needs + "getID3()-WindowsSupport" to work under Windows. + (thanks ahØartemis*dk) + ID3v2.4 compressed frames are now supported + php_uname() calls changed to use PHP_OS constant + Added SCMPX extensions to ID3v1 genres (0xF0-0xFE) + Obfuscated contributor email address in changelog and sourcecode + Added memory-saving EmbeddedLookup() function for lookup tables + in RIFF and ID3v2 modules (thanks ahØartemis*dk) + Major memory patches to many modules by using + $var = &$INFO_ARRAY_AT_SOME_INDEX + in place of large multi-dimensional array declarations. + Memory saved: RIFF: ~200kB; ID3v2: ~475kB; ASF: ~50kB etc. + (thanks ahØartemis*dk) + + +1.7.0b4: [November-19-2003] James Heinrich + » Support added for MPC files with old SV4-SV6 structure + » RealVideo now properly supported with resolution, framerate, etc + (thanks jcsston) + » RealAudio files with old-style file format (v2-v4) are now + fully supported + » Support added for DolbyDigital WAV files (thanks ahØartemis*dk) + ¤ ['RIFF'] is now ['riff'] to conform to make all root key names + lowercase + ¤ ['OFR'] is now ['ofr'] to conform to make all root key names + lowercase + ¤ ['tags_html'] is now available as a copy of ['tags'] but + with all text replaced with an HTML version of all characters + above chr(127), translated according to whatever the encoding + of the source tag is, in the HTML form Ӓ + ¤ CopyTagsToComments() is now available in getid3_lib + ¤ QuicktimeVR files now return a ['video']['dataformat'] of + 'quicktimevr' instead of 'quicktime' (thanks gtsØtsu*biz) + ¤ Quicktime video files with DivX, Xvid, 3ivx or MPEG4 video + streams now return those names as ['video']['dataformat'] + ¤ MPEG video files are now identified with ['video']['codec'] set + to either 'MPEG-1' or 'MPEG-2' (rather than just 'MPEG'). If you + see a file wrongly identified, please report it! + (thanks fccHandler) + ¤ All bitrate values in ['mpeg']['audio'] is now reported in bps + rather than kbps (ie 128000 instead of 128) for consistancy + ¤ AVIs with MP2 audio now report ['audio']['dataformat'] as 'mp2' + rather than 'wav' (thanks metalbrainØnetian*com) + ¤ Added ['md5_data_source'] for OptimFROG + ¤ AC3 in RIFF-WAV now identified with ['audio']['dataformat'] + returning 'ac3' + ¤ WavPack ['extra_bc'] now returned as integer + ¤ WavPack ['extras'] now returned as 3-element array of integers + ¤ MP3 ['audio']['encoder options'] now returns 'VBR' or 'CBR' only + (no bitrate) if no LAME preset is used, or 'VBR q??' where ?? is + a number 0-100 for Fraunhofer-encoded VBR MP3s + * Bugfix: VBR MP3s could have incorrect bitrate reported + * Bugfix: Quicktime files with MP4 audio were not returning + ['video']['dataformat'] (thanks robØmassive-interactive*nl) + * Bugfix: strpad vs str_pad typo in module.riff.php + (thanks nicojunØusers*sourceforge*net) + * Bugfix: ReplayGain information was often wrong for MPC files + * Bugfix: MD5 and other post-TAIL chunks were not being processed + in module.audio.optimfrog.php + * Bugfix: Undefined variable in table_var_dump() in demo/check.php + * Bugfix: QuickTime files now only return information in [audio] + or [video] if those exist in the file + * Bugfix: WavPack no longer tries to read entire compressed data + chunk + * Bugfix: Properly handle VBR MP3s with "Info" (rather than + "Xing") header frame. foobar2000 adds this to MP3 files when + "Fix MP3 Header" function is used (thanks ahØartemis*dk) + * Bugfix: Fraunhofer VBRI headers for MP3s were assuming 2-byte + entries for TOC rather than using stride, and were ignoring the + scaling value. (thanks sebastianØmaresweb*net) + Several QuickTime atoms have been added to an exclusion list + because they have been observed, but I have no idea what they + are supposed to do so I can't add real support for them, but + they should not generate warnings (robØmassive-interactive*nl) + Old MPC encoder (before v1.06) was return as v0.00, now returned + as 'Buschmann v1.7.0-v1.7.9 or Klemm v0.90-v1.05' + (thanks ahØartemis*dk) + Added check for magic_quotes_runtime and code to disable it if + neccesary (thanks stefan*kischkelØt-online*de) + Added 3ivx fourCCs to module.audio-video.quicktime.php + MP3 and AC3 streams are now parsed when contained inside RIFF-WAV + or RIFF-AVI container formats + Better detection of named presets in LAME 3.93/3.94 + + +1.7.0b3: [October-17-2003] James Heinrich + » AC-3 (aka Dolby Digital) is now supported. + New file: /getid3/module.audio.ac3.php + * Bugfix: ID3v2-writing function Unsynchronise() was broken, which + made ID3v2 tag containing binary data (typically pictures) get + corrupted. (thanks t*coombesØbendigo*vic*gov*au, + i*kuehlbornØndh*net, mikeØdelusion*org, mikeØftl*com) + * Bugfix: Zip comments now returned as array instead of string, + as they're supposed to be. + * Bugfix: Quicktime/MP4 files may have reported extremely low + bitrates (thanks spunkØdasspunk*com) + Improved double-ID3v1 check to prevent false detection when string + "TAG" is present in APE or Lyrics3 + Fixed /demo/simple.php + Fixed /demo/joinmp3.php + Fixed /demo/mimeonly.php + Fixed /demo/write.php + + +1.7.0b2: [October-15-2003] James Heinrich + » TTA Lossless Audio Compressor format now supported. + (http://tta.iszf.irk.ru) + New file: /getid3/module.graphic.tta.php + » PhotoCD (PCD) format now supported. Image data for the three + lowest resolutions (192x128, 384x256, 768x512) can be optionally + extracted. + New file: /getid3/module.graphic.pcd.php + ¤ RIFF-MP3 files now should return the same ['md5_data'] as the + identical MP3 file outside the RIFF container + ¤ Name of LAME preset used (if available, needs LAME v3.90+) + returned in ['mpeg']['audio']['LAME']['preset_used'] and also as + part of ['audio']['encoder_options'] + ¤ VQF module now sets ['audio']['encoder_options'] to i.e. CBR96 + ¤ MP3 module now sets ['audio']['encoder_options'] on CBR files + and all LAME-encoded files + ¤ MPC module now sets ['audio']['encoder_options'] + ¤ Monkey module now sets ['audio']['encoder_options'] + ¤ AAC module now sets ['audio']['encoder_options'] to profile name + ¤ ASF module now sets ['audio']['encoder_options'] + ¤ Ogg module adds ['audio']['encoder_options'] -b 128 on + Ogg Vorbis 1.0+ ABR files + ¤ Ogg module adds ['audio']['encoder_options'] -q N on + Ogg Vorbis 1.0+ VBR files 44k/48k sample rate/stereo files only. + ¤ Ogg module ['audio']['encoder_options'] "Nominal birate: 80k" to + other Ogg Vorbis files. + ¤ ID3v2 track number now returned as string (with leading zeros, + if present in data) rather than integer (thanks Plamen) + ¤ ASF module returns ['asf']['comments']['encoding_time_unix'] if + available (from WM/EncodingTime) + ¤ Fixed /demo/mysql.php and added some new features: + - encoder options + - ID3v2 "Encoded By" + - non-empty comments + - total entries in database summary (totals & averages) + - database version update + * Bugfix: 'UNICODE' iconv() charset changed to 'UTF-16LE' or + 'UTF-16BE' as appropriate + * Bugfix: iconv_fallback() function created in case iconv() fails + * Bugfix: fixed MD5 calls in demo/check.php + * Bugfix: reenabled detection of APE + Lyrics3 tags in same file + * Bugfix: ASF module now returns ID3v1 genre as string instead of + number - patch from Eugene Toder. + * Bugfix: ASF module now reads non-standard field names, + i.e. "date" as well as WM/Year - patch from Eugene Toder. + * Bugfix: ASF module now returns genre as-is if it is not a + standard ID3v1 genre (thanks wonderboy) + * Bugfix: Eliminated false-synch problem in MP3 module + * Bugfix: Fixed missing root ['bitrate'] for most formats + * Bugfix: ['audio']['compression_ration'] missing for MPC + (thanks WaldoMonster) + * Bugfix: NSV module died in 1.7.0b1 + * Bugfix: ASF module died in 1.7.0b1 when WM/Picture preset + * Bugfix: ASF tracknumber incorrect when specified by WM/Track + rather than WM/TrackNumber (thanks jgriffiniiiØhotmail*com) + * Bugfix: MPEG audio+video playtime should now be pretty accurate + (ie within 0.1% variation at most) + (thanks mgrimmØhealthtvchannel*org) + * Bugfix: ID3v2 not being copied to ['tags'] in some cases + * Bugfix: LAME CBR files with Info tag were being incorrectly + flagged as VBR (thanks Jojo) + * Bugfix: LAME tag not being detected for LAME 3.90 (original) + Changed regex pattern match for MP3 to include 3rd byte for more + reliable/accurate pattern matching + Added duplicate-ID3v1 tag checking (two ID3v1 tags, one after the + other) that has been known to occur with iTunes + (thanks towbØtiscali*de) + Added instructions for enabling iconv() support under Windows + Removed some unneccesary debugging code + Suppressed duplicate PHP warnings for missing include files + Included some missing dependencies in various files + /demo/audioinfo.class.php now copies ['audio']['encoder_options'] + + +1.7.0b1: [2003-09-28] Allan Hansen + This beta version was not made by James Heinrich. It was made by + Allan Hansen <ahØartemis*dk> - please send bug reports on this + beta directly to me. + + James Heinrich will release 1.7.0 final, but it may take some time + to work out the bugs from the major rewrite. + + This version could be called getID3lite. It makes a lot of checks + optional and makes it easy to remove support for undesired formats + + It also is more library-like. Older versions of getID3() declared + an incredible amount of global scope functions and defined several + constants. 1.7.0beta1 still declares constants, but they are all + prepended by GETID3_. It declares no global scope functions - they + are all wrapped into classes. + + » Made getID3() depend on iconv library: compile PHP --with-iconv + » Created new directory structure + Moved all demos to demos/ + Moved all getID3() files to getid3/ + Renamed most files to module.something + Changed header in all module.something to explain what they do + Simply remove all modules you don't need + Wrapped all modules into classes + * Bugfix: Implemented misc patches from Eugene Toder + * Bugfix: Implemented misc patches from "six" + ¤ Added root key 'encoding' + ¤ Added prefix GETID3_ to all defined constants. + ¤ Wrapped getid3.php into getid3 class + ¤ Wrapped getid3.functions.php into getid3_lib class + Removed unused functions + Moved several functions away from getid3.functions.php and + into the files where they are actually used. + Renamed getid3.functions.php to getid3.lib.php + Moved getid3.rgad.php functions into getid3_lib + Moved getid3.getimagesize.php funcitons ingo getid3_lib + ¤ Moved getid3.ogginfo.php into ogg module + ¤ Combined GetTagOnly() and GetAllFileInfo() in method analyze + ¤ Removed redundant and unuseful root keys + 'file_modified_time' == filemtime($filename) + 'md5_file' == md5_file($filename) + 'exist' == file_exists($filename) + ¤ Changed root key ['tags'] from array of string to array of array + of comments. + Simplified code for detecting base path. + Removed ob_ from InitializeFilepointerArray(). That was really a + ugly HACK to get output from fopen. If user want the reason, + he should open the file himself! + Checking for APE tags before lyrics3 - makes Lyrics3 not depend + on APE tag. It seems to work on my test file. + Changed ['error'] and ['warning'] in multiple files to append to + array instead of appending to string. That simplified code in + getid3.php too. + Simplified clean-up procedure: simply remove all empty root keys + Setting tags in individual modules instead of main getid3.php + Made Bonk and ASF modules non-dependent on id3 modules - id3 + optional. + Rewrote HandleAllTags() - simplified and convert comments to + desired encoding. + Replaced all calls to RoughTranslateUnicodeToASCII() in ASF module + with a TrimConvert() method. This uses iconv() for conversion. + It also converts from UNICODE instead of UTF-16BE, as the spec + says it should. + Replaced all calls to RoughTranslateUnicodeToASCII() in id3v2 + module with iconv(). id3v2 module also reads + $ThisFileInfo['encoding'] and converts all comments to this + format. All other formats just add their comments in their + native charset, but every comment field in id3v2 can have a + different encoding, so this is needed. + Did same thing as above with ISO module. However - it does not + work. I need to find out how to specify big-endian unicode != + UNICODING encoding name given to iconv(). + Built-in assume mp3 format in getid3.php + Temporarily nuked root key ['comments'] and CopyCommentsToRoot() + Updated demo/audioinfo.class.php + Updated demo/check.php - some thing don't work! + Other demos are out of order! + + +1.6.5: [October-06-2003] James Heinrich + » Added support for LiteWave (thanks supportØclearjump*com) + Ø Split out speedup info from ['OFR']['OFR']['compression'] into + ['OFR']['OFR']['speedup'] + Ø If EXIF functions for JPEG not available, now warning not error + Ø ID3v2 track number now returned as string (with leading zeros, + if present in data) rather than integer (thanks Plamen) + * Bugfix: now correctly parses cbSize element of WAVEFORMATEX + structure (thanks supportØclearjump*com) + * Bugfix: ASF module now reads non-standard field names, + i.e. "date" as well as WM/Year - patch from Eugene Toder. + * Bugfix: ASF module now returns genre as-is if it is not a + standard ID3v1 genre (thanks wonderboy) + * Bugfix: ['audio']['compression_ration'] missing for MPC + (thanks WaldoMonster) + Prevent infinite loop in MP3 histogram if framelength == 0 + Added wFormatTag values 0x00FF and 0x2001 - 0x2005 + (thanks steveØheadbands*com) + Added "twos" and "sowt" FourCCs for Mac AIFC + + +1.6.4: [June-30-2003] James Heinrich + » Added support for free-format MP3s + (thanks Sebastian Mares for the idea) + » Compressed (Flash 6+) SWF files are now handled properly + (thanks alan*cheungØalumni*ust*hk) + » Added DeleteLyrics3() to getid3.lyrics3.php + » Added FixID3v1Padding() to getid3.putid3.php + » Added new simple MP3-splicing sample file + (thanks tommybobØmailandnews*com for the idea) + New file: getid3.demo.joinmp3.php + » Moved all contents of getid3.putid3.php into either + getid3.id3v1.php or getid3.id3v2.php or getid3.functions.php as + appropriate + Removed file: getid3.putid3.php + ¤ ['error'] and ['warning'] keys now return as arrays, not strings + ¤ New root key for all files: ['file_modified_time'] (UNIX time) + ¤ getid3.demo.scandir.php renamed to getid3.demo.mysql.php + ¤ New demo file returns the MIME type only for a single file + (thanks adminØe-tones*co*uk for the idea) + New file: getid3.demo.mimeonly.php + ¤ Added check for valid ID3v1 padding (strings should be padded + with null characters but some taggers incorrectly use spaces). + A warning will be generated if padding is invalid. New boolean + key ['id3v1']['padding_valid'] indicates padding validity. + ¤ CleanUpGetAllMP3info() removes more useless root keys for + unknown-format files + ¤ Extended LAME information in ['mpeg']['audio']['LAME'] is now + only returned for LAME v3.90+ + ¤ LAME-encoded MP3s now return + ['mpeg']['audio']['LAME']['long_version'] as well as + ['mpeg']['audio']['LAME']['short_version'] - these are identical + in LAME v3.90+ but older versions will report longer more + detailed version information if available + ¤ New Lyrics3 values: ['lyrics3']['raw']['offset_start'] and + ['lyrics3']['raw']['offset_end'] + ¤ New optional parameter on getAPEtagFilepointer() to scan from a + defined offset rather than end-of-file to allow scanning of APE + tags before Lyrics3 tags + ¤ ['tag_offset_start'] and ['tag_offset_end'] are now present in + ['ape'], ['lyrics3'], ['id3v1'] and ['id3v2'] + ¤ Numerous changes to the returned structure and content for La + files, including parsing the seektable (if applicable) and + parsing RIFF data occuring after the end of the compressed audio + data (notably RIFF comments) + (thanks mikeØbevin*de) + ¤ getSWFHeaderFilepointer() now has optional 3rd parameter + $ReturnAllTagData (default == false) which if set to true will + return data on all tags in ['swf']['tags'] + ¤ ['swf']['bgcolor'] now returns the 6-character string + representing the background color in HTML hex color format + (thanks ubergeekØubergeek*tv) + ¤ ['swf']['header']['frame_delay'] is no longer returned + ¤ getQuicktimeHeaderFilepointer() now has two additional optional + parameters: $ReturnAtomData (default == true) and + $ParseAllPossibleAtoms (default == false). Setting + $ReturnAtomData to false will reduce the size of the returned + data array by unsetting ['quicktime']['moov'] before returning. + Leaving $ParseAllPossibleAtoms as false now suppresses parsing + of several atom types that contain very large tables of data + that are not typically useful. Atom type suppressed are: + stts, stss, stsc, stsz, and stco + (thanks ubergeekØubergeek*tv) + ¤ ['fileformat'] no longer set to 'id3' if ID3v1 or ID3v2 tag + detected but no other data format recognized + * Bugfix: La files now return the correct values for + ['avdataoffset'] and ['avdataend'] and therefore the correct + values for ['md5_data'] - note that ['md5_data'] values will not + match values from previous versions of getID3() - the previous + versions were incorrect + (thanks mikeØbevin*de) + * Bugfix: A temporary file was being created in the web server's + root directory (not DocumentRoot) each time ['md5_data'] was + calculated, and not removed due to lack of permissions. Temp + file is now created (as it was supposed to be) in the directory + of the file being examined, or the system temp directory, and + properly removed when done. + * Bugfix: Several incorrect values were being returned inside + ['mpeg']['audio']['LAME'] (thanks bouvigneØmp3-tech*org) + * Bugfix: SWF frame rates values were usually incorrect. + (thanks alan.cheungØalumni*ust*hk and ubergeekØubergeek*tv) + * Bugfix: ID3v2.2 files always flagged 4 bytes of invalid padding + (thanks marcaØmac*com) + * Bugfix: Lyrics3 without ID3v1 was not working properly + * Bugfix: Lyrics3, APE & ID3v1 can all now exist in the same file. + A warning is issued if APE comes after Lyrics3 (because Lyrics3- + aware taggers probably are not APE-aware and therefore won't be + able to find the Lyrics3 tag) (thanks mp3gainØhotmail*com) + * Bugfix: WriteAPEtag() now writes the APE tag before any Lyrics3 + tags (if present) and removes any incorrect ones that are after + existing Lyrics3 tags (thanks mp3gainØhotmail*com) + * Bugfix: RIFF-WAVE file with incorrect NumberOfSamples values in + the 'fact' chunk no longer cause incorrect playtime calculation + (thanks stprasadØindusnetworks*com) + * Bugfix: getid3.demo.simple.php had undefined variables if the + file needed to be deep-scanned with assumeFormat + * Bugfix: fixed previously-incorrect ['avdataend'] values for APE + and Lyrics3 tags in some cases, which in some cases means that + ['md5_data'] is different than previously (now correct) + Much-improved detection of AAC-ADTS, which also means MP3 + format detection should now be nearly twice as fast + Truncated AVIs and WAVs are now reported + Number of new features and bugfixes in getid3.demo.mysql.php + Quicktime 'meta' atoms now parsed, so Quicktime MP4 files can now + return artist, title, album, etc (thanks spunkØdasspunk*com) + Consolidated all comments processing functions (processing the + ['comments'] and ['tags'] keys) into HandleAllTags() which now + also checks to ensure that APE tags are really better than ID3v2 + before using them in ['comments'] + Known issue with Meracl ID3 Tag Writer v1.3.4 truncating last byte + of MP3 file when appending new ID3v1 tag now specifically noted + (rather than generic Probably Truncated File message) + getid3.demo.mysql.php now stores last-modified time for each file + getid3.demo.mysql.php is now case-sensitive for filenames + getid3.demo.mysql.php can generate M3U playlists of any of the + groups of files it can select (duplicate filenames, tag types, + etc.) + getid3.demo.mysql.php can now find mismatched tag contents and + filenames + getid3.demo.check.php now shows total number of errors & warnings + GetFileFormatArray() now matches actual patterns for MP3 files + based on the first two bytes of the file, rather than just the + first one + Simplified DeleteAPEtag() and made it work properly with Lyrics3 + + +1.6.3: [May-17-2003] James Heinrich + » Added support for Bonk (thanks ahØartemis*dk) + New file: getid3.bonk.php + » Added support for AVR (thanks ahØartemis*dk) + New file: getid3.avr.php + ¤ Contents of getid3.id3.php moved to getid3.id3v1.php + Removed file: getid3.id3.php + ¤ Contents of getid3.frames.php moved to getid3.id3v2.php + Removed file: getid3.frames.php + ¤ Returned data structure documentation improved and updated and + now stored in getid3.structure.txt rather than getid3.readme.txt + New file: getid3.structure.txt + ¤ Now including the GNU General Public License in the distribution + as getid3.license.txt + New file: getid3.license.txt + ¤ Added new, optional, parameter to WriteAPEtag() (and also + GenerateAPEtag()) which must be set to TRUE if the values you + are passing are already UTF8-encoded, otherwise all data is + encoded to UTF8 by default. For all ASCII/ANSI data this value + should be left at the defaul value of FALSE. + ¤ Added third, optional, parameter to getID3v2Filepointer() - + $StartingOffset (default == 0) which can parse an ID3v2 tag + in a file at a position other than the start-of-file. + ¤ ['video']['pixel_aspect_ratio'] now returned when known + ¤ AVI files with WMA audio now return ['audio']['dataformat'] + of 'wma' rather than 'wav' + ¤ ASF-WMA files now return the artist value from WM/AlbumArtist + in ['comments']['artist'] (thanks msibbaldØsaebauld*com) + ¤ ASF-WMA files now return the 'author' value from + ['asf']['content_description'] in ['comments']['artist'] + instead of ['comments']['author'] + ¤ ASF-WMA files now return the 'description' value from + ['asf']['content_description'] in ['comments']['comment'] + instead of ['comments']['description'] + * Bugfix: APE tag writing with multiple values for a tag (more + than one ARTIST for example) was not being correctly written + (thanks ahØartemis*dk) + * Bugfix: CreateDeepArray() was returning an empty-string key as + the top-level returned value - ['iso']['files'] now directly + contains the file listing without an empty array in between. + * Bugfix: ID3v2 genreid was not being returned in some cases. + * Bugfix: APEv1 tags would generate error messages + * Bugfix: APE tags would sometimes show phantom second entry for + each item (title, artist, etc) with no data + * Bugfix: APE tag writing was not UTF8-encoding the data - + non-ASCII characters (above chr(127)) were being incorrectly + stored (thanks ahØartemis*dk) + * Bugfix: getid3.demo.scandir.php had undefined function error + * Bugfix: getid3.demo.scandir.php would not display list of files + with no tags + Added link to getid3.demo.check.php from list of specific-tags + files in getid3.demo.scandir.php + + +1.6.2: [May-04-2003] James Heinrich + » New official mirror site for getID3() - http://www.getid3.org + » Added basic support for SWF (Flash) (thanks n8n8Øyahoo*com) + New file: getid3.swf.php + » Added experimental support for parsing the audio portion of + MPEG-video files. I don't have any actual documentation for + this, so this part is experimental and not guaranteed accurate, + but it seems to be working OK as far as I have been able to test + it. Bug reports (or even better - documentation!) are welcome at + info@getid3.org + » Added new simple directory-scanning sample file + New file: getid3.demo.simple.php + » getid3.demo.write.php now writes APE tags as well. + ¤ Renamed getid3.write.php to getid3.demo.write.php + ¤ Renamed audioinfo.class.php to getid3.demo.audioinfo.class.php + ¤ getid3.php now automatically includes the getid3.functions.php + function library file, no need to include it seperately. + ¤ getLyrics3Filepointer() has been changed to be consistant with + all the other similar function structures - the parameters have + changed. The old function has been renamed to getLyrics3Data() + ¤ Added DeleteAPEtag() function to getid3.ape.php + ¤ HandleID3v1Tag() now only handles ID3v1. Lyrics3 processing is + now done by HandleLyrics3Tag() + ¤ If BitrateHistogram is enabled in getOnlyMPEGaudioInfo() it now + also returns ['mpeg']['audio']['version_distribution'] showing + the number of frames of each MPEG version (1, 2 or 2.5) - all + frames *should* be of the same MPEG version + ¤ getID3v1Filepointer() always returns TRUE now, even if it didn't + find a valid ID3v1 tag + ¤ getOnlyMPEGaudioInfo() now looks for MPEG sync in the first 128k + bytes rather than the first 64k bytes + ¤ Added dummy function GetAllMP3info() to generate warning not to + use that deprecated function. + ¤ ['video']['codec'] is now 'MPEG' for all MPEG video files (this + will change to 'MPEG-1' or 'MPEG-2' as soon as I figure out how + to determine that) (thanks jigalØspill*nl) + ¤ ['mpeg']['audio']['LAME']['mp3_gain'] renamed to + ['mpeg']['audio']['LAME']['mp3_gain_db'] (gain in dB) + ¤ Added ['mpeg']['audio']['LAME']['mp3_gain_factor'] (gain as a + multiplication factor) + ¤ Added support for Preset and Surround Info bytes from LAME VBR + tag (http://gabriel.mp3-tech.org/mp3infotag.html) + * Bugfix: APE tag writing would put the string 'Array' for all + values rather than the actual data (thanks ahØartemis*dk) + * Bugfix: Warning now generated for VBR MPEG-video files because + getID3() cannot determine average bitrate. If you know of + documentation that would tell me how to do this, please email + info@getid3.org + * Bugfix: Replay Gain values from Vorbis comments are now + returned in ['replay_gain'] (and not in ['comments']) + (thanks ahØartemis*dk) + * Bugfix: Replay Gain values from APE comments are now correctly + returned in ['replay_gain'] (thanks ahØartemis*dk) + * Bugfix: getid3.demo.check.php is now case-insensitive when + assuming a format for a corrupted file if standard detection + does not identify the file type. + * Bugfix: RIFF comments were overwriting/suppressing ID3 comments + for RIFF-MP3 files (thanks wmØwofuer*com) + * Bugfix: RIFF-MP3 files with 'RMP3' chunks instead of 'WAVE' were + not being correctly identified. + * Bugfix: ID3v2 padding shorter than the length of an ID3v2 frame + header was not correctly detected + * Bugfix: getid3.demo.check.php now does in-depth scanning for MP2 + and MP1 files the same as for MP3 files based on file extension + if a MPEG-audio structure isn't found immediately at the start + of the file + * Bugfix: removed condition where RIFF-WAV was being scanned for + MPEG-audio signature when it shouldn't be present (non-MP3 WAV) + * Bugfix: ASF files were not always showing correct audio datatype + * Bugfix: array_merge_clobber() and array_merge_noclobber() were + not being conditionally defined in getid3.functions.php + (thanks rich.martinØreden-anders*com) + * Bugfix: stream_numbers was not being correctly returned in + bitrate_mutual_exclusion_object chunks of ASF files + * Bugfix: Added support for 24kHz and 12kHz audio in ASF files + * Bugfix: Removed possible undefined offset error in MP3s where + cannot find synch before end of file + * Bugfix: Removed potential out-of-memory crash situation when + parsing Real files with chunks larger than the available memory + (thanks jigalØspill*nl) + * Bugfix: ID3v1 was incorrectly taking precedence over ID3v2 in + the ['comments'] array (thanks lionelflØwanadoo*fr) + * Bugfix: No longer calculates overall bitrate and playtime for + VBR MPEG video files based on the audio bitrate. + * Bugfix: AssumeFormat was not working properly + Added summary footer line to getid3.demo.check.php + Added '.mpeg' to the list of assume-format-from-filenames list in + getid3.demo.check.php + MPEG-video files now more reliably detected + A number of additional features have been added to + getid3.demo.scandir.php + Added many RIFF-AVI audio types and fourcc video types to the + lookup functions in getid3.riff.php + Now identifes files with Lyrics3 v1 tags that are of incorrect + length (v1 Lyrics3 is supposed to be 5100 bytes long, but + [unknown program] writes variable-length tags (which is illegal + for Lyrics3 v1)). getID3() now correctly parses these tags and + issues a warning. + Split GetFileFormat() to GetFileFormat() and GetFileFormatArray() + HTML colors in getid3.demo.check.php are now defined as constant + variables at the top of the file (if you want to change them) + Added support for OptimFROG v4.50x (non-alpha) (new header fields) + (thanks floringhidoØyahoo*com) + Added support for Lossless Audio v0.4 (thanks mikeØbevin*de) + + +1.6.1: [March-03-2003] James Heinrich + » Added support for writing APE v2. + WriteAPEtag() in getid3.ape.php + NOTE: APE v1 writing support will *not* be added to future + versions of getID3() + (thanks ahØartemis*dk and adamØphysco*com for the idea) + » Added support for AIFF (Audio Interchange File Format) including + AIFF, AIFC and 8SVX (thanks ahØartemis*dk for the idea) + Removed file: getid3.aiff.php + » Added support for OptimFROG (v4.50a and v4.2x) + (thanks ahØartemis*dk for the idea) + New file: getid3.optimfrog.php + » Added support for WavPack (thanks ahØartemis*dk for the idea) + » Added support for LPAC (thanks ahØartemis*dk for the idea) + » Added support for NeXT/Sun .au format + New file: getid3.au.php + » Added support for Creative SoundBlaster VOC format + New file: getid3.voc.php + » Added support for the BWF (Broadcast Wave File) RIFF chunks + "bext" and "MEXT" (thanks Ryan and njhØsurgeradio*co*uk) + » Added support for the CART (Broadcast Wave File) RIFF chunks + (thanks Ryan) + » Added getid3.demo.scandir.php - a sample recursive scanning demo + that scans every file in a given directory, and all sub- + directories, and stores the resulting data in MySQL database, + and then displays a list of duplicate files based on md5_data + ¤ ['md5_data_source'] now contains the MD5 value for the original + uncompressed data for formats that store that information + (currently only FLAC v0.5+). ['md5_data'] (if chosen to be + calculated) will contain the calculated MD5 value for the + compressed file. To check if 2 files are identical in every way, + including all comments: compare ['md5_file']. To check if two + files were compressed from the same source file: compare + ['md5_data_source']. To check if the compressed audio/video data + of two files is identical, even if comments or even the + container file format is different (MP3 in RIFF container, + FLAC in Ogg container, etc): compare ['md5_data']. + ¤ ['md5_data'] for 8-bit WAV files is now calculated based on a + converted version of the data from unsigned to signed (MSB + inverted) to match the MD5 value calculated by FLAC + ¤ New optional parameter added to GetAllFileInfo() - + $MD5dataIfMD5SourceKnown (default: false). If false the md5_data + value will NOT be calculated for files (such as FLAC) that have + ['md5_data_source'] set, even if $MD5data == true. + (thanks ahØartemis*dk) + ¤ getid3.check.php renamed to getid3.demo.check.php + ¤ Added GetTagOnly() function to getid3.php - similar to + GetAllFileInfo() except only takes a filename as a parameter and + only returns ID3v2, APE, Lyrics3 and ID3v1 tag information - no + attempt is made to parse the data contents of the file at all. + (thanks Phil for the idea) + ¤ Added ['audio']['lossless'] and ['video']['lossless'] for all + formats (when known). Both are boolean values - true means the + data is lossless-compressed, false means the data is lossy- + compressed. + ¤ Added ['audio']['compression_ratio'] and/or + ['video']['compression_ratio'] for all formats. Returns a number + (usually) less than 1, where 1 represents no compression and 0.5 + represents a compressed file half the size of the original file + ¤ Added ['video']['bits_per_sample'] to all video formats (when + known) + ¤ Added ['video']['frame_rate'] to all video formats (when known) + ¤ ['fileformat'] set to 'mp1' or 'mp2' instead of 'mp3' when + ['audio']['dataformat'] is one of those (thanks ahØartemis*dk) + ¤ Added 4th parameter to md5_data(), $invertsign, which will invert + the MSB of each byte before MD5'ing. This is needed for 8-bit + WAV files because FLAC calculates the stored MD5 value on + signed data rather than the original byte values. ['md5_data'] + of an 8-bit WAV will now match the ['md5_data_source'] value + (thanks lichvarmØphoenix*inf*upol*cz) + ¤ ['ape']['items']['data'] and ['ape']['items']['data_ascii'] now + contains an array of values, if the tag contains UTF-8 text (as + opposed to binary data) + ¤ ['mpeg']['audio']['bitratemode'] renamed to + ['mpeg']['audio']['bitrate_mode'] + * Bugfix: Removed potential bug that could replace all MP3 file + contents with only the new ID3v2 tag in getid3.putid3.php + * Bugfix: md5_data values calculated for RIFF (WAV, AVI) files + were incorrect (thanks ahØartemis*dk) + * Bugfix: MP3 data in an MP4 wrapper fileformat could not identify + bitrate (thanks ahØartemis*dk) + * Bugfix: ['audio'] and/or ['video'] keys would sometimes get + removed even if not empty + * Bugfix: Prevented creation of null entries in + ['RIFF']['WAVE']['INFO'] if a comment entry was not present + * Bugfix: Potential infinite-loop condition in getid3.ogg.php + (thanks afshin.behniaØsbcglobal*net) + * Bugfix: Ogg files with illegal ID3v1 (and/or APE or Lyrics3) + tags were not finding the last Ogg page + (thanks afshin.behniaØsbcglobal*net) + * Bugfix: replay-gain values not properly set from LAME tag + * Bugfix: RIFF-MP3 had incorrect md5_data + * Bugfix: the LAME DLL CBR problem of not re-writing the LAME + frame at the beginning of the data is now detected for MP3s + with ID3v2 tags as well + * Bugfix: APE tags with multiple values (ie multiple entries in + the "artist" tag) are now shown properly in ['ape']['items'] + * Bugfix: fixed condition where APE tag with no ID3v1 tag could be + mistaken for APE tag with ID3v1 (and incorrectly parsed) + * Bugfix: added warning if ID3v2 frame has zero-length data + (thanks cmassetØclubinternet*fr) + * Bugfix: getid3.frames.php looking for non-existant key in USER + frames + Improved detection of RIFF-MP3 data. [unknown program] encodes + RIFF-WAV data with a chunk name of 'RMP3' instead of the + standard 'RIFF' + Encoder now returned in both ['comments'] and ['audio']['encoder'] + for RIFF-WAV files with an INFO.ISFT chunk + Generate a warning for FLAC files encoded with v0.3 or v0.4 + because audio_signature is not calculated during encoding + (thanks ahØartemis*dk) + Modified getid3.check.php to display md5_data_source as well as + md5_file and md5_data if display-MD5 mode is selected + Modified getid3.check.php to assume-format based on file extension + in browse mode if fileformat is found to be 'id3' (formerly only + if the fileformat was null) + Changed scaling of BitrateColor() from representing 1-256kbps to + representing 1-768kbps for better display of high-bitrate files, + specifically lossless-compressed CD-audio (FLAC, LA, etc) + + +1.6.0: [January-30-2003] James Heinrich + » Added support for OggFLAC (FLAC data stored in an Ogg container) + (thanks ahØartemis*dk for the idea) + » Added support for Speex (the data stored in an Ogg container) + » Comments are now available in the root 2-dimensional array + ['comments'] - each entry in this array will contain one or more + strings. For example, if there are two artists then + ['comments']['artist'][0] will contain the first one and + ['comments']['artist'][1] the other. All keys are forced + lowercase. Comments will be stored in the ['comments'] array in + this order of precedence: + 1) Native format tags (ASF, VQF, NSV, RIFF, Quicktime, Vorbis) + 2) APE tags + 3) ID3v2 + 4) Lyrics3 + 5) ID3v1 + Lower-priority tags will not overwrite or append existing values + of higher-priority tags (for example, 'artist' in ID3v1 will be + ignored if already specified in APE), but missing values will be + filled in (for example, if 'album' is specified in ID3v2 but not + in APE, it will be included in the ['comments'] array). + Note: Root keys (['title'], ['artist'], etc) are NOT available + in this or future versions of getID3(). + (thanks ahØartemis*dk) + » MD5 hashes are now available for all formats for both the entire + file (['md5_file']) and the portion of the file containing only + the audio/video data, stripped of all prepended/appended tags + like ID3v2, ID3v1, APE, etc - ['md5_data'] + (thanks ahØartemis*dk for alternate md5_file() function that + runs on UNIX system running PHP < 4.2.0) + NOTE: Ogg files require the use of vorbiscomment to obtain the + md5_data value. vorbiscomment must be downloaded from + http://www.vorbis.com/download.psp and placed in the getID3() + directory. All Ogg formats (Vorbis, OggFLAC, Speex) are affected + by this problem, but only OggVorbis files can be processed with + vorbiscomment. OggFLAC and Speex files will be processed by + getID3(), but this may result in an incorrect value for md5_data + in the event that VorbisComments are larger than 1 page (4-8kB). + NOTE: md5_data for Ogg will not work if PHP is running in Safe + Mode + » There is now a wrapper class available, written by Allan Hansen, + which should simplify extracting most common basic information + (such as format, bitrate, comments). + New file: audioinfo.class.php + » OggWrite() in getid3.ogginfo.php has been replaced with a new + version that uses vorbiscomment to write the comments, because + of a reported bug that can corrupt OggVorbis files such they + cannot be played. + NOTE: Ogg comment writing now requires the use of vorbiscomment + which must be downloaded from http://www.vorbis.com/download.psp + and placed in the getID3() directory. + NOTE: Ogg comment writing will not work if PHP is running in + Safe Mode + ¤ New root key ['tags'] is now always returned for all formats. + It is an array that may contain any of: + * Native format tags: 'vqf', 'riff', 'vorbiscomment', 'asf', + 'nsv', 'real', 'midi', 'zip', 'quicktime' + * Appended data tags: 'ape', 'lyrics3', 'id3v2', 'id3v1' + ¤ New root key ['audio'] is an array containing any or all of: + codec, channels, channelmode, bitrate, bits_per_sample, + dataformat, bitrate_mode, sample_rate, encoder + Note: This replaces several root keys, including: + bitrate_audio, bits_per_sample, frequency, channels + ¤ New root key ['video'] is an array containing any or all of: + bitrate_mode, bitrate, codec, resolution_x, resolution_y, + resolution_y, frame_rate, encoder + Note: This replaces several root keys, including: + bitrate_video, resolution_x, resolution_y, frame_rate + ¤ ['id3']['id3v1'] has moved to ['id3v1'] + ¤ ['id3']['id3v2'] has moved to ['id3v2'] + ¤ ['audiodataoffset'] and ['audiodataend'] have been renamed to + ['avdataoffset'] and ['avdataend'] respectively + ¤ GetAllMP3info() has been changed to GetAllFileInfo() with a + different parameter list ($allowedFormats is no longer a + parameter). Check your code where you're calling + GetAllMP3Info() - you will need to change both the function + name and the parameter list if you pass more than 2 parameters + ¤ All formats now return ['audio']['dataformat'] and/or + ['video']['dataformat'] where appropriate - this goes along with + ['fileformat'] - ['fileformat'] will return the actual structure + of the file, whereas ['dataformat'] will return the format of + the data inside that structure. For example, an Ogg file can + contain Vobis data (normal), or it can contain FLAC data in the + Ogg container format. In that case, ['fileformat'] would be + 'ogg', but ['dataformat'] would be 'flac'. + Note: this means that WAV and AVI files now return a + ['fileformat'] of 'riff' rather than 'wav' or 'avi'. + ¤ ['filesize'] is no longer returned for files larger than 2GB + because PHP does not support large file access. Attempting to + parse a file larger than 2GB will result in a message stored in + ['error'] and ['filesize'] not set. + ¤ APEtag, ID3v1, and ID3v2 are now supported on ALL multimedia + files - even if illegal by format. Ogg will return warning if + ID3/APE tags are present. (thanks ahØartemis*dk) + ¤ All files: non-critical errors are now returned in the root key + ['warning'] rather than ['error'] (only critical errors that + prevent getID3() from correctly parsing the file are returned in + ['error'] (thanks ahØartemis*dk) + ¤ Renamed all references to $MP3fileInfo to $ThisFileInfo + ¤ Joliet now supported for ISO-9660. + ['iso']['supplementary_volume_descriptor'] is now returned, if + available, and ['iso']['files'] will contain ASCII equivalents + of the Unicode directory structure & filenames stored. + ¤ Moved Monkey's Audio code from getid3.ape.php to seperate file. + New file: getid3.monkey.php + ¤ Added new keys for ISO-9660: ['name_ascii'] for directories, + ['file_identifier_ascii'] for files + ¤ Added root key ['track'] for CD-audio files + ¤ Ogg/Vorbis-comment files now have comments returned inside + ['ogg']['comments_common'] as an array of strings, rather than + simple strings in ['ogg'] + ¤ Quicktime files now have comments returned inside + ['quicktime']['comments'] as an array of strings, rather than + simple strings in ['quicktime'] + ¤ ['mime_type'] is a new root key returned for all supported + formats (thanks ahØartemis*dk) + ¤ ['fileformat'] now returns 'mp1' instead of 'mp3' for MPEG-1 + layer-I audio files (thanks ahØartemis*dk) + ¤ ['mpeg']['audio']['bitratemode'] now returns lowercase + ¤ MPEG-4 audio files which consist of MP3 data wrapped in a + Quicktime fileformat will now return the usual data in + ['mpeg']['audio'] + ¤ Type-1 DV AVIs are now supported + ¤ DV AVIs will return 1 or 2 in ['RIFF']['video'][x]['dv_type'] + ¤ Changed ['fileformat'] from 'mpg' to 'mpeg' for MPEG video files + ¤ ASF comments are now stored in ['asf']['comments'] instead of + ['asf'] + ¤ RealMedia chunk data is now returned inside ['real']['chunks'] + instead of ['real'] + ¤ ['replay_gain'] now properly populated from APE tags + ¤ Added support for ASF_Old_ASF_Index_Object in ASF files + (thanks ahØartemis*dk) + ¤ AAC-ADTS files now return ['aac']['bitrate_distribution'] + ¤ ParseVorbisComments() has been replaced with + ParseVorbisCommentsFilepointer() (with different parameters) + ¤ All references to any key ['frequency'] are now ['sample_rate'] + ¤ Moved ID3v2 comments from ['id3v2'] into common root + ['comments'] structure, and now returns more values than before + * Bugfix: ['iso']['files'] and ['zip']['files'] could potentially + contain duplicate entries (in a numeric-indexed array) for files + if the directory structure specifies files multiple times. + Entries are now guaranteed unique, with the last entry for the + file overwriting any former ones. + * Bugfix: RIFF parsing had numerous issues, including: + - large AVIs would take a very very long time to parse + - chunks with odd (not even) sizes would cause the parser fail + - video and/or audio codecs not always identified + The ParseRIFF() function has been completely rewritten and fixes + all known issues with RIFF parsing. Users are, however, + encouraged to double-check output of any parsed (AVI/WAV/CDDA) + files. + * Bugfix: Modified getid3.riff.php to return correct total + bitrates for AVIs with multiple audio streams + * Bugfix: GetFileFormat() was not creating array structure + correctly (thanks ahØartemis*dk) + * Bugfix: LAME tag for MP3s can only specify up to 255kbps, so any + files with actual CBR bitrate of >=256 were reported incorrectly + * Bugfix: Lyrics3 synched lyrics were not being correctly returned + * Bugfix: CreateDeepArray() was broken for non-nested cases, which + meant ZIP and ISO ['files'] structures were broken + * Bugfix: Incorrect pattern matching for ZIP files meant no zip + files were being detected as such + * Bugfix: AAC-ADIF was returning an incorrect number of channels + (too few) in some cases (thanks ahØartemis*dk) + * Bugfix: Vorbis comments were returning an incorrect value for + ['dataoffset'] in some cases + * Bugfix: MPEG video ['marker_bit'] and ['vbv_buffer_size'] were + incorrect + * Bugfix: ['playtime_string'] could potentially have a value of + x minutes and 60 seconds (ie 3:60 instead of 4:00) + Added support for FLAC cuesheets (FLAC 1.1.0+) + (thanks ahØartemis*dk) + Improved parsing speed in MP3, MP2 and AAC (thanks ahØartemis*dk) + Extra error-checking added to try and identify corrupt files for + most audio formats (thanks ahØartemis*dk) + More accurate playtime calculation for RealMedia + (thanks ahØartemis*dk) + Changed all relevant files to use ['audiodataoffset'] and + ['audiodataend'] rather than ['filesize'] where appropriate + (thanks ahØartemis*dk) + Added text encoding type 255 as a duplicate of UTF-16BE but with + Big-Endian rather than Little-Endian byte order + Added many RIFF-AVI audio types and fourcc video types to the + lookup functions in getid3.riff.php + Added numerous new known GUIDs to getid3.asf.php + Added PoweredBygetID3() function to easily get a "powered by" + string with the current getID3() version. + Added "Morgan Multimedia Motion JPEG2000" (MJ2C), "DivX v5" (DX50) + and "XviD" (XVID) codecs to list of known codecs in + getid3.riff.php + Changed GETID3_INCLUDEPATH path seperators to forced / + (from \ for Windows) + Modified getid3.check.php to only change \ directory seperators to + / on Windows operating systems + Modified getid3.check.php to handle larger-than-2GB files (which + now do not return a filesize) + Modified getid3.check.php to handle ['dataformat_audio'] and + ['dataformat_video'] + Modified getid3.check.php to show a list of present tags in one + column rather than one column for each of ID3v1, ID3v2, etc + Modified getid3.check.php to show MD5 values. Initially disabled + but can be enabled for a directory with a click. md5_file is + always calculated when displaying detailed info about a single + file; md5_data is calculated if the file is < 50MB + Modified getid3.check.php to show errors and warnings. Details are + visible with a mouseover or a click. + Changed getid3.check.php to use SafeStripSlashes instead of a + manual conditional directory name replacement for special + characters + Added sample recursive scanning sample code to getid3.readme.txt + (thanks lipisinØmail*ru for the idea) + + +1.5.7: [January-10-2003] James Heinrich + » Added support for ISO 9660 (CD-ROM image) format. Most-useful + data is directory structure returned under ['iso']['files'] + Note: Only ISO-9660 supported, not (yet) Joliet extension + (thanks nebula_djØsofthome*net for the idea) + New file: getid3.iso.php + ¤ ZIP files are now parsed by getID3() itself without relying on + built-in PHP functions and/or ZZipLib support. + (thanks Vince for the idea) + ¤ ZIP files now return a simple directory listing with filename + and filesize info only under ['zip']['files']. + Note: empty subdirectories will note appear in here, only files + and non-empty subdirectories. Information for all entries, + including empty subdirectories, is available under + ['zip']['central_directory'] (or under ['zip']['entries'] if the + Central Directory cannot be located (usually due to a trucated + file). + ¤ RIFF-WAV files with MP3 data (or MP3s with RIFF headers, if you + want to think of it that way) now have the MPEG audio portion + scanned and the usual data returned in ['mpeg']['audio'] if the + RIFF audio codec has wFormatTag of "85" (identified by getID3() + as "MPEG Layer 3") + (thanks ahØartemis*dk for the idea) + ¤ EXIF data (if present) is returned for JPEG files under + ['jpg']['exif'] (thanks nebula_djØsofthome*net) + ¤ ['filepath'] now returned for all files with the directory part + of the full filename. + ¤ ['filenamepath'] is now returned for all files (equivalent to + ['filepath'].'/'.['filename']) + * Bugfix: ['id3']['id3v2'][<framename>]['dataoffset'] was wrong + * Bugfix: MP3s tagged with iTunes have an invalid comment field + frame name ('COM ' - should be 'COMM') but the data is valid + otherwise; the frame is now renamed to 'COMM' and parsed + normally (with the error noted in ['error']) + (thanks kheller2Ømac*com for the sample file) + * Bugfix: Some ASF/WMA audio files were not being identified as + any format (thanks ahØartemis*dk) + * Bugfix: Warning now generated and ASCII format assumed for + invalid text encoding values in ID3v2 + * Bugfix: Changed ZIP detection pattern from 'PK' to 'PK\x04\x03' + * Bugfix: Ogg/FLAC files with large Vorbis comments were dying in + an infinite loop with lots of error messages due to missing $fd + parameter on ParseVorbisComments() (thanks ahØartemis*dk) + * Bugfix: ['data'] and ['image_mime'] were being returned for all + Ogg comments even if they were not images for versions of PHP + that have image_type_to_mime_type() built in (ie PHP 4.3.0+) + + +1.5.6: [December-31-2002] James Heinrich + » Added support for NSV (Nullsoft Streaming Video) + (www.nullsoft.com/nsv/) + (thanks demonØsoundplanet*com for the idea) + New file: getid3.nsv.php + » Added support for CD-audio track files (track01.cda etc) + ¤ Added standard ['frame_rate'] root value when known (AVI, NSV, + MPEG-video) + ¤ ASF files now report ['fileformat'] of: + 'wmv' when Windows Media Video codec v7/v8/v9 is used; + 'wma' when any 'Windows Media Audio' named audio codec is used + and no video stream is present; + 'asf' in all other cases (audio-only, video-only, or both) + ¤ Removed support for ZIP functions (will be rewritten to not + require ZZIPlib support in future versions) + ¤ Added function SafeStripSlashes() as a drop-in replacement for + stripslashes(), but that only strips slashes if magic_quotes_gpc + is set + ¤ Removed support for remote file scanning (HTTP / FTP) + ¤ Added ['aac']['frames'] (number of AAC frames in file) + ¤ Added ['mpeg']['audio']['frame_count'] when a bitrate histogram + is created + ¤ Average bitrate for VBR MP3/MP2 is calculated from actual counts + of frames of various bitrates (rather than relying on the header + values or filesize) when a bitrate histogram is created + ¤ RecursiveFrameScanning() split out into seperate function + (getid3.mp3.php) + ¤ Removed old function getMP3header() from getid3.mp3.php + ¤ Changed default MPEG_VALID_CHECK_FRAMES (number of mp3 frames + scanned to ensure a valid audio sequence has been located) from + 10 to 25. This means scanning will be slightly slower, but more + reliable/accurate + * Bugfix: ID3v2.2 - valid frame names not correctly detected + (thanks maeckerØweb*de for the sample file) + * Bugfix: ID3v2.2 - valid padding not correctly detected + (thanks maeckerØweb*de for the sample file) + * Bugfix: MIDI files with flat key signatures were not being + correctly reported (thanks alexleeisØshaw*ca for sample file) + * Bugfix: now returns message in ['error'] if file does not exist + * Bugfix: ['RIFF']['video'][x]['codec'] wasn't always being + correctly populated + * Bugfix: ['bitrate'] was incorrect for multi-stream RealMedia + * Bugfix: ['playtime_seconds'] was sometimes null or incorrect + for multi-stream RealMedia + * Bugfix: ChannelTypeID was incorrect in RVA2 ID3v2.4 frames + * Bugfix: Fixed potential divide-by-zero error for corrupt FLAC + files (thanks ahØartemis*dk) + * Bugfix: AAC-ADTS was not returning ['bitrate_mode'] unless + $ReturnExtendedInfo was TRUE (thanks ahØartemis*dk) + * Bugfix: LAME-encoded CBR MP3s now properly identified as CBR + with correct bitrate (thanks ahØartemis*dk) + * Bugfix: VBR MP2 (or headerless MP3) is now identified as VBR + rather than CBR. Note: to obtain VBR bitrate for headerless + files, the entire file is scanned and a histogram distribution + of bitrates is created, and the average bitrate calculated from + that. (thanks ahØartemis*dk for sample file) + Added support for DSIZ chunks in VQF, and checks to make sure size + of audio data matches DSIZ value, if present + (thanks ahØartemis*dk for sample file) + Rewrote GetAllMP3info() - removed some unneccesary code, changed + format-detection routine from ParseAsThisFormat() to + GetFileFormat() to allow for more flexible format parsing + (needed for ISO CD-ROM images, helpful for Quicktime and others) + Changed references in all files from string-cast indexes: ["$i"] + to non-cast indexes: [$i] where appropriate + Put a sans-serif 9pt style on all text in getid3.check.php + getAACADTSheaderFilepointer() now return TRUE if synch is lost + after the first frame has been successfully parsed (previously + it would return FALSE if synch was lost at any time, meaning the + file is most likely MP3, which was incorrect) + (thanks ahØartemis*dk for sample file) + Speed improvement code changes to getid3.mp3.php (up to 24% faster + in some cases) (thanks ahØartemis*dk for the code) + Changed all include_once() to require_once() + + +1.5.5: [November-25-2002] James Heinrich + » Added support for La (Lossless Audio - www.lossless-audio.com) + (thanks ahØartemis*dk for the idea) + New file: getid3.la.php + ¤ Moved lookup functions from getid3.lookup.php to the files where + they are used. + New file: getid3.id3.php + New file: getid3.rgad.php + Removed file: getid3.lookup.php + ¤ getID3v1Filepointer() returns FALSE if ID3v1 tag not found + ¤ Added new paramter "ReturnExtendedInfo" to the function + getAACADTSheaderFilepointer() in getid3.aac.php which now + defaults to FALSE - if TRUE then the data for every frame is + returned (containing aac_frame_length, adts_buffer_fullness and + num_raw_data_blocks, which aren't usually very useful). Speed + improvement with FALSE is about 35%. + ¤ Now returns fopen() errors in ['error'], for example if a remote + file is not accessible. + ¤ Changed default number of MP3 audio frames to scan to determine + if a valid stream has been found from 5 to 10, now also defined + as a constant at the top of getid3.mp3.php This will result in + slightly slower MP3 parsing, but greater reliability in + detecting false/invalid/corrupted VBR headers. + ¤ fopen() errors now displayed in getid3.putid3.php + (thanks miguel.dieckmannØhamburg*de) + ¤ Added 4th parameter to decodeMPEGaudioHeader() $ScanAsCBR which + will force an MP3 audio frame sequence to be force-scanned in + CBR mode. You should never need to call this directly, it's only + used internally to scan for MP3 files that have an illegal VBR + header with CBR data. (thanks fletchØpobox*com) + * Bugfix: ASF_Marker_Object in getid3.asf.php was always returning + an error in non-existant "reserved_1" and failing + * Bugfix: VBR bitrate calculations in getid3.mp3.php only occur if + ['mpeg']['audio']['VBR_frames'] is defined. + (thanks fletchØpobox*com) + * Bugfix: getid3.putid3.php no longer deletes original MP3 if + ID3v2 tag writing fails (thanks miguel*dieckmannØhamburg*de) + * Bugfix: incorrect order of if-statement error messages in + getid3.putid3.php (thanks miguel*dieckmannØhamburg*de) + getid3.asf.php now notes the error and continues parsing rather + than failing when it encounters an error parsing a chunk + Now actually scan 1000 frames for AAC ADTS as reported in the + v1.5.4 changelog, rather than 100. (thanks ahØartemis*dk) + Improved scanning speed in getAACADTSheaderFilepointer() by ~30% + (thanks ahØartemis*dk for the fix) + Added FileSizeNiceDisplay() function to getid3.functions.php for + formatting filesize output in kB, MB, GB, etc. + + +1.5.4: [October-07-2002] James Heinrich + » Added support for Quicktime. + New file: getid3.quicktime.php + » Added support for AAC files, both ADTS and ADIF header formats. + ADIF format is a pain because it's very similar to standard MP3 + header format, and it's hard to distinguish between the two. I + have tried to make the detection accurate, but I have a limited + number of AAC test files to play with so if you have an AAC file + that gets detected as MP3/MP2 (or vice-versa), please send me + the details via email at getid3Øsilisoftware*com + ADTS format is very slow to parse because to get the bitrate of + VBR files the whole file has to be stepped through frame by + frame (getID3() scans up to the first 1000 frames and assumes + that to be close enough). + Note: I would suggest commenting out support for AAC (see top of + GetAllMP3info() function in getid3.php) unless you need it. + (thanks jfaulØgmx*de for the idea and sample Delphi source code) + New file: getid3.aac.php + » Added bitrate distribution analysis option for MP3 VBR files. A + new boolean parameter for getOnlyMPEGaudioInfo() enabled this + feature which steps through the MP3 file frame by frame and + counts how many frames of each bitrate exist. This information + is returned in ['mpeg']['audio']['bitrate_distribution'] + Caution: this feature is very inefficient for large files and + takes a very long time and does lots of disk I/O. Use with care. + ¤ Changed layout of allowedFormats in GetAllMP3info() function in + getid3.php to allow easy removal of support for any of the + supported format. As stated above, I recommend commenting out + AAC unless needed. + ¤ Added ['flac']['compressed_audio_bytes'], + ['flac']['uncompressed_audio_bytes'], and + ['flac']['compression_ratio'] + ¤ Replaced FXPT2DOT30toFloat() function with FixedPoint2_30() + * Bugfix: getid3.mpc.php was slightly miscalculating the number of + samples, therefore also bitrate and playtime + (thanks ahØartemis*dk for the fix) + * Bugfix: MonkeyCompressionLevelNameLookup() didn't know about + 'insane' compression (thanks ahØartemis*dk for the fix) + * Bugfix: MonkeySamplesPerFrame() was incorrect for MAC v3.95+ + (thanks ahØartemis*dk for the fix) + * Bugfix: getid3.check.php wasn't processing the assumeFormat + directive when (register_globals == off) + * Bugfix: detecting of synch pattern for MP3 files with invalid + data at the beginning wasn't always correct, also meant possible + incorrect bitrate/duration/etc info for such corrupt files. + getid3.functions.php now includes a replacement utf8_decode() + function for those PHP installations that are not configured + with the --with-xml option. (thanks stephaneØtekartists*com) + + +1.5.3: [September-30-2002] James Heinrich + » Added support for VQF. (thanks mtØmansonthomas*com for the idea) + New file: getid3.vqf.php + » Added support for FLAC. Comments, if present, are returned under + ['ogg'] because they follow the Ogg Vorbis structure standard. + New file: getid3.flac.php + ¤ OS/2-format bitmaps are now correctly interpreted. The format of + the bitmap is now returned in ['bmp']['type_os'] and + ['bmp']['type_version']. OS/2 bitmaps can be v1 or v2, Windows + can be v1, v4 or v5 + + +1.5.2: [September-25-2002] James Heinrich + » Support for RealMedia (audio & video) added + Note: only tested on G2 and v5 audio and video files - if anyone + has older and/or newer sample files, please test it and/or send + me the sample files. + (thanks stephaneØtekartists*com for idea) + New file: getid3.real.php + » Support for BMP added. Palette and pixel data can optionally be + extracted as well - this is slow and generally unneccesary, but + the option is there if you need it. Also includes PlotBMP() + which will take the extracted pixel data and output it as a true + color PNG. This function requires GD v2.0+ + Note: Untested on 16-bit and 32-bit BMPs because I couldn't find + any sample files - if you know of a program that can create such + files, please email getid3Øsilisoftware*com + Note: Support for RGB (uncompressed), RLE8 and RLE4 is included + and tested. BITFIELDS support is also included for 16- & 32-bit + formats, but it's untested, so if anybody has any test files + please send them to getid3Øsilisoftware*com + Note: Support currently only for Windows-format BMPs, and trying + to parse an OS/2-format bitmap leads to unpredictable/invalid + results. + New file: getid3.bmp.php + » PNG now fully parsed, including all information chunks + New file: getid3.png.php + ¤ Support for GIF/JPG/PNG moved to seperate files and expanded, + including standard ['resolution_x'] and ['resolution_y'] as well + as more thorough parsing of header information + New file: getid3.gif.php + New file: getid3.jpg.php + table_var_dump() simplified and now outputs {-style character + entities for characters outside the normal alphanumeric range + CleanOggCommentName() changed to a regular expression + (thanks chris-getid3Øbolt*cx for rewriting the function) + + +1.5.1: [September-20-2002] James Heinrich + » Added support for MPEGplus/Musepack SV7. ['fileformat'] is 'SV7' + for version 7 files (versions 4, 5 ,6 and 8 are not supported + yet, but will be of ['fileformat'] SV4, SV5, SV6 and SV8) when + they are supported (thanks Christian Fritz for the idea) + New file: getid3.mpc.php + ¤ ['bitrate_audio'], ['bitrate_video'], ['bitrate_mode'], + ['channels'], ['resolution_x'], and ['resolution_y'] keys added + for all appropriate formats + ¤ Ogg files with a COVERART comment now save and display the + attached image the same way as is done with ID3v2 APICs + ¤ ['ogg']['comments'][n]['data'] and + ['ogg']['comments'][n]['dataoffset'] is now returned for all + comments. ['ogg']['comments'][n]['data'] is only useful if + the field is supposed to contain binary data. It is a + base64_decode()'d version of ['value']. + ['ogg']['comments'][n]['dataoffset'] is the byte offset in the + file at which the 'COMMENTNAME=value string' starts, not the + start of just 'value' + ¤ ['ogg']['comments'][n]['image_mime'] is now returned if + ['ogg']['comments'][n]['data'] contains valid image data. + ¤ More than 3 Ogg pages may now be read in, if the comment data + is longer than 1 page (usually about 4kB) + ¤ ['fileformat'] is now 'mp2' rather than 'mp3' if it's MPEG-1, + Layer-II audio + ¤ ASF bitrates now calculated even if stream_bitrate_properties + object not present + ¤ ['asf']['stream_properties_object'] is now a numeric-key array + with one entry for each stream - the key being the stream number + ¤ ['replay_gain'] is returned for all audio formats that support + it (MP3-LAME, ID3v2, Ogg) (thanks Christian Fritz for the idea) + ¤ ['mpeg']['audio']['LAME']['RGAD']['radio_replay_gain'] is now + ['mpeg']['audio']['LAME']['RGAD']['radio'] (same for audiophile) + ¤ ASF/WMA files now use WM/Track to get track number from if + WM/TrackNumber is not available (thanks stephaneØtekartists*com) + ¤ ASF/WMV files now returns ['year'] and ['asf']['year'] + ¤ ASV/WMV files now use ['content_description']['description'] for + the ['comment'] field (thanks stephaneØtekartists*com) + ¤ ['track'] is now always returned as an integer + * Bugfix: Ogg comments that are larger than one data page (usually + about 4kB) are now correctly parsed (thanks Christian Fritz) + * Bugfix: Ogg comment data is now UTF8-decoded + * Bugfix: Ogg comment writing now UTF8-encodes the data + * Bugfix: playtime for ASF files was off by <preroll> (usually + between 3 and 12 seconds) + * Bugfix: ['asf']['stream_properties_objects']['flags'] data was + possibly incorrect + * Bugfix: ASF Padding Object was overwriting + Stream Bitrate Properties Object data (now returned correctly in + ['asf']['padding_object'] + * Bugfix: ASF Marker Object Reserved_2 field was incorrect + * Bugfix: ASF Bitrate Mutual Exclusion Object had incorrect stream + numbers + Warning displayed if incorrectly-formatted Ogg comment is present + (known to be an issue with CDex v1.40, but fixed by v1.50b7) + (thanks Christian Fritz) + Ogg comment writing now checks for valid comment names + Added bitrate column in getid3.check.php, and added some formatting + (font, colour) + Performance tweaks using bitwise math instead of binary string + operations + + +1.5.0: [September-18-2002] James Heinrich + » Ogg comment writing support added. getid3.write.php has been + updated to allow for writing comment tags to both MP3 and Ogg. + Big thanks to Chris Bolt <chris-getid3Øbolt*cx> for writing the + OggWrite() function and offering it for inclusion in getID3() + New file: getid3.ogginfo.php + » Support for Monkey's Audio and APE tag added. + (thanks Christian Fritz for the idea) + New file: getid3.ape.php + ['fileformat'] now returns 'mac' for Monkey's Audio files, or + 'ape' for files with an APE tag (Monkey's Audio or other format) + » getid3.thumbnail.php has been removed from the distribution and + the table_var_dump() function now outputs APICs as seperate + files in the same directory as the analyzed file. This should + make the image-displaying more reliable as well as reduce + complexity. The naming convention for the images is + filename.ext.[byte offset of APIC data].[jpg|gif|png] + If anybody still has any problems with corrupted images please + let me know at getid3Øsilisoftware*com + » Support for extended Xing/LAME tag + (see http://users.belgacom.net/gc247244/extra/tag.html) + Data is returned in ['mpeg']['audio']['LAME'] + ¤ ['ogg']['tracknumber'] has been renamed to ['ogg']['track'] and + ['track'] is now returned in the root of the array + ¤ ['ogg']['pageheader'][n]['flag'] has been renamed to + ['ogg']['pageheader'][n]['flags'] and the unprocessed flag byte + is available in ['ogg']['pageheader'][n]['flags_raw'] + ¤ ['frequency'] is now returned for WAVE files in the root of the + array (thanks danielØelectroteque*org) + ¤ ASF files now return codec, bitrate, resolution, etc information + under ['asf']['video_media'] or ['asf']['audio_media'] + * Bugfix: RVA2 and EQU2 writing in getid3.putid3.php were + incorrectly writing Volume Adjustment field + * Bugfix: EQU2 in getid3.frames.php was reading Volume Adjustment + as unsigned integer instead of signed integer + * Bugfix: handling of remote files over HTTP & FTP was broken + (thanks Vince) + * Bugfix: incorrect handling of some ASF packets + ASF/Windows Media format now more fully parsed, including Index + Objects + Added several new fourCC video codecs + + +1.4.3: [September-15-2002] James Heinrich + » Now parses ASF / WMV / WMA files + ¤ New file: getid3.asf.php + * Bugfix: RoughTranslateUnicodeToASCII() would return nothing + if didn't find a terminator it was expecting + Added FILETIMEtoUNIXtime() function (for converting 64-bit + Microsoft FILETIME timestamps, used in ASF files and elsewhere, + to UNIX Epoch timestamps) + Added GUIDtoBytestring() and BytestringToGUID() functions + + +1.4.2: [September-12-2002] James Heinrich + » getID3() now requires PHP v4.1.0 or higher because it now is + designed to work with register_globals = off and the new auto- + globals ($_GET, $_SERVER, etc). + * Bugfix: VBR MP3 files with Fraunhofer-style VBR header were not + being correctly detected in most cases + (thanks dkushnerØoddcast*com and mikeØftl*com for sample files) + * Bugfix: IsValidTextEncoding() was broken + * Bugfix: Add stripslashes($EditorFilename) to getid3.write.php + (writing was broken for files with ' or " in the filename) + (thanks mikeØftl*com and kthejoker) + * Bugfix: If there is garbage data between a valid VBR header + frame and a sequence of valid MPEG-audio frames the VBR data is + no longer discarded. (thanks to mikeØftl*com for sample + Fraunhofer-style VBR file produced with MusicMatch v7.2) + ¤ Changed variable system to work with (register_globals = off) + ¤ Moved relevant code into seperate PlaytimeString() function + ¤ Added nl2br() to table_var_dump() for cleaner output + ¤ Now returns the following keys from Fraunhofer-VBR files: + ['VBR_seek_offsets'], ['VBR_seek_offsets_stride'], + ['VBR_offsets_relative'] and ['VBR_offsets_absolute'] + ¤ Added ID3v1matchesID3v2() function and implemented in + getid3.check.php (thanks to "Guest" in the forums for the idea) + Changed amount of data read in getid3.getimagesize.php from 10kB + to entire file. (thanks mikeØftl*com) + Wrapped function_exists() checks around function definitions in + getid3.functions.php + Fixed a lot of E_WARNING and E_NOTICE situations, especially in + ID3-writing code (getid3.putid3.php, etc) + Added checks to make sure all needed data is available for writing + ID3v2 tags + + +1.4.1b5: [May-30-2002] James Heinrich + * Bugfix: Unsynchronise() was broken, now fixed + (thanks mikeØftl*com) + * Bugfix: GenerateID3v2Tag() now correctly uses non-synchsafe + integers for frame size descriptors in ID3v2.3 and ID3v2.2 + (thanks mikeØftl*com) + ¤ Added ['artist'], ['title'], etc keys to root of returned + array to provide a common place to access any returned info + from any file type. Currently gets info from ID3v1, ID3v2, + Ogg, and RIFF/WAVE. Possible returned keys are: + title, artist, album, year, genre, comment, track + ¤ Modified LookupGenre() function to search for either genre based + on numeric ID, or now reverse lookup as well + ¤ Added ['artist'], ['title'], etc keys to ['RIFF'] information + if info tags are present + Added functionality to attach a picture to the ID3v2 tag in + getid3.write.php + Sorted genres into alphabetical order (special 3 at end of list) + in getid3.write.php + Changed the comment-edit field in getid3.write.php to a multi-line + <textarea> from a single-line <input> + getid3.write.php now only writes ID3v2 frames that have data + Added default TXXX field to getid3.write.php to put a tagger info + field when writing ID3v2 tags. Description is "ID3v2-tagged by" + and data is "getID3() v[version] (www.silisoftware.com)" + Changed getid3.check.php to use the new common info keys + Improved file-format detection in getid3.check.php - if the auto- + detect based on the first few bytes of the file doesn't find a + known format (for example if the header is corrupt), a more + thorough scan is done based on the file extension + Added 'Edit ID3' link from getid3.check.php to getid3.write.php for + MP3 files (thanks maxØgutalin*com for the idea) + Added 'Delete file' link from getid3.check.php to getid3.write.php + allowing you to permanently delete a file (be careful with this!!) + (thanks maxØgutalin*com for the idea) + Added some mouse-over titles for links in getid3.check.php + + +1.4.1b4: [May-15-2002] James Heinrich + * Bugfix: getid3.check.php wasn't parsing MP3s with invalid headers + or padding at the beginning of the file - added 'assumeFormat' + parameter and 'Parse this file as:' options to force parsing in a + particular format (thanks Alcohol for the sample file) + * Bugfix: unset(['fileformat']) and ['error'] added in cases where + file cannot be parsed in the assumed or forced format + + +1.4.1b3: [May-01-2002] James Heinrich + ¤ For Ogg files, now calculates the real average bitrate (returned + in ['ogg']['bitrate_average']) and so the playtime of the file is + calculated on actual average bitrate, not nominal bitrate, so it + should be accurate now (thanks to stephaneØtekartists*com for + telling me it was wrong) + * Bugfix: ID3v2FrameIsAllowed() wasn't behaving properly if the + writing functions were called for more than one file, because of + the static array not being cleared between uses. This is an + updated fix because the one in 1.4.1b2 didn't work :o) + (thanks soulcatcherØevilsoft*org and yoyo) + Added rawurlencode() to the filename parameter in table_var_dump() + for images (wouldn't work with path/file names containing special + characters (#, &, ", +) (thanks Christian Fritz) + getid3.check.php no longer attempts to scan all MIDI tracks in + directory-browse mode, since this can take a long time. Detailed + single-file view is still fully scanned (new third parameter for + getMIDIHeaderFilepointer() controls this) + Small improvements to MoreNaturalSort() + + +1.4.1b2: [April-18-2002] James Heinrich + ¤ GetAllMP3Info()'s 2nd parameter has changed from boolean to string + (now specifying the parse-this-file-as-this format, like 'mp3', + but also can be FALSE to mean don't assume any format, auto-detect + only), and a third parameter (array containing allowed formats) + has been added. The assumedFormat parameter allows a file to be + forced to be parsed as a certain format rather than relying on the + auto-detection of getID3() (ex: an MP3 wrapped in a RIFF/WAV + header will be auto-detected as RIFF/WAV, but if forced to parse + as MP3 will extract the original MP3 information) + (thanks reel_tazØusers*sourceforge*net) + * Bugfix: ID3v2FrameIsAllowed() wasn't behaving properly if the + writing functions were called for more than one file, because of + the static array not being cleared between uses (thanks yoyo) + * Bugfix: Lyrics3 data wasn't being properly copied from the ['raw'] + keys to the easy keys (['title'], etc.) (thanks Christian Fritz) + * Bugfix: some testing code was accidentally left in + getid3.thumbnail.php (thanks Christian Fritz) + * Bugfix: RIFF/WAVE files are now more likely to have all their + chunks parsed. + * Bugfix: RIFF/WAVE bitrate & playtime now better calculated + * Bugfix: MP3 scanning for synch doesn't go beyond 64k now, to stop + intensive scanning through large file that don't have a synch + (thanks soulcatcherØevilsoft*org for a weird sample file) + Improved performance when scanning for MP3 synch (about 600% faster + if the synch is never found) + ZIP files no longer return the contents of each compressed file, as + that would very easily be more data than PHP could handle. + (thanks davidbullockØtech-center*com) + getid3.check.php now displays entries in a more natural sort order: + case insensitive, ignores most punctuation, treats accented chars + the same as their unaccent equivalent (thanks mikeØftl*com) + Added support for SmartSound-format RIFF files (which are regular + RIFF/WAVE files with the first 4 chars changed from RIFF to SDSS) + All instances of while(list() = each()) replaced with foreach() + + +1.4.1b1: [April-11-2002] James Heinrich + » Parses MIDI files. + NOTE: very slow at parsing, much slower than any other file type + NOTE: playtime is generally mostly accurate, but not always 100% + » Parses ZIP files (if ZZIPlib available, and only in PHP 4.0.7RC1 + and later (see http://www.php.net/manual/en/ref.zip.php) + NOTE: currently untested as I'm unable to find php_zip.dll for + PHP/Win32 - if someone has a copy of this file, please email me: + getid3Øsilisoftware*com + » Parses JPEG files (requires GD installed) + » Parses PNG files (requires GD v1.6+ installed) + » Parses GIF files (requires GD < v1.6 installed) + » For MP3s, once a valid synch is detected, the next 5 frames are + also scanned for valid synch signatures, to prevent false + identification of synch. For corrupt MP3 files this will be a bit + slower, but hopefully produce more reliable results. + (Thanks to mpdjØbtinternet*com for bringing this to my attention, + and xbhoffØpacbell*net for explaining what was happening) + (Thanks also to macik for helping me with MP3 frame lengths: + http://66.96.216.160/cgi-bin/YaBB.pl + ?board=c&action=display&num=1018474068) + » The actual image data is now displayed (for JPEG, PNG and GIF + images only) rather than a binary text dump in getid3.check.php + (specifically table_var_dump()) for APIC frames. Made possible + by the inclusion of (a modified version of) GetURLImageSize() by + Filipe Laborde-Basto (www.rezox.com). You can right-click, save-as + to extract the image to a file. + NOTE: The actual image data is still returned in ['data'] + ¤ ['image_mime'], ['image_width'], ['image_height'], ['image_bytes'] + are now returned for APICs + ¤ split parsing functions out into seperate files: lyrics3, id3v1, + id3v2, mp3, ogg, riff, mpeg, midi, zip + ¤ ['ogg']['bitrate_ave'] -> ['ogg']['bitrate_nominal'] (thanks to + stephaneØtekartists*com for pointing out that "nominal" bitrate + may actually differ significantly from the "average" bitrate) + The real average bitrate seems to be only extractable by parsing + the entire file and calculating the average bitrate. This is not + yet an option, but hopefully in a future version of getID3() + ¤ ['filename'] now returned for all files + ¤ ['ogg']['date'] and ['ogg']['description'] now returned when + available (thanks stephaneØtekartists*com) + ¤ ['mpeg']['audio']['crc'] now contains the CRC (if present) + ¤ ['bitrate'] is now returned as a double instead of an int + ¤ ['dataoffset'] is now returned for all ID3v2 frames + * Bugfix: MP3 CRC presence ['mpeg']['audio']['protection'] was being + reported as opposite of what it actually should be + * Bugfix: MPEG videos weren't being detected (they were being + parsed as MP3), and even if they were, there was a typo in + getMPEGHeaderFilepointer() (thanks Christian Fritz) + * Bugfix: getid3.functions.php wasn't being included in + getid3.write.php (thanks mikeØftl*com) + * Bugfix: Browse:___ directory name in getid3.check.php wasn't + correct with directory names with ' and other strange characters + (thanks Christian Fritz) + ID3v2FrameProcessing() now checks to see if the next frame is valid + after it encounters an invalid FrameID, and if the next frameID + appears valid, it will just skip the current (invalid) frame and + continue processing (it would previously abort at the first sign + of incorrect structure) (thanks stephaneØtekartists*com) + getid3.check.php now scans filetypes based on content, not filename + extension, and shows the filetype in the displayed output. Files + are only scanned as MP3 if ID3v2 or MPEG-audio signatures are at + the immediate beginning of the file (MP3 used to be the default + format), so a corrupt file may not show up as MP3 format in the + browse screen, but in detail it will scan in-depth + getid3.check.php now has columns to show the presence of ID3v1, + ID3v2 and Lyrics3 content + Helium2 (www.helium2.com) has been known to write ID3v2.4 tags with + non-synchsafe-integer framesizes, getID3() now checks for this and + will override and parse the tag as ID3v2.3 if the tag would parse + fine as ID3v2.3 when it's really specified as ID3v2.4 (thanks + Christian Fritz for the test files) + + +1.4.0b9: [April-05-2002] James Heinrich + » Ogg files now return bitrate and playtime (playtime calculated + from nominal bitrate and filesize, so it's only approximately + accurate). (thanks stephaneØtekartists*com for the idea) + * Bugfix: ID3v1 tags were not properly being parsed - track, genre + and comment fields were incorrect. (thanks Christian Fritz) + * Bugfix: getid3.check.php would not browse directories with single + quotes (') or double quotes (") in the directory name. + (thanks Christian Fritz) + * Bugfix: Improved detection of MPEG-video files (a sample MP3 file + had a false MPEG video signature at the beginning), and the MPEG- + video parsing function now only looks for the MPEG-video header + in the first 100k bytes of the file, to prevent needlessly + scanning very large files. Also will not infinitely loop if it + does not find what it's looking for. (thanks Christian Fritz) + ['error'] now returned if MP3 synch doesn't occur at beginning of + file if ID3v2 not used (ie there's some kind of padding there that + should not be) + Reduced use of fread() in getMPEGHeaderFilepointer() (now faster) + Added "file parsed in x.xxx seconds" to getid3.check.php + Added "browse: <directory>" link to getid3.check.php + Changed default ID3v2 majorversion from 2.4 to 2.3 in + getid3.write.php because Winamp (and probably many other + ID3v2-aware tools) can only read up to ID3v2.3 + (thanks mikeØftl*com) + + +1.4.0b8: [April-04-2002] James Heinrich + » Lyrics3 support added (thanks Christian Fritz for the idea) + ¤ check.php renamed to getid3.check.php + ¤ write.php renamed to getid3.write.php + ¤ ['id3']['id3v2']['error'] (if present) now reported in ['error'] + ¤ ['mpeg']['audio']['error'] (if present) now reported in ['error'] + * Bugfix: RoughTranslateUnicodeToASCII() was completely mangling + UTF-16/UTF-16BE encoded text + * Bugfix: The warning about MP3ext wasn't always showing up + (thanks davidbullockØtech-center*com) + getID3v1Filepointer() cleaned up & shortened + Moved the include_once() statements around so that a minimum of code + is included + + +1.4.0b7: [April-03-2002] James Heinrich + » RIFFs (specifically AVIs) are now more completely parsed, + almost everything in the returned ['RIFF'] array has been moved + around and/or restructured. A lot of new data is in there too - + codecs, frame size, etc. + ¤ Better recursive parsing of RIFFs (sub-arrays are now in the right + place) + * Bugfix: the isset() idea introduced in beta 5 was incorrectly + implemented, such that ['asciidata'] and ['asciidescription'] were + never returned - this had the side effect that ID3v2 comments were + not copied to ['id3']['id3v2']['comment'] (thanks mikeØftl*com) + * Bugfix: MPEG audio synch wasn't being detected, and therefore MPEG + audio data not parsed, if no ID3v2 header present in an MP3 + ID3v1 track number only returned if greater than zero + Removed !== FALSE (introduced in 1.4.0b6) from while(fread()) loops, + some users were reporting problems with that syntax. + Changed substr($string, 0, 1) to $string{0} syntax in most files + Reformatted changelog.txt to 72-column width + + +1.4.0b6: [April-01-2002] James Heinrich + * Bugfix: 1.4.0b5 introduced a bug where any RIFF file other than + PCM WAVE (this includes any compressed WAV, as well as all AVIs) + would crash getID3() + Reduced use of fread() in getOggHeaderFilepointer() for increased + speed + Added constant FREAD_BUFFER_SIZE for many fread() operations + Added !== FALSE check to while(fread()) loops + (thanks davidbullockØtech-center*com) + Added more entries to RIFFwFormatTagLookup() + (still looking for a good complete list) + Converted use of hexdec() in getid3.lookup.php to 0x1234 notation + + +1.4.0b5: [March-28-2002] James Heinrich + ¤ Renamed decodeheader() to decodeMPEGaudioHeader() + * Bugfix: Fixed infinite loop problem for RIFF/WAV files with + unknown chunks + * Bugfix: WXXX frames were incorrectly writing from ['URL'] instead + of ['url'] + * Bugfix: RoughTranslateUnicodeToASCII() wasn't properly decoding + UTF-16/UTF-16BE + Changed all quoted strings from " to ' to hopefully improve speed + (although benchmarks have not yet shown any significant + improvement in speed) (thanks davidbullockØtech-center*com) + Improved code in check.php for dealing with symbolic links + (thanks davidbullockØtech-center*com) + Changed '<?' tags to '<?php' (thanks davidbullockØtech-center*com) + Added processing time indicator in check.php + (ie 'directory scanned in 2.45 seconds') + Replaced all instances of feof() to prevent infinite loop conditions + Moved lookup portions of decodeMPEGaudioHeader() to + getid3.lookup.php + Replaced $arrayname[$index] with $arrayname["$index"] to avoid PHP + E_NOTICEs (thanks davidbullockØtech-center*com) + Wrapped isset() around many if statements, to avoid PHP E_NOTICEs, + hence improve speed (up to 30x speed improvement reported in some + cases :) + + +1.4.0b4: [March-26-2002] James Heinrich + ¤ RIFF/WAV file format now parsed, returned under ['riff'] + ¤ Support for Relative Gain Adjustment in RIFF/WAV files + ¤ ['channels'] (1 or 2) now returned for MP3 and WAV files + ¤ ['bitrate'] now returned (in bits-per-second) at root level for + MP3 and WAV files + Added support for RGAD (Relative Gain ADjustment) ID3v2 frames, both + reading & writing + (see http://privatewww.essex.ac.uk/~djmrob/replaygain/ for details + on RGAD) (thanks Christian Fritz for the idea) + Removed some test data-dumping from the ID3v2 writing functions + Language code 'XXX' now returns descriptive string 'unknown' instead + of NULL + Seperated out comments from top of getid3.php into getid3.readme.txt + and changelog.txt + Split out non-lookup functions from getid3.lookup.php to + getid3.functions.php + + +1.4.0b3: [March-25-2002] James Heinrich + ¤ ['asciidata'] for WXXX frames now returns correct information, but + under ['asciidescription'] (thanks Christian Fritz) + ¤ Added ['framenamelong'] to all returned frame data arrays with + text description of that frame (ie 'RVA2' would return 'Relative + volume adjustment (2)') (thanks Christian Fritz) + ¤ ['datalength'] is now ['indexeddatalength'] in ASPI frames (was + confliciting with the all-frames ['datalength'] as introduced in + v1.4.0b1 + ¤ ['datalength'] now returned as integer (rather than double) where + possible + + +1.4.0b2: [March-21-2002] James Heinrich + ¤ ['mpeg']['audio']['bitrate'] now returned as int rather than + double for VBR files + * Bugfix: MPEG audio information wasn't being parsed on files that + had neither ID3v1 or ID3v2 + * Bugfix: COMM/WXXX frames weren't returning 'asciidata' in + ID3v2.2, which also meant the ['id3']['id3v2']['comment'] field + wasn't being returned (thanks stephaneØtekartists*com) + * Bugfix: file might not be found if filename actually contains + escaped chars or %xx-formatted characters + (thanks reel_tazØusers*sourceforge*net) + Added support for running with Register Globals turned off + (thanks reel_tazØusers*sourceforge*net) + Added urlencode() where needed in check.php + (thanks reel_tazØusers*sourceforge*net) + Fixed IE buffering/display problem in progress counter in check.php + + +1.4.0b1: [March-11-2002] James Heinrich + » ID3v2 writing support via WriteID3v2() in putid3.php + RemoveID3v2() and RemoveID3v1() functions now available in + putid3.php All ID3v1 and ID3v2 writing functions have been moved + to putid3.php and example file write.php has been added to the + distribution + ¤ MPEG audio frame information (bitrate, frequency, etc) now + returned inside ['mpeg']['audio'] instead of just ['mpeg'] + ¤ MPEG video information now parsed, returned in ['mpeg']['video'] + Note: audio portion of video system files is not yet being parsed + ¤ All flag bits are now returned as boolean rather than int or + string + ¤ RVA2 data now returned as an array (multiple RVA2 tags are + allowed) + ¤ RVA2/EQU2 description returned under ['description'] rather than + ['identification'] + ¤ RVAD/EQUA adjustments now returned as signed integers, rather than + absolute values which required you to check flag bytes + ¤ RVRB/REV data no longer returns under ['reverb'] array + ¤ WXXX/W???/LINK frames now return ['url'] instead of ['URL'] + ¤ USER now properly returns both ['language'] and ['languagename'] + ¤ OWNE now returns ['purchasedateunix'] as a UNIX timestamp + (only if ['purchasedate'] is a valid date) + ¤ ['id3']['id3v2']['padding'] now returned with information on padding + ¤ ['headerlength'] now includes the initial 6 or 10 bytes of the + ID3v2 header + ¤ ['artist'], ['title'], ['album'], ['tracknumber'], ['genre'] now + returned for easier access for Ogg files + ¤ added ['datalength'] to all ID3v2 frames: length of frame data, + not including frame header + ¤ ['fileformat'] now returns 'id3' if there are ID3v1 or ID3v2 tags + but no audio data + ¤ ['fileformat'] now returns 'mpg' if it's an MPEG system (video + + audio) file + * Bugfix: RVAD was being parsed incorrectly + * Bugfix: ['currency'] and ['purchasedate'] now correctly returned + in OWNE + * Bugfix: Frequncies in 'EQU2' frames were incorrectly double + * Bugfix: ['bytedeviation'] and ['msdeviation'] now properly + returned as integer rather than binary string for 'MLLT' frames + * Bugfix: ['filename'] now properly returned for 'GEOB' frames + * Bugfix: ['imagetype'] now properly returned for 'PIC' frames in + ID3v2.2 + * Bugfix: Genre not being written if not set in WriteID3v1() + (thanks reel_tazØusers*sourceforge*net) + * Bugfix: Changed write mode to 'r+b' from 'a+' because ID3v1 tags + were being appended instead of overwritten if they already existed + (thanks reel_tazØusers*sourceforge*net) + * Bugfix: open would fail on filenames containing quotes + (thanks javierØcrackdealer*com) + * Bugfix: various values were incorrectly returned (unneeded ord()) + in these frames: COMR, USER, ENCR, GRID, PRIV, SIGN + * Bugfix: ASPI ['bitsperpoint'] was not correctly returned + * Bugfix: RoughTranslateUnicodeToASCII() was not returning the last + char for UTF-16 + * Bugfix: ['audiobytes'] now correctly 0 if no synch found + * Bugfix: GenreLookup was incorrectly returning 'Remix' instead of + 'Blues' for GenreID 0 + Added sample directory browser to check.php + Seperated out MPEGaudio-parsing functionality into + getOnlyMPEGaudioInfo() which may be called directly if you don't + need any ID3 parsing (thanks djpretzelØcox*rr*com for idea) + Reduced use of fread() for increased performance in + getID3v1Filepointer() + Added clearstatcache() before checking filesize - size after writing + tag now correct + Added hack for mp3Rage (www.chaoticsoftware.com) that puts + ID3v2.3-formatted MIME type instead of 3-char ID3v2.2-format image + type (thanks xbhoffØpacbell*net for test file) + + +1.3.2: [February-15-2002] James Heinrich + ¤ UFID/UFI, USLT/ULT, COMM/COM, APIC/PIC, GEOB/GEO, CRM, RVA2, EQU2, + POPM/POP, AENC/CRA, ENCR and GRID frame data now returned under + numeric array index rather than by ownerID + ¤ RVA2 frame data is now returned keyed by $channeltypeid instead of + $frame_idstring + ¤ WXXX/WXX frame description now returned under ['description'] + instead of ['data'] + Trailing null bytes now trimmed from frame (W??? & T???) text data + (it shouldn't be there to begin with, but a sample file encoded by + [unknown program] had data padded to 50 chars with null bytes, + which caused ParseID3v2GenreString() to freeze). + + +1.3.1: [February-13-2002] James Heinrich + * Bugfix: ['playtime_seconds'] and ['playtime_string'] were not + being returned + * Bugfix: ['fileformat'] was incorrectly being returned as a + 2-element array + * Bugfix: USLT wasn't being correctly parsed + Improved RoughTranslateUnicodeToASCII() + (thanks reel_tazØusers*sourceforge*net for Unicode test file) + + +1.3.0: [February-13-2002] James Heinrich + » ID3v1 writing support via WriteID3v1() + ¤ MPEG audio frame information (bitrate, frequency, etc) now + returned inside ['mpeg'] + ¤ ['mpeg']['raw'] returns the integer values of the bits for MPEG + audio information as returned in ['mpeg'] by decodeheader() + (thanks reel_tazØusers*sourceforge*net) + ¤ 'protection', 'padding', 'private', 'copyright' and 'original' now + return as boolean + ¤ 'bitrate' and 'frequency' now return as int (except in special + case of 'free') + Language name as well as code retured where appropriate + (ie 'English' and 'eng') + Text frames with invalid TextEncoding value are now passed through + anyway + ID3v1 data (title, artist, album, year, comment) is now trimmed + (no more nulls) + RoughTranslateUnicodeToASCII() now uses utf8_decode() for UTF-8 + + +1.2.5: [January-30-2002] James Heinrich + * Bugfix: Playtime calculations for VBR files were off slightly + (rounding error) + * Bugfix: Extended header length was incorrectly calculated + * Bugfix: Genre strings such as '03' weren't being handled correctly + More complete support for ID3v2.3 FrameIDs + Split out getid3.frames.php (FrameID-specific parsing function) + Split out getid3.lookup.php (assorted lookup-table functions) + Searches for what directory getid3.*.php support files are in (must + be same as getid3.php, but doesn't have to be same as main file - + for example your main file could be /index.php, but including + /lib/getid3/getid3.php) + Simplified, tweaked, changed and/or eliminated several functions. + + +1.2.4: [January-26-2002] James Heinrich + » Basic support for reading Ogg-Vorbis comment tags + + +1.2.3: [January-24-2002] James Heinrich + » ID3v2.2.x 3-char FrameIDs are now fully parsed + Note: While I've included support for 22 FrameIDs as defined in + the specs, I don't have test files for all of them. If anyone + knows of programs that generate any of the untested tags, please + email getid3Øsilisoftware*com ! Here's what's tested and not: + Tested: T??, COM + Untested: UFI, TXX, W??, WXX, IPL, MCI, ETC, MLL, STC, ULT, SLT, + RVA, EQU, REV, PIC, GEO, CNT, POP, BUF, CRM, CRA, LNK + table_var_dump() now displays boolean variables as TRUE or FALSE + table_var_dump() now uses htmlspecialchars() to avoid broken-table + problems + + +1.2.2: [January-18-2002] James Heinrich + ¤ Parses ID3v2 genres into ['id3']['id3v2']['genreid'] and + ['id3']['id3v2']['genrelist'] where appropriate + (thanks stephaneØtekartists*com for the idea) + Added ID3v2 genre abbreviations 'RX' (remix) and 'CR' (cover) + + +1.2.1: [January-17-2002] James Heinrich + * Bugfix: 'mp3' was being returned in ['format'], but 'zip' was + being returned in ['fileformat'], both are now returned in + ['fileformat'] + ¤ Splits ['id3']['id3v2']['track'] in the format '5/12' into + ['track'] = '5' and ['totaltracks'] = '12' + ¤ Enabled ['id3']['id3v2']['title'] etc for ID3v2.2.x + (3-char frame names) (thanks stephaneØtekartists*com) + ¤ Changed v1.?? version number format to v1.?.? + Scans through the file until it finds the MPEG synch (start of audio + frame) - some files encoded by LAME 3.91 had undocumented padding + after the ID3v2 header; getMP3headerFilepointer() now scans until + it finds synch (or EOF) (thanks adamØtrekjapan*com) + Improved Unicode conversion in RoughTranslateUnicodeToASCII() + + +1.20: [January-15-2002] James Heinrich + » Support for variable-bitrate (VBR) files, both Xing and Fraunhofer + headers + » All 4-character FrameIDs are now fully parsed according to the + specs at http://www.id3.org/id3v2.4.0-frames.txt + ¤ This means that most no longer return ['flags'] and ['data'] + Note: While I've included support for 30 FrameIDs as defined in + the specs, I don't have test files for all of them. If anyone + knows of programs that generate any of the untested tags, please + email getid3Øsilisoftware*com ! Here's what's tested and not: + Tested: UFID, T???, WXXX, USLT, SYLT, COMM, APIC, GEOB + Untested: TXXX, W???, MCDI, ETCO, MLLT, SYTC, RVA2, EQU2, RVRB, + PCNT, POPM, RBUF, AENC, USER, OWNE, COMR, ENCR, GRID, + PRIV, SIGN, SEEK, ASPI + ¤ Added 'title', 'artist', etc names to ID3v2 data (easier to access + than the 4-character FrameIDs of the ID3v2 standard) + (thanks jaksonØgmx.net) + * Bugfix: added fclose() at end of GetAllMP3Info() + (thanks stephaneØtekartists*com) + * Bugfix: ID3v1 wasn't being parsed if ID3v2 wasn't present + (thanks jaksonØgmx.net) + * Bugfix: several flags were being parsed incorrectly (the structure + had changed from ID3v2.3 to ID3v2.4) - v2.3 flags were being + incorrectly parsed + Much more compact implementation of decodeheader() + (thanks jaksonØgmx.net for the idea) + ID3v1 genres 126 through 147 (thanks jaksonØgmx.net) + New table_var_dump() function in check.php + (based partially on idea by jaksonØgmx.net) + Seperated ID3v1 retrieval into seperate function + + +1.11: [December-23-2001] James Heinrich + All functions merged into file getid3.php + Updated documentation to reflect new returned information + + +1.10: [December-20-2001] James Heinrich + * Bugfix: ID3v1 Track# was incorrectly being parsed whether it + existed or not + Changed calling procedure to recommend using + GetAllMP3info($filename) from getmp3header.php + Now includes check.php - example file + ¤ Checks to see if file is in ZIP or MP3 format + (returned in ['format']) + [Ed. Note: ['fileformat'] as of v1.2.1] + + +1.06: [November-05-2001] James Heinrich + * Bugfix: ID3v2.2.x frames weren't being parsed since they use + 6-byte rather than 10-byte headers as v2.3+ does + (thanks spunkØmac*com for pointing that out) + + +1.05: [September-06-2001] James Heinrich + * Bugfix: ID3v2 was being parsed even if it didn't exist + + +1.04: [July-16-2001] James Heinrich + * Bugfix: typo in Extended Header section (strpad() should be + str_pad()) (thanks jurroonØyahoo*com) + + +1.03: [May-07-2001] James Heinrich + * Bugfix: Added missing ['id3']['id3v1']['genreid'] and + ['id3']['id3v1']['genre'] + + +1.02: [May-05-2001] James Heinrich + ¤ Added ['getID3version'] + + +1.01: [May-04-2001] James Heinrich + » Added support for frame-level de-unsynchronisation (as per + ID3v2.4.0 specs) in addition to ID3v2.3.x tag-level + de-unsynchronisation + + +1.00: [May-04-2001] James Heinrich + » Initial public release + + +/////////////////////////////////////////////////////////////////////// + +Future Plans +============ + + Features + -------- + + * Writing support for Real + * Better support for MP4 container format + * Support for Matroska (www.matroska.org) (thanks ahØartemis*dk) + http://corecodec.com/modules.php?op=modload&name=PNphpBB2&file=viewtopic&t=227 + * scan for appended ID3v2 tag at end of file per ID3v2.4 specs (Section 5.0) + * Support for JPEG-2000 (http://www.morgan-multimedia.com/jpeg2000_overview.htm) + * Support for MOD (mod/stm/s3m/it/xm/mtm/ult/669) (thanks ahØartemis*dk) + * Support for FROG (http://ghido.shelter.ro/FROG.php) + * Lyrics3 v1 & v2 writing support + * Support for gzip + * Support for ACE (thanks Vince) + * Support for Ogg other than Vorbis, Speex and OggFlac (ie. Ogg+Xvid) + (thanks ahØartemis*dk) + * Ability to create Xing/LAME VBR header for VBR MP3s that are missing VBR header + * Ability to "clean" ID3v2 padding (replace invalid padding with valid padding) + * Ability to convert RIFF-MP3 to regular MP3 (strip RIFF headers) + * Warn if MP3s change version mid-stream (in full-scan mode) + * check for corrupt/broken mid-file MP3 streams in histogram scan + * Support for lossless-compression formats + (http://www.firstpr.com.au/audiocomp/lossless/#Links) + (http://compression.ca/act-sound.html) + (http://web.inter.nl.net/users/hvdh/lossless/lossless.htm) + * Support for Sonarc (http://www.firstpr.com.au/audiocomp/lossless/sonarc/) + * Support for WavArc (http://www.firstpr.com.au/audiocomp/lossless/wavarc/) + * Support for WaveZip/MUSICompress (http://hometown.aol.com/sndspace) + * Support for LTAC (http://www.nue.tu-berlin.de/wer/liebchen/ltac.html) + * Support for RIFF-INFO chunks + * http://lotto.st-andrews.ac.uk/~njh/tag_interchange.html + (thanks Nick Humfrey <njhØsurgeradio*co*uk>) + * http://abcavi.narod.ru/sof/abcavi/infotags.htm + (thanks Kibi) + * Better support for Bink video + * http://www.hr/josip/DSP/AudioFile2.html + * http://www.pcisys.net/~melanson/codecs/ + * http://sox.sourceforge.net/AudioFormats-11.html + * DiamondWare Digitized .dwd + * Tandy Deskmate .snd + * Sample Vision + * Detect mp3PRO + * Support for PSD + * Support for JPC + * Support for JP2 + * Support for JPX + * Support for JB2 + * Support for IFF + * Support for ICO + * Support for ANI + * Support for EXE (comments, author, etc) (thanks p*quaedackersØplanet*nl) + * Support for DVD-IFO (region, subtitles, aspect ratio, etc) + (thanks p*quaedackersØplanet*nl) + * More complete support for SWF - parsing encapsulated MP3 and/or JPEG content + (thanks n8n8Øyahoo*com) + * Support for a2b + * MPC-SV8 (http://www.uni-jena.de/~pfk/mpp/sv8/components.html) + http://www.personal.uni-jena.de/~pfk/mpp/ + * Optional scan-through-frames for AVI verification + (thanks rockcohenØmassive-interactive*nl) + * Support for TTF (thanks infoØbutterflyx*com) + * Support for DSS (http://www.getid3.org/phpBB2/viewtopic.php?t=171) + * Support for SMAF (http://smaf-yamaha.com/what/demo.html) + http://www.getid3.org/phpBB2/viewtopic.php?t=182 + * Support for AMR (http://www.getid3.org/phpBB2/viewtopic.php?t=195) + * Support for 3gpp (http://www.getid3.org/phpBB2/viewtopic.php?t=195) + * Support for ID4 (http://www.wackysoft.cjb.net grizlyY2KØhotmail*com) + * Parse XML data returned in Ogg comments + * Parse XML data from Quicktime SMIL metafiles (klausrathØmac*com) + * ID3v2.2 tag writing support + * ID3v2 genre string creator function + * Support for optional XML-format output (thanks moisei for the idea) + * More complete parsing of JPG + * Support for all old-style ASF packets + * ASF framerate guess + * ASF/WMA/WMV tag writing + * Parse declared T??? ID3v2 text information frames, where appropriate + (thanks Christian Fritz for the idea) + * Recognize encoder: + http://www.guerillasoft.com/EncSpot2/index.html + http://ff123.net/identify.html + http://www.hydrogenaudio.org/?act=ST&f=16&t=9414 + http://www.hydrogenaudio.org/?showtopic=11785 + * Support for other OS/2 bitmap structures: Bitmap Array('BA'), + Color Icon('CI'), Color Pointer('CP'), Icon('IC'), Pointer ('PT') + http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm + * Support for WavPack RAW mode + * ASF/WMA/WMV data packet parsing + * ID3v2FrameFlagsLookupTagAlter() + * ID3v2FrameFlagsLookupFileAlter() + * obey ID3v2 tag alter/preserve/discard rules + * http://www.geocities.com/SiliconValley/Sector/9654/Softdoc/Illyrium/Aolyr.htm + * proper checking for LINK/LNK frame validity in ID3v2 writing + * proper checking for ASPI-TLEN frame validity in ID3v2 writing + * proper checking for COMR frame validity in ID3v2 writing + * http://www.geocities.co.jp/SiliconValley-Oakland/3664/index.html + * decode GEOB ID3v2 structure as encoded by RealJukebox, + decode NCON ID3v2 structure as encoded by MusicMatch + (probably won't happen - the formats are proprietary) + + + Known Bugs/Issues in getID3() that may be fixed eventually + ----------------------------------------------------------- + + * Cannot determine bitrate for MPEG video with VBR video data + (need documentation) + * Interlace/progressive cannot be determined for MPEG video + (need documentation) + * MIDI playtime is sometimes inaccurate + * AAC-RAW mode files cannot be identified + * WavPack-RAW mode files cannot be identified + * mp4 files report lots of "Unknown QuickTime atom type" + (need documentation) + * Encrypted ASF/WMA/WMV files warn about "unhandled GUID + ASF_Content_Encryption_Object" + * Bitrate split between audio and video cannot be calculated for + NSV, only the total bitrate. (need documentation) + * All Ogg formats (Vorbis, OggFLAC, Speex) are affected by the + problem of large VorbisComments spanning multiple Ogg pages, but + but only OggVorbis files can be processed with vorbiscomment. + * The version of "head" supplied with Mac OS 10.2.8 (maybe other + versions too) does only understands a single option (-n) and + therefore fails. getID3 ignores this and returns wrong md5_data. + + + + Known Bugs/Issues in getID3() that cannot be fixed + -------------------------------------------------- + + * Files larger than 2GB (of any format) cannot be parsed by + getID3() due to limitations in the PHP filesystem functions + + + Known Bugs/Issues in other programs + ----------------------------------- + + * Winamp (up to v2.80 at least) does not support ID3v2.4 tags, + only ID3v2.3 + see: http://forums.winamp.com/showthread.php?postid=387524 + * Some versions of Helium2 (www.helium2.com) do not write + ID3v2.4-compliant Frame Sizes, even though the tag is marked + as ID3v2.4) (detected by getID3()) + * MP3ext V3.3.17 places a non-compliant padding string at the end + of the ID3v2 header. This is supposedly fixed in v3.4b21 but + only if you manually add a registry key. This fix is not yet + confirmed. (detected by getID3()) + * CDex v1.40 (fixed by v1.50b7) writes non-compliant Ogg comment + strings, supposed to be in the format "NAME=value" but actually + written just "value" (detected by getID3()) + * Oggenc 0.9-rc3 flags the encoded file as ABR whether it's + actually ABR or VBR. + * iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably + other versions are too) writes ID3v2.3 comment tags using a + frame name 'COM ' which is not valid for ID3v2.3+ (it's an + ID3v2.2-style frame name) (detected by getID3()) + * MP2enc does not encode mono CBR MP2 files properly (half speed + sound and double playtime) + * MP2enc does not encode mono VBR MP2 files properly (actually + encoded as stereo) + * tooLAME does not encode mono VBR MP2 files properly (actually + encoded as stereo) + * AACenc encodes files in VBR mode (actually ABR) even if CBR is + specified + * AAC/ADIF - bitrate_mode = cbr for vbr files + * LAME 3.90-3.92 prepends one frame of null data (space for the + LAME/VBR header, but it never gets written) when encoding in CBR + mode with the DLL + * Ahead Nero encodes TwinVQF with a DSIZ value (which is supposed + to be the filesize in bytes) of "0" for TwinVQF v1.0 and "1" for + TwinVQF v2.0 (detected by getID3()) + * Ahead Nero encodes TwinVQF files 1 second shorter than they + should be + * AAC-ADTS files are always actually encoded VBR, even if CBR mode + is specified (the CBR-mode switches on the encoder enable ABR + mode, not CBR as such, but it's not possible to tell the + difference between such ABR files and true VBR) + * STREAMINFO.audio_signature in OggFLAC is always null. "The reason + it's like that is because there is no seeking support in + libOggFLAC yet, so it has no way to go back and write the + computed sum after encoding. Seeking support in Ogg FLAC is the + #1 item for the next release." - Josh Coalson (FLAC developer) + NOTE: getID3() will calculate md5_data in a method similar to + other file formats, but that value cannot be compared to the + md5_data value from FLAC data in a FLAC file format. + * STREAMINFO.audio_signature is not calculated in FLAC v0.3.0 & + v0.4.0 - getID3() will calculate md5_data in a method similar to + other file formats, but that value cannot be compared to the + md5_data value from FLAC v0.5.0+ + * RioPort (various versions including 2.0 and 3.11) tags ID3v2 with + a WCOM frame that has no data portion + * Earlier versions of Coolplayer adds illegal ID3 tags to Ogg Vorbis + files, thus making them corrupt. + * Meracl ID3 Tag Writer v1.3.4 (and older) incorrectly truncates the + last byte of data from an MP3 file when appending a new ID3v1 tag. + (detected by getID3()) + * Lossless-Audio files encoded with and without the -noseek switch + do actually differ internally and therefore cannot match md5_data + * iTunes has been known to append a new ID3v1 tag on the end of an + existing ID3v1 tag when ID3v2 tag is also present + (detected by getID3()) diff --git a/modules/id3/demos/demo.audioinfo.class.php b/modules/id3/demos/demo.audioinfo.class.php new file mode 100644 index 00000000..d38ec198 --- /dev/null +++ b/modules/id3/demos/demo.audioinfo.class.php @@ -0,0 +1,319 @@ +<?php + +// +----------------------------------------------------------------------+ +// | PHP version 4.1.0 | +// +----------------------------------------------------------------------+ +// | Placed in public domain by Allan Hansen, 2002. Share and enjoy! | +// +----------------------------------------------------------------------+ +// | /demo/demo.audioinfo.class.php | +// | | +// | Example wrapper class to extract information from audio files | +// | through getID3(). | +// | | +// | getID3() returns a lot of information. Much of this information is | +// | not needed for the end-application. It is also possible that some | +// | users want to extract specific info. Modifying getID3() files is a | +// | bad idea, as modifications needs to be done to future versions of | +// | getID3(). | +// | | +// | Modify this wrapper class instead. This example extracts certain | +// | fields only and adds a new root value - encoder_options if possible. | +// | It also checks for mp3 files with wave headers. | +// +----------------------------------------------------------------------+ +// | Example code: | +// | $au = new AudioInfo(); | +// | print_r($au->Info('file.flac'); | +// +----------------------------------------------------------------------+ +// | Authors: Allan Hansen <ahØartemis*dk> | +// +----------------------------------------------------------------------+ +// + + + +/** +* getID3() settings +*/ + +require_once('../getid3/getid3.php'); + + + + +/** +* Class for extracting information from audio files with getID3(). +*/ + +class AudioInfo { + + /** + * Private variables + */ + var $result = NULL; + var $info = NULL; + + + + + /** + * Constructor + */ + + function AudioInfo() { + + // Initialize getID3 engine + $this->getID3 = new getID3; + $this->getID3->option_md5_data = true; + $this->getID3->option_md5_data_source = true; + $this->getID3->encoding = 'UTF-8'; + } + + + + + /** + * Extract information - only public function + * + * @access public + * @param string file Audio file to extract info from. + */ + + function Info($file) { + + // Analyze file + $this->info = $this->getID3->analyze($file); + + // Exit here on error + if (isset($this->info['error'])) { + return array ('error' => $this->info['error']); + } + + // Init wrapper object + $this->result = array (); + $this->result['format_name'] = @$this->info['fileformat'].'/'.@$this->info['audio']['dataformat'].(isset($this->info['video']['dataformat']) ? '/'.@$this->info['video']['dataformat'] : ''); + $this->result['encoder_version'] = @$this->info['audio']['encoder']; + $this->result['encoder_options'] = @$this->info['audio']['encoder_options']; + $this->result['bitrate_mode'] = @$this->info['audio']['bitrate_mode']; + $this->result['channels'] = @$this->info['audio']['channels']; + $this->result['sample_rate'] = @$this->info['audio']['sample_rate']; + $this->result['bits_per_sample'] = @$this->info['audio']['bits_per_sample']; + $this->result['playing_time'] = @$this->info['playtime_seconds']; + $this->result['avg_bit_rate'] = @$this->info['audio']['bitrate']; + $this->result['tags'] = @$this->info['tags']; + $this->result['comments'] = @$this->info['comments']; + $this->result['warning'] = @$this->info['warning']; + $this->result['md5'] = @$this->info['md5_data']; + + // Post getID3() data handling based on file format + $method = @$this->info['fileformat'].'Info'; + if (@$this->info['fileformat'] && method_exists($this, $method)) { + $this->$method(); + } + + return $this->result; + } + + + + + /** + * post-getID3() data handling for AAC files. + * + * @access private + */ + + function aacInfo() { + $this->result['format_name'] = 'AAC'; + } + + + + + /** + * post-getID3() data handling for Wave files. + * + * @access private + */ + + function riffInfo() { + if ($this->info['audio']['dataformat'] == 'wav') { + + $this->result['format_name'] = 'Wave'; + + } else if (ereg('^mp[1-3]$', $this->info['audio']['dataformat'])) { + + $this->result['format_name'] = strtoupper($this->info['audio']['dataformat']); + + } else { + + $this->result['format_name'] = 'riff/'.$this->info['audio']['dataformat']; + + } + } + + + + + /** + * * post-getID3() data handling for FLAC files. + * + * @access private + */ + + function flacInfo() { + $this->result['format_name'] = 'FLAC'; + } + + + + + + /** + * post-getID3() data handling for Monkey's Audio files. + * + * @access private + */ + + function macInfo() { + $this->result['format_name'] = 'Monkey\'s Audio'; + } + + + + + + /** + * post-getID3() data handling for Lossless Audio files. + * + * @access private + */ + + function laInfo() { + $this->result['format_name'] = 'La'; + } + + + + + + /** + * post-getID3() data handling for Ogg Vorbis files. + * + * @access private + */ + + function oggInfo() { + if ($this->info['audio']['dataformat'] == 'vorbis') { + + $this->result['format_name'] = 'Ogg Vorbis'; + + } else if ($this->info['audio']['dataformat'] == 'flac') { + + $this->result['format_name'] = 'Ogg FLAC'; + + } else if ($this->info['audio']['dataformat'] == 'speex') { + + $this->result['format_name'] = 'Ogg Speex'; + + } else { + + $this->result['format_name'] = 'Ogg '.$this->info['audio']['dataformat']; + + } + } + + + + + /** + * post-getID3() data handling for Musepack files. + * + * @access private + */ + + function mpcInfo() { + $this->result['format_name'] = 'Musepack'; + } + + + + + /** + * post-getID3() data handling for MPEG files. + * + * @access private + */ + + function mp3Info() { + $this->result['format_name'] = 'MP3'; + } + + + + + /** + * post-getID3() data handling for MPEG files. + * + * @access private + */ + + function mp2Info() { + $this->result['format_name'] = 'MP2'; + } + + + + + + /** + * post-getID3() data handling for MPEG files. + * + * @access private + */ + + function mp1Info() { + $this->result['format_name'] = 'MP1'; + } + + + + + /** + * post-getID3() data handling for WMA files. + * + * @access private + */ + + function asfInfo() { + $this->result['format_name'] = strtoupper($this->info['audio']['dataformat']); + } + + + + /** + * post-getID3() data handling for Real files. + * + * @access private + */ + + function realInfo() { + $this->result['format_name'] = 'Real'; + } + + + + + + /** + * post-getID3() data handling for VQF files. + * + * @access private + */ + + function vqfInfo() { + $this->result['format_name'] = 'VQF'; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/demos/demo.browse.php b/modules/id3/demos/demo.browse.php new file mode 100644 index 00000000..b0cdc85f --- /dev/null +++ b/modules/id3/demos/demo.browse.php @@ -0,0 +1,626 @@ +<HTML> +<HEAD> +<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8"/> +<TITLE>getID3() - Sample file browser</TITLE> +</HEAD> +<BODY> +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// /demo/demo.browse.php - part of getID3() // +// Sample script for browsing/scanning files and displaying // +// information returned by getID3() // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + + +require_once('../getid3/getid3.php'); + +// Initialize getID3 engine +$getID3 = new getID3; + + +$getID3checkColor_Head = 'CCCCDD'; +$getID3checkColor_DirectoryLight = 'EEBBBB'; +$getID3checkColor_DirectoryDark = 'FFCCCC'; +$getID3checkColor_FileLight = 'EEEEEE'; +$getID3checkColor_FileDark = 'DDDDDD'; +$getID3checkColor_UnknownLight = 'CCCCFF'; +$getID3checkColor_UnknownDark = 'BBBBDD'; + + + +if (!function_exists('getmicrotime')) { + function getmicrotime() { + list($usec, $sec) = explode(' ', microtime()); + return ((float) $usec + (float) $sec); + } +} + + + +ob_start(); +echo '<HTML><HEAD>'; +echo '<TITLE>getID3() - /demo/demo.browse.php (sample script)</TITLE>'; +echo '<STYLE>BODY,TD,TH { font-family: sans-serif; font-size: 9pt; }</STYLE>'; +echo '</HEAD><BODY>'; + +if (isset($_REQUEST['deletefile'])) { + if (file_exists($_REQUEST['deletefile'])) { + if (unlink($_REQUEST['deletefile'])) { + $deletefilemessage = 'Successfully deleted '.addslashes($_REQUEST['deletefile']); + } else { + $deletefilemessage = 'FAILED to delete '.addslashes($_REQUEST['deletefile']).' - error deleting file'; + } + } else { + $deletefilemessage = 'FAILED to delete '.addslashes($_REQUEST['deletefile']).' - file does not exist'; + } + if (isset($_REQUEST['noalert'])) { + echo '<B><FONT COLOR="'.(($deletefilemessage{0} == 'F') ? '#FF0000' : '#008000').'">'.$deletefilemessage.'</FONT></B><HR>'; + } else { + echo '<SCRIPT LANGUAGE="JavaScript">alert("'.$deletefilemessage.'");</SCRIPT>'; + } +} + + +if (isset($_REQUEST['filename'])) { + if (!file_exists($_REQUEST['filename'])) { + die($_REQUEST['filename'].' does not exist'); + } + $starttime = getmicrotime(); + $AutoGetHashes = (bool) (filesize($_REQUEST['filename']) < 52428800); // auto-get md5_data, md5_file, sha1_data, sha1_file if filesize < 50MB + + $getID3->option_md5_data = $AutoGetHashes; + $getID3->option_sha1_data = $AutoGetHashes; + $ThisFileInfo = $getID3->analyze($_REQUEST['filename']); + if ($AutoGetHashes) { + $ThisFileInfo['md5_file'] = getid3_lib::md5_file($_REQUEST['filename']); + $ThisFileInfo['sha1_file'] = getid3_lib::sha1_file($_REQUEST['filename']); + } + + + getid3_lib::CopyTagsToComments($ThisFileInfo); + + $listdirectory = dirname(getid3_lib::SafeStripSlashes($_REQUEST['filename'])); + $listdirectory = realpath($listdirectory); // get rid of /../../ references + + if (GETID3_OS_ISWINDOWS) { + // this mostly just gives a consistant look to Windows and *nix filesystems + // (windows uses \ as directory seperator, *nix uses /) + $listdirectory = str_replace('\\', '/', $listdirectory.'/'); + } + + if (strstr($_REQUEST['filename'], 'http://') || strstr($_REQUEST['filename'], 'ftp://')) { + echo '<I>Cannot browse remote filesystems</I><BR>'; + } else { + echo 'Browse: <A HREF="'.$_SERVER['PHP_SELF'].'?listdirectory='.urlencode($listdirectory).'">'.$listdirectory.'</A><BR>'; + } + + echo table_var_dump($ThisFileInfo); + $endtime = getmicrotime(); + echo 'File parsed in '.number_format($endtime - $starttime, 3).' seconds.<BR>'; + +} else { + + $listdirectory = (isset($_REQUEST['listdirectory']) ? getid3_lib::SafeStripSlashes($_REQUEST['listdirectory']) : '.'); + $listdirectory = realpath($listdirectory); // get rid of /../../ references + $currentfulldir = $listdirectory.'/'; + + if (GETID3_OS_ISWINDOWS) { + // this mostly just gives a consistant look to Windows and *nix filesystems + // (windows uses \ as directory seperator, *nix uses /) + $currentfulldir = str_replace('\\', '/', $listdirectory.'/'); + } + + if ($handle = @opendir($listdirectory)) { + + echo str_repeat(' ', 300); // IE buffers the first 300 or so chars, making this progressive display useless - fill the buffer with spaces + echo 'Processing'; + + $starttime = getmicrotime(); + + $TotalScannedUnknownFiles = 0; + $TotalScannedKnownFiles = 0; + $TotalScannedPlaytimeFiles = 0; + $TotalScannedBitrateFiles = 0; + $TotalScannedFilesize = 0; + $TotalScannedPlaytime = 0; + $TotalScannedBitrate = 0; + $FilesWithWarnings = 0; + $FilesWithErrors = 0; + + while ($file = readdir($handle)) { + set_time_limit(30); // allocate another 30 seconds to process this file - should go much quicker than this unless intense processing (like bitrate histogram analysis) is enabled + echo ' .'; // progress indicator dot + flush(); // make sure the dot is shown, otherwise it's useless + $currentfilename = $listdirectory.'/'.$file; + + // symbolic-link-resolution enhancements by davidbullockØtech-center*com + $TargetObject = realpath($currentfilename); // Find actual file path, resolve if it's a symbolic link + $TargetObjectType = filetype($TargetObject); // Check file type without examining extension + + if($TargetObjectType == 'dir') { + switch ($file) { + case '..': + $ParentDir = realpath($file.'/..').'/'; + if (GETID3_OS_ISWINDOWS) { + $ParentDir = str_replace('\\', '/', $ParentDir); + } + $DirectoryContents[$currentfulldir]['dir'][$file]['filename'] = $ParentDir; + break; + + case '.': + // ignore + break; + + default: + $DirectoryContents[$currentfulldir]['dir'][$file]['filename'] = $file; + break; + } + + } elseif ($TargetObjectType == 'file') { + + $getID3->option_md5_data = isset($_REQUEST['ShowMD5']); + $fileinformation = $getID3->analyze($currentfilename); + + getid3_lib::CopyTagsToComments($fileinformation); + + $TotalScannedFilesize += @$fileinformation['filesize']; + + if (isset($_REQUEST['ShowMD5'])) { + $fileinformation['md5_file'] = md5($currentfilename); + } + + if (!empty($fileinformation['fileformat'])) { + $DirectoryContents[$currentfulldir]['known'][$file] = $fileinformation; + $TotalScannedPlaytime += @$fileinformation['playtime_seconds']; + $TotalScannedBitrate += @$fileinformation['bitrate']; + $TotalScannedKnownFiles++; + } else { + $DirectoryContents[$currentfulldir]['other'][$file] = $fileinformation; + $DirectoryContents[$currentfulldir]['other'][$file]['playtime_string'] = '-'; + $TotalScannedUnknownFiles++; + } + if (isset($fileinformation['playtime_seconds']) && ($fileinformation['playtime_seconds'] > 0)) { + $TotalScannedPlaytimeFiles++; + } + if (isset($fileinformation['bitrate']) && ($fileinformation['bitrate'] > 0)) { + $TotalScannedBitrateFiles++; + } + } + } + $endtime = getmicrotime(); + closedir($handle); + echo 'done<BR>'; + echo 'Directory scanned in '.number_format($endtime - $starttime, 2).' seconds.<BR>'; + flush(); + + $columnsintable = 14; + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="3">'; + + echo '<TR BGCOLOR="#'.$getID3checkColor_Head.'"><TH COLSPAN="'.$columnsintable.'">Files in '.$currentfulldir.'</TH></TR>'; + $rowcounter = 0; + foreach ($DirectoryContents as $dirname => $val) { + if (is_array($DirectoryContents[$dirname]['dir'])) { + uksort($DirectoryContents[$dirname]['dir'], 'MoreNaturalSort'); + foreach ($DirectoryContents[$dirname]['dir'] as $filename => $fileinfo) { + echo '<TR BGCOLOR="#'.(($rowcounter++ % 2) ? $getID3checkColor_DirectoryDark : $getID3checkColor_DirectoryLight).'">'; + if ($filename == '..') { + echo '<TD COLSPAN="'.$columnsintable.'">Parent directory: <A HREF="'.$_SERVER['PHP_SELF'].'?listdirectory='.urlencode($dirname.$filename).'"><B>'; + if (GETID3_OS_ISWINDOWS) { + echo str_replace('\\', '/', realpath($dirname.$filename)); + } else { + echo realpath($dirname.$filename); + } + echo '/</B></A></TD>'; + } else { + echo '<TD COLSPAN="'.$columnsintable.'"><A HREF="'.$_SERVER['PHP_SELF'].'?listdirectory='.urlencode($dirname.$filename).'"><B>'.FixTextFields($filename).'</B></A></TD>'; + } + echo '</TR>'; + } + } + + echo '<TR BGCOLOR="#'.$getID3checkColor_Head.'">'; + echo '<TH>Filename</TH>'; + echo '<TH>File Size</TH>'; + echo '<TH>Format</TH>'; + echo '<TH>Playtime</TH>'; + echo '<TH>Bitrate</TH>'; + echo '<TH>Artist</TH>'; + echo '<TH>Title</TH>'; + if (isset($_REQUEST['ShowMD5'])) { + echo '<TH>MD5 File (File) (<A HREF="'.$_SERVER['PHP_SELF'].'?listdirectory='.rawurlencode(isset($_REQUEST['listdirectory']) ? $_REQUEST['listdirectory'] : '.').'">disable</A>)</TH>'; + echo '<TH>MD5 Data (File) (<A HREF="'.$_SERVER['PHP_SELF'].'?listdirectory='.rawurlencode(isset($_REQUEST['listdirectory']) ? $_REQUEST['listdirectory'] : '.').'">disable</A>)</TH>'; + echo '<TH>MD5 Data (Source) (<A HREF="'.$_SERVER['PHP_SELF'].'?listdirectory='.rawurlencode(isset($_REQUEST['listdirectory']) ? $_REQUEST['listdirectory'] : '.').'">disable</A>)</TH>'; + } else { + echo '<TH COLSPAN="3">MD5 Data (<A HREF="'.$_SERVER['PHP_SELF'].'?listdirectory='.rawurlencode(isset($_REQUEST['listdirectory']) ? $_REQUEST['listdirectory'] : '.').'&ShowMD5=1">enable</A>)</TH>'; + } + echo '<TH>Tags</TH>'; + echo '<TH>Errors & Warnings</TH>'; + echo '<TH>Edit</TH>'; + echo '<TH>Delete</TH>'; + echo '</TR>'; + + if (isset($DirectoryContents[$dirname]['known']) && is_array($DirectoryContents[$dirname]['known'])) { + uksort($DirectoryContents[$dirname]['known'], 'MoreNaturalSort'); + foreach ($DirectoryContents[$dirname]['known'] as $filename => $fileinfo) { +//var_dump($fileinfo); + echo '<TR BGCOLOR="#'.(($rowcounter++ % 2) ? $getID3checkColor_FileDark : $getID3checkColor_FileLight).'">'; + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?filename='.urlencode($dirname.$filename).'" TITLE="View detailed analysis">'.FixTextFields(getid3_lib::SafeStripSlashes($filename)).'</A></TD>'; + echo '<TD ALIGN="RIGHT"> '.number_format($fileinfo['filesize']).'</TD>'; + echo '<TD ALIGN="RIGHT"> '.NiceDisplayFiletypeFormat($fileinfo).'</TD>'; + echo '<TD ALIGN="RIGHT"> '.(isset($fileinfo['playtime_string']) ? $fileinfo['playtime_string'] : '-').'</TD>'; + echo '<TD ALIGN="RIGHT"> '.(isset($fileinfo['bitrate']) ? BitrateText($fileinfo['bitrate'] / 1000, 0, ((@$fileinfo['audio']['bitrate_mode'] == 'vbr') ? true : false)) : '-').'</TD>'; + echo '<TD ALIGN="LEFT"> '.(isset($fileinfo['comments_html']['artist']) ? implode('<BR>', $fileinfo['comments_html']['artist']) : '').'</TD>'; + echo '<TD ALIGN="LEFT"> '.(isset($fileinfo['comments_html']['title']) ? implode('<BR>', $fileinfo['comments_html']['title']) : '').'</TD>'; + if (isset($_REQUEST['ShowMD5'])) { + echo '<TD ALIGN="LEFT"><TT>'.(isset($fileinfo['md5_file']) ? $fileinfo['md5_file'] : ' ').'</TT></TD>'; + echo '<TD ALIGN="LEFT"><TT>'.(isset($fileinfo['md5_data']) ? $fileinfo['md5_data'] : ' ').'</TT></TD>'; + echo '<TD ALIGN="LEFT"><TT>'.(isset($fileinfo['md5_data_source']) ? $fileinfo['md5_data_source'] : ' ').'</TT></TD>'; + } else { + echo '<TD ALIGN="CENTER" COLSPAN="3">-</TD>'; + } + echo '<TD ALIGN="LEFT"> '.@implode(', ', array_keys($fileinfo['tags'])).'</TD>'; + + echo '<TD ALIGN="LEFT"> '; + if (!empty($fileinfo['warning'])) { + $FilesWithWarnings++; + echo '<A HREF="javascript:alert(\''.FixTextFields(implode('\\n', $fileinfo['warning'])).'\');" TITLE="'.FixTextFields(implode("\n", $fileinfo['warning'])).'">warning</A><BR>'; + } + if (!empty($fileinfo['error'])) { + $FilesWithErrors++; + echo '<A HREF="javascript:alert(\''.FixTextFields(implode('\\n', $fileinfo['error'])).'\');" TITLE="'.FixTextFields(implode("\n", $fileinfo['error'])).'">error</A><BR>'; + } + echo '</TD>'; + + echo '<TD ALIGN="LEFT"> '; + switch (@$fileinfo['fileformat']) { + case 'mp3': + case 'mp2': + case 'mp1': + case 'flac': + case 'mpc': + echo '<A HREF="demo.write.php?Filename='.urlencode($dirname.$filename).'" TITLE="Edit tags">edit tags</A>'; + break; + case 'ogg': + switch (@$fileinfo['audio']['dataformat']) { + case 'vorbis': + echo '<A HREF="demo.write.php?Filename='.urlencode($dirname.$filename).'" TITLE="Edit tags">edit tags</A>'; + break; + } + break; + default: + break; + } + echo '</TD>'; + echo '<TD ALIGN="LEFT"> <A HREF="'.$_SERVER['PHP_SELF'].'?listdirectory='.urlencode($listdirectory).'&deletefile='.urlencode($dirname.$filename).'" onClick="return confirm(\'Are you sure you want to delete '.addslashes($dirname.$filename).'? \n(this action cannot be un-done)\');" TITLE="Permanently delete '."\n".FixTextFields($filename)."\n".' from'."\n".' '.FixTextFields($dirname).'">delete</A></TD>'; + echo '</TR>'; + } + } + + if (isset($DirectoryContents[$dirname]['other']) && is_array($DirectoryContents[$dirname]['other'])) { + uksort($DirectoryContents[$dirname]['other'], 'MoreNaturalSort'); + foreach ($DirectoryContents[$dirname]['other'] as $filename => $fileinfo) { + echo '<TR BGCOLOR="#'.(($rowcounter++ % 2) ? $getID3checkColor_UnknownDark : $getID3checkColor_UnknownLight).'">'; + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?filename='.urlencode($dirname.$filename).'"><I>'.$filename.'</I></A></TD>'; + echo '<TD ALIGN="RIGHT"> '.(isset($fileinfo['filesize']) ? number_format($fileinfo['filesize']) : '-').'</TD>'; + echo '<TD ALIGN="RIGHT"> '.NiceDisplayFiletypeFormat($fileinfo).'</TD>'; + echo '<TD ALIGN="RIGHT"> '.(isset($fileinfo['playtime_string']) ? $fileinfo['playtime_string'] : '-').'</TD>'; + echo '<TD ALIGN="RIGHT"> '.(isset($fileinfo['bitrate']) ? BitrateText($fileinfo['bitrate'] / 1000) : '-').'</TD>'; + echo '<TD ALIGN="LEFT"> </TD>'; // Artist + echo '<TD ALIGN="LEFT"> </TD>'; // Title + echo '<TD ALIGN="LEFT" COLSPAN="3"> </TD>'; // MD5_data + echo '<TD ALIGN="LEFT"> </TD>'; // Tags + echo '<TD ALIGN="LEFT"> </TD>'; // Warning/Error + echo '<TD ALIGN="LEFT"> </TD>'; // Edit + echo '<TD ALIGN="LEFT"> <A HREF="'.$_SERVER['PHP_SELF'].'?listdirectory='.urlencode($listdirectory).'&deletefile='.urlencode($dirname.$filename).'" onClick="return confirm(\'Are you sure you want to delete '.addslashes($dirname.$filename).'? \n(this action cannot be un-done)\');" TITLE="Permanently delete '.addslashes($dirname.$filename).'">delete</A></TD>'; + echo '</TR>'; + } + } + + echo '<TR BGCOLOR="#'.$getID3checkColor_Head.'">'; + echo '<TD><B>Average:</B></TD>'; + echo '<TD ALIGN="RIGHT">'.number_format($TotalScannedFilesize / max($TotalScannedKnownFiles, 1)).'</TD>'; + echo '<TD> </TD>'; + echo '<TD ALIGN="RIGHT">'.getid3_lib::PlaytimeString($TotalScannedPlaytime / max($TotalScannedPlaytimeFiles, 1)).'</TD>'; + echo '<TD ALIGN="RIGHT">'.BitrateText(round(($TotalScannedBitrate / 1000) / max($TotalScannedBitrateFiles, 1))).'</TD>'; + echo '<TD ROWSPAN="2" COLSPAN="'.($columnsintable - 5).'"><TABLE BORDER="0" CELLSPACING="0" CELLPADDING="2"><TR><TH ALIGN="RIGHT">Identified Files:</TH><TD ALIGN="RIGHT">'.number_format($TotalScannedKnownFiles).'</TD><TD> </TD><TH ALIGN="RIGHT">Errors:</TH><TD ALIGN="RIGHT">'.number_format($FilesWithErrors).'</TD></TR><TR><TH ALIGN="RIGHT">Unknown Files:</TH><TD ALIGN="RIGHT">'.number_format($TotalScannedUnknownFiles).'</TD><TD> </TD><TH ALIGN="RIGHT">Warnings:</TH><TD ALIGN="RIGHT">'.number_format($FilesWithWarnings).'</TD></TR></TABLE>'; + echo '</TR>'; + echo '<TR BGCOLOR="#'.$getID3checkColor_Head.'">'; + echo '<TD><B>Total:</B></TD>'; + echo '<TD ALIGN="RIGHT">'.number_format($TotalScannedFilesize).'</TD>'; + echo '<TD> </TD>'; + echo '<TD ALIGN="RIGHT">'.getid3_lib::PlaytimeString($TotalScannedPlaytime).'</TD>'; + echo '<TD> </TD>'; + echo '</TR>'; + } + echo '</TABLE>'; + } else { + echo '<B>ERROR: Could not open directory: <U>'.$currentfulldir.'</U></B><BR>'; + } +} +echo PoweredBygetID3(); +echo '</BODY></HTML>'; +ob_end_flush(); + + + + + + + + +function RemoveAccents($string) { + // return strtr($string, 'ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ', 'SOZsozYYuAAAAAAACEEEEIIIIDNOOOOOOUUUUYsaaaaaaaceeeeiiiionoooooouuuuyy'); + // Revised version by markstewardØhotmail*com + return strtr(strtr($string, 'ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÿ', 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'), array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u')); +} + + +function BitrateColor($bitrate, $BitrateMaxScale=768) { + // $BitrateMaxScale is bitrate of maximum-quality color (bright green) + // below this is gradient, above is solid green + + $bitrate *= (256 / $BitrateMaxScale); // scale from 1-[768]kbps to 1-256 + $bitrate = round(min(max($bitrate, 1), 256)); + $bitrate--; // scale from 1-256kbps to 0-255kbps + + $Rcomponent = max(255 - ($bitrate * 2), 0); + $Gcomponent = max(($bitrate * 2) - 255, 0); + if ($bitrate > 127) { + $Bcomponent = max((255 - $bitrate) * 2, 0); + } else { + $Bcomponent = max($bitrate * 2, 0); + } + return str_pad(dechex($Rcomponent), 2, '0', STR_PAD_LEFT).str_pad(dechex($Gcomponent), 2, '0', STR_PAD_LEFT).str_pad(dechex($Bcomponent), 2, '0', STR_PAD_LEFT); +} + +function BitrateText($bitrate, $decimals=0, $vbr=false) { + return '<SPAN STYLE="color: #'.BitrateColor($bitrate).($vbr ? '; font-weight: bold;' : '').'">'.number_format($bitrate, $decimals).' kbps</SPAN>'; +} + +function FixTextFields($text) { + $text = getid3_lib::SafeStripSlashes($text); + $text = htmlentities($text, ENT_QUOTES); + return $text; +} + + +function string_var_dump($variable) { + ob_start(); + var_dump($variable); + $dumpedvariable = ob_get_contents(); + ob_end_clean(); + return $dumpedvariable; +} + + +function table_var_dump($variable) { + $returnstring = ''; + switch (gettype($variable)) { + case 'array': + $returnstring .= '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="2">'; + foreach ($variable as $key => $value) { + $returnstring .= '<TR><TD VALIGN="TOP"><B>'.str_replace("\x00", ' ', $key).'</B></TD>'; + $returnstring .= '<TD VALIGN="TOP">'.gettype($value); + if (is_array($value)) { + $returnstring .= ' ('.count($value).')'; + } elseif (is_string($value)) { + $returnstring .= ' ('.strlen($value).')'; + } + if (($key == 'data') && isset($variable['image_mime']) && isset($variable['dataoffset'])) { + $imagechunkcheck = getid3_lib::GetDataImageSize($value); + $DumpedImageSRC = (!empty($_REQUEST['filename']) ? $_REQUEST['filename'] : '.getid3').'.'.$variable['dataoffset'].'.'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); + if ($tempimagefile = fopen($DumpedImageSRC, 'wb')) { + fwrite($tempimagefile, $value); + fclose($tempimagefile); + } + $returnstring .= '</TD><TD><IMG SRC="'.$DumpedImageSRC.'" WIDTH="'.$imagechunkcheck[0].'" HEIGHT="'.$imagechunkcheck[1].'"></TD></TR>'; + } else { + $returnstring .= '</TD><TD>'.table_var_dump($value).'</TD></TR>'; + } + } + $returnstring .= '</TABLE>'; + break; + + case 'boolean': + $returnstring .= ($variable ? 'TRUE' : 'FALSE'); + break; + + case 'integer': + case 'double': + case 'float': + $returnstring .= $variable; + break; + + case 'object': + case 'null': + $returnstring .= string_var_dump($variable); + break; + + case 'string': + $variable = str_replace("\x00", ' ', $variable); + $varlen = strlen($variable); + for ($i = 0; $i < $varlen; $i++) { + if (ereg('['."\x0A\x0D".' -;0-9A-Za-z]', $variable{$i})) { + $returnstring .= $variable{$i}; + } else { + $returnstring .= '&#'.str_pad(ord($variable{$i}), 3, '0', STR_PAD_LEFT).';'; + } + } + $returnstring = nl2br($returnstring); + break; + + default: + $imagechunkcheck = getid3_lib::GetDataImageSize($variable); + if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { + $returnstring .= '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="2">'; + $returnstring .= '<TR><TD><B>type</B></TD><TD>'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]).'</TD></TR>'; + $returnstring .= '<TR><TD><B>width</B></TD><TD>'.number_format($imagechunkcheck[0]).' px</TD></TR>'; + $returnstring .= '<TR><TD><B>height</B></TD><TD>'.number_format($imagechunkcheck[1]).' px</TD></TR>'; + $returnstring .= '<TR><TD><B>size</B></TD><TD>'.number_format(strlen($variable)).' bytes</TD></TR></TABLE>'; + } else { + $returnstring .= nl2br(htmlspecialchars(str_replace("\x00", ' ', $variable))); + } + break; + } + return $returnstring; +} + + +function NiceDisplayFiletypeFormat(&$fileinfo) { + + if (empty($fileinfo['fileformat'])) { + return '-'; + } + + $output = $fileinfo['fileformat']; + if (empty($fileinfo['video']['dataformat']) && empty($fileinfo['audio']['dataformat'])) { + return $output; // 'gif' + } + if (empty($fileinfo['video']['dataformat']) && !empty($fileinfo['audio']['dataformat'])) { + if ($fileinfo['fileformat'] == $fileinfo['audio']['dataformat']) { + return $output; // 'mp3' + } + $output .= '.'.$fileinfo['audio']['dataformat']; // 'ogg.flac' + return $output; + } + if (!empty($fileinfo['video']['dataformat']) && empty($fileinfo['audio']['dataformat'])) { + if ($fileinfo['fileformat'] == $fileinfo['video']['dataformat']) { + return $output; // 'mpeg' + } + $output .= '.'.$fileinfo['video']['dataformat']; // 'riff.avi' + return $output; + } + if ($fileinfo['video']['dataformat'] == $fileinfo['audio']['dataformat']) { + if ($fileinfo['fileformat'] == $fileinfo['video']['dataformat']) { + return $output; // 'real' + } + $output .= '.'.$fileinfo['video']['dataformat']; // any examples? + return $output; + } + $output .= '.'.$fileinfo['video']['dataformat']; + $output .= '.'.$fileinfo['audio']['dataformat']; // asf.wmv.wma + return $output; + +} + +/* not needed Allan Hansen +function ListOfAssumeFormatExtensions() { + // These values should almost never get used - the only use for them + // is to possibly help getID3() correctly identify a file that has + // garbage data at the beginning of the file, but a correct filename + // extension. + + //$AssumeFormatExtensions[<filename extension>] = <file format>; + + $AssumeFormatExtensions['aac'] = 'aac'; + $AssumeFormatExtensions['iff'] = 'aiff'; + $AssumeFormatExtensions['aif'] = 'aiff'; + $AssumeFormatExtensions['aifc'] = 'aiff'; + $AssumeFormatExtensions['iff'] = 'aiff'; + $AssumeFormatExtensions['aiff'] = 'aiff'; + $AssumeFormatExtensions['wmv'] = 'asf'; + $AssumeFormatExtensions['wma'] = 'asf'; + $AssumeFormatExtensions['asf'] = 'asf'; + $AssumeFormatExtensions['au'] = 'au'; + $AssumeFormatExtensions['bmp'] = 'bmp'; + $AssumeFormatExtensions['mod'] = 'bonk'; + $AssumeFormatExtensions['bonk'] = 'bonk'; + $AssumeFormatExtensions['flac'] = 'flac'; + $AssumeFormatExtensions['gif'] = 'gif'; + $AssumeFormatExtensions['iso'] = 'iso'; + $AssumeFormatExtensions['jpeg'] = 'jpg'; + $AssumeFormatExtensions['jpg'] = 'jpg'; + $AssumeFormatExtensions['la'] = 'la'; + $AssumeFormatExtensions['pac'] = 'lpac'; + $AssumeFormatExtensions['mac'] = 'mac'; + $AssumeFormatExtensions['ape'] = 'mac'; + $AssumeFormatExtensions['mid'] = 'midi'; + $AssumeFormatExtensions['midi'] = 'midi'; + $AssumeFormatExtensions['mid'] = 'midi'; + $AssumeFormatExtensions['xm'] = 'mod'; + $AssumeFormatExtensions['it'] = 'mod'; + $AssumeFormatExtensions['s3m'] = 'mod'; + $AssumeFormatExtensions['mp3'] = 'mp3'; + $AssumeFormatExtensions['mp2'] = 'mp3'; + $AssumeFormatExtensions['mp1'] = 'mp3'; + $AssumeFormatExtensions['mpc'] = 'mpc'; + $AssumeFormatExtensions['mpg'] = 'mpeg'; + $AssumeFormatExtensions['mpeg'] = 'mpeg'; + $AssumeFormatExtensions['nsv'] = 'nsv'; + $AssumeFormatExtensions['ofr'] = 'ofr'; + $AssumeFormatExtensions['spx'] = 'ogg'; + $AssumeFormatExtensions['ogg'] = 'ogg'; + $AssumeFormatExtensions['png'] = 'png'; + $AssumeFormatExtensions['mov'] = 'quicktime'; + $AssumeFormatExtensions['qt'] = 'quicktime'; + $AssumeFormatExtensions['rar'] = 'rar'; + $AssumeFormatExtensions['ra'] = 'real'; + $AssumeFormatExtensions['ram'] = 'real'; + $AssumeFormatExtensions['rm'] = 'real'; + $AssumeFormatExtensions['wav'] = 'riff'; + $AssumeFormatExtensions['wv'] = 'riff'; + $AssumeFormatExtensions['vox'] = 'riff'; + $AssumeFormatExtensions['cda'] = 'riff'; + $AssumeFormatExtensions['xvid'] = 'riff'; + $AssumeFormatExtensions['avi'] = 'riff'; + $AssumeFormatExtensions['divx'] = 'riff'; + $AssumeFormatExtensions['avi'] = 'riff'; + $AssumeFormatExtensions['wav'] = 'riff'; + $AssumeFormatExtensions['rka'] = 'rkau'; + $AssumeFormatExtensions['swf'] = 'swf'; + $AssumeFormatExtensions['sz'] = 'szip'; + $AssumeFormatExtensions['voc'] = 'voc'; + $AssumeFormatExtensions['vqf'] = 'vqf'; + $AssumeFormatExtensions['zip'] = 'zip'; + + return $AssumeFormatExtensions; +} +*/ + + +function MoreNaturalSort($ar1, $ar2) { + if ($ar1 === $ar2) { + return 0; + } + $len1 = strlen($ar1); + $len2 = strlen($ar2); + $shortest = min($len1, $len2); + if (substr($ar1, 0, $shortest) === substr($ar2, 0, $shortest)) { + // the shorter argument is the beginning of the longer one, like "str" and "string" + if ($len1 < $len2) { + return -1; + } elseif ($len1 > $len2) { + return 1; + } + return 0; + } + $ar1 = RemoveAccents(strtolower(trim($ar1))); + $ar2 = RemoveAccents(strtolower(trim($ar2))); + $translatearray = array('\''=>'', '"'=>'', '_'=>' ', '('=>'', ')'=>'', '-'=>' ', ' '=>' ', '.'=>'', ','=>''); + foreach ($translatearray as $key => $val) { + $ar1 = str_replace($key, $val, $ar1); + $ar2 = str_replace($key, $val, $ar2); + } + + if ($ar1 < $ar2) { + return -1; + } elseif ($ar1 > $ar2) { + return 1; + } + return 0; +} + +function PoweredBygetID3($string='<BR><HR NOSHADE><DIV STYLE="font-size: 8pt; font-face: sans-serif;">Powered by <A HREF="http://getid3.sourceforge.net" TARGET="_blank"><B>getID3() v<!--GETID3VER--></B><BR>http://getid3.sourceforge.net</A></DIV>') { + return str_replace('<!--GETID3VER-->', GETID3_VERSION, $string); +} + +?> +</BODY> +</HTML>
\ No newline at end of file diff --git a/modules/id3/demos/demo.cache.dbm.php b/modules/id3/demos/demo.cache.dbm.php new file mode 100644 index 00000000..acaaa0f3 --- /dev/null +++ b/modules/id3/demos/demo.cache.dbm.php @@ -0,0 +1,29 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// /demo/demo.cache.dbm.php - part of getID3() // +// Sample script demonstrating the use of the DBM caching // +// extension for getID3() // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + +require_once('../getid3/getid3.php'); +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'extension.cache.dbm.php', __FILE__, true); + +$getID3 = new getID3_cached_dbm('db3', '/zimweb/test/test.dbm', '/zimweb/test/test.lock'); + +$r = $getID3->analyze('/path/to/files/filename.mp3'); + +echo '<pre>'; +var_dump($r); +echo '</pre>'; + +// uncomment to clear cache +// $getID3->clear_cache(); + +?>
\ No newline at end of file diff --git a/modules/id3/demos/demo.cache.mysql.php b/modules/id3/demos/demo.cache.mysql.php new file mode 100644 index 00000000..537b2f0c --- /dev/null +++ b/modules/id3/demos/demo.cache.mysql.php @@ -0,0 +1,29 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// /demo/demo.cache.mysql.php - part of getID3() // +// Sample script demonstrating the use of the DBM caching // +// extension for getID3() // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + +require_once('../getid3/getid3.php'); +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'extension.cache.mysql.php', __FILE__, true); + +$getID3 = new getID3_cached_mysql('localhost', 'database', 'username', 'password'); + +$r = $getID3->analyze('/path/to/files/filename.mp3'); + +echo '<pre>'; +var_dump($r); +echo '</pre>'; + +// uncomment to clear cache +//$getID3->clear_cache(); + +?>
\ No newline at end of file diff --git a/modules/id3/demos/demo.joinmp3.php b/modules/id3/demos/demo.joinmp3.php new file mode 100644 index 00000000..976884f9 --- /dev/null +++ b/modules/id3/demos/demo.joinmp3.php @@ -0,0 +1,96 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// /demo/demo.joinmp3.php - part of getID3() // +// Sample script for splicing two or more MP3s together into // +// one file. Does not attempt to fix VBR header frames. // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + + +// sample usage: +// $FilenameOut = 'combined.mp3'; +// $FilenamesIn[] = 'file1.mp3'; +// $FilenamesIn[] = 'file2.mp3'; +// $FilenamesIn[] = 'file3.mp3'; +// +// if (CombineMultipleMP3sTo($FilenameOut, $FilenamesIn)) { +// echo 'Successfully copied '.implode(' + ', $FilenamesIn).' to '.$FilenameOut; +// } else { +// echo 'Failed to copy '.implode(' + ', $FilenamesIn).' to '.$FilenameOut; +// } + +function CombineMultipleMP3sTo($FilenameOut, $FilenamesIn) { + + foreach ($FilenamesIn as $nextinputfilename) { + if (!is_readable($nextinputfilename)) { + echo 'Cannot read "'.$nextinputfilename.'"<BR>'; + return false; + } + } + if (!is_writeable($FilenameOut)) { + echo 'Cannot write "'.$FilenameOut.'"<BR>'; + return false; + } + + require_once('../getid3/getid3.php'); + if ($fp_output = @fopen($FilenameOut, 'wb')) { + + // Initialize getID3 engine + $getID3 = new getID3; + foreach ($FilenamesIn as $nextinputfilename) { + + $CurrentFileInfo = $getID3->analyze($nextinputfilename); + if ($CurrentFileInfo['fileformat'] == 'mp3') { + + if ($fp_source = @fopen($nextinputfilename, 'rb')) { + + $CurrentOutputPosition = ftell($fp_output); + + // copy audio data from first file + fseek($fp_source, $CurrentFileInfo['avdataoffset'], SEEK_SET); + while (!feof($fp_source) && (ftell($fp_source) < $CurrentFileInfo['avdataend'])) { + fwrite($fp_output, fread($fp_source, 32768)); + } + fclose($fp_source); + + // trim post-audio data (if any) copied from first file that we don't need or want + $EndOfFileOffset = $CurrentOutputPosition + ($CurrentFileInfo['avdataend'] - $CurrentFileInfo['avdataoffset']); + fseek($fp_output, $EndOfFileOffset, SEEK_SET); + ftruncate($fp_output, $EndOfFileOffset); + + } else { + + echo 'failed to open '.$nextinputfilename.' for reading'; + fclose($fp_output); + return false; + + } + + } else { + + echo $nextinputfilename.' is not MP3 format'; + fclose($fp_output); + return false; + + } + + } + + } else { + + echo 'failed to open '.$FilenameOut.' for writing'; + return false; + + } + + fclose($fp_output); + return true; +} + +?>
\ No newline at end of file diff --git a/modules/id3/demos/demo.mimeonly.php b/modules/id3/demos/demo.mimeonly.php new file mode 100644 index 00000000..dd6dec6f --- /dev/null +++ b/modules/id3/demos/demo.mimeonly.php @@ -0,0 +1,53 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// /demo/demo.mimeonly.php - part of getID3() // +// Sample script for scanning a single file and returning only // +// the MIME information // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + +echo '<HTML><HEAD><STYLE>BODY, TD, TH { font-family: sans-serif; font-size: 10pt; }</STYLE></HEAD><BODY>'; + +if (!empty($_REQUEST['filename'])) { + + echo 'The file "'.$_REQUEST['filename'].'" has a MIME type of "'.GetMIMEtype($_REQUEST['filename']).'"'; + +} else { + + echo 'Usage: <TT>'.$_SERVER['PHP_SELF'].'?filename=<I>filename.ext</I></TT>'; + +} + + +function GetMIMEtype($filename) { + // include getID3() library (can be in a different directory if full path is specified) + require_once('../getid3/getid3.php'); + // Initialize getID3 engine + $getID3 = new getID3; + + $DeterminedMIMEtype = ''; + if ($fp = fopen($filename, 'rb')) { + $ThisFileInfo = array('avdataoffset'=>0, 'avdataend'=>0); + + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); + $tag = new getid3_id3v2($fp, $ThisFileInfo); + + fseek($fp, $ThisFileInfo['avdataoffset'], SEEK_SET); + $formattest = fread($fp, 16); // 16 bytes is sufficient for any format except ISO CD-image + fclose($fp); + + $DeterminedFormatInfo = $getID3->GetFileFormat($formattest); + $DeterminedMIMEtype = $DeterminedFormatInfo['mime_type']; + } + return $DeterminedMIMEtype; +} + +?> +</BODY> +</HTML>
\ No newline at end of file diff --git a/modules/id3/demos/demo.mysql.php b/modules/id3/demos/demo.mysql.php new file mode 100644 index 00000000..f44c62ad --- /dev/null +++ b/modules/id3/demos/demo.mysql.php @@ -0,0 +1,1825 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// /demo/demo.mysql.php - part of getID3() // +// Sample script for recursively scanning directories and // +// storing the results in a database // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + +// OPTIONS: +$getid3_demo_mysql_encoding = 'ISO-8859-1'; +$getid3_demo_mysql_md5_data = false; // All data hashes are by far the slowest part of scanning +$getid3_demo_mysql_md5_file = false; + + +if (!@mysql_connect('localhost', 'getid3', 'getid3')) { + die('Could not connect to MySQL host: <BLOCKQUOTE STYLE="background-color: #FF9933; padding: 10px;">'.mysql_error().'</BLOCKQUOTE>'); +} +if (!@mysql_select_db('getid3')) { + die('Could not select database: <BLOCKQUOTE STYLE="background-color: #FF9933; padding: 10px;">'.mysql_error().'</BLOCKQUOTE>'); +} + +if (!@include_once('../getid3/getid3.php')) { + die('Cannot open '.realpath('../getid3/getid3.php')); +} +// Initialize getID3 engine +$getID3 = new getID3; +$getID3->option_md5_data = $getid3_demo_mysql_md5_data; +$getID3->encoding = $getid3_demo_mysql_encoding; + + +function RemoveAccents($string) { + // Revised version by markstewardØhotmail*com + return strtr(strtr($string, 'ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÿ', 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy'), array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u')); +} + +function FixTextFields($text) { + $text = getid3_lib::SafeStripSlashes($text); + $text = htmlentities($text, ENT_QUOTES); + return $text; +} + +function BitrateColor($bitrate, $BitrateMaxScale=768) { + // $BitrateMaxScale is bitrate of maximum-quality color (bright green) + // below this is gradient, above is solid green + + $bitrate *= (256 / $BitrateMaxScale); // scale from 1-[768]kbps to 1-256 + $bitrate = round(min(max($bitrate, 1), 256)); + $bitrate--; // scale from 1-256kbps to 0-255kbps + + $Rcomponent = max(255 - ($bitrate * 2), 0); + $Gcomponent = max(($bitrate * 2) - 255, 0); + if ($bitrate > 127) { + $Bcomponent = max((255 - $bitrate) * 2, 0); + } else { + $Bcomponent = max($bitrate * 2, 0); + } + return str_pad(dechex($Rcomponent), 2, '0', STR_PAD_LEFT).str_pad(dechex($Gcomponent), 2, '0', STR_PAD_LEFT).str_pad(dechex($Bcomponent), 2, '0', STR_PAD_LEFT); +} + +function BitrateText($bitrate, $decimals=0) { + return '<SPAN STYLE="color: #'.BitrateColor($bitrate).'">'.number_format($bitrate, $decimals).' kbps</SPAN>'; +} + +function fileextension($filename, $numextensions=1) { + if (strstr($filename, '.')) { + $reversedfilename = strrev($filename); + $offset = 0; + for ($i = 0; $i < $numextensions; $i++) { + $offset = strpos($reversedfilename, '.', $offset + 1); + if ($offset === false) { + return ''; + } + } + return strrev(substr($reversedfilename, 0, $offset)); + } + return ''; +} + +if (!empty($_REQUEST['renamefilefrom']) && !empty($_REQUEST['renamefileto'])) { + + if ($_REQUEST['renamefilefrom'] === $_REQUEST['renamefileto']) { + $results = '<SPAN STYLE="color: #FF0000;"><B>Source and Destination filenames identical</B><BR>FAILED to rename'; + } elseif (!file_exists($_REQUEST['renamefilefrom'])) { + $results = '<SPAN STYLE="color: #FF0000;"><B>Source file does not exist</B><BR>FAILED to rename'; + } elseif (file_exists($_REQUEST['renamefileto']) && (strtolower($_REQUEST['renamefilefrom']) !== strtolower($_REQUEST['renamefileto']))) { + $results = '<SPAN STYLE="color: #FF0000;"><B>Destination file already exists</B><BR>FAILED to rename'; + } elseif (@rename($_REQUEST['renamefilefrom'], $_REQUEST['renamefileto'])) { + $SQLquery = 'DELETE FROM `files` WHERE (filename = "'.mysql_escape_string($_REQUEST['renamefilefrom']).'")'; + safe_mysql_query($SQLquery); + $results = '<SPAN STYLE="color: #008000;">Successfully renamed'; + } else { + $results = '<BR><SPAN STYLE="color: #FF0000;">FAILED to rename'; + } + $results .= ' from:<BR><I>'.$_REQUEST['renamefilefrom'].'</I><BR>to:<BR><I>'.$_REQUEST['renamefileto'].'</I></SPAN><HR>'; + echo $results; + exit; + +} elseif (!empty($_REQUEST['m3ufilename'])) { + + header('Content-type: audio/x-mpegurl'); + echo '#EXTM3U'."\n"; + echo WindowsShareSlashTranslate($_REQUEST['m3ufilename'])."\n"; + exit; + +} elseif (!isset($_REQUEST['m3u']) && !isset($_REQUEST['m3uartist']) && !isset($_REQUEST['m3utitle'])) { + + echo '<HTML><HEAD><TITLE>getID3() demo - /demo/mysql.php</TITLE><STYLE>BODY, TD, TH { font-family: sans-serif; font-size: 10pt; } A { text-decoration: none; } A:hover { text-decoration: underline; } A:visited { font-style: italic; }</STYLE></HEAD><BODY>'; + +} + + +function WindowsShareSlashTranslate($filename) { + if (substr($filename, 0, 2) == '//') { + return str_replace('/', '\\', $filename); + } + return $filename; +} + +function safe_mysql_query($SQLquery) { + $result = @mysql_query($SQLquery); + if (mysql_error()) { + die('<FONT COLOR="red">'.mysql_error().'</FONT><HR><TT>'.$SQLquery.'</TT>'); + } + return $result; +} + +function mysql_table_exists($tablename) { + return (bool) mysql_query('DESCRIBE '.$tablename); +} + +function AcceptableExtensions($fileformat, $audio_dataformat='', $video_dataformat='') { + static $AcceptableExtensionsAudio = array(); + if (empty($AcceptableExtensionsAudio)) { + $AcceptableExtensionsAudio['mp3']['mp3'] = array('mp3'); + $AcceptableExtensionsAudio['mp2']['mp2'] = array('mp2'); + $AcceptableExtensionsAudio['mp1']['mp1'] = array('mp1'); + $AcceptableExtensionsAudio['asf']['asf'] = array('asf'); + $AcceptableExtensionsAudio['asf']['wma'] = array('wma'); + $AcceptableExtensionsAudio['riff']['mp3'] = array('wav'); + $AcceptableExtensionsAudio['riff']['wav'] = array('wav'); + } + static $AcceptableExtensionsVideo = array(); + if (empty($AcceptableExtensionsVideo)) { + $AcceptableExtensionsVideo['mp3']['mp3'] = array('mp3'); + $AcceptableExtensionsVideo['mp2']['mp2'] = array('mp2'); + $AcceptableExtensionsVideo['mp1']['mp1'] = array('mp1'); + $AcceptableExtensionsVideo['asf']['asf'] = array('asf'); + $AcceptableExtensionsVideo['asf']['wmv'] = array('wmv'); + $AcceptableExtensionsVideo['gif']['gif'] = array('gif'); + $AcceptableExtensionsVideo['jpg']['jpg'] = array('jpg'); + $AcceptableExtensionsVideo['png']['png'] = array('png'); + $AcceptableExtensionsVideo['bmp']['bmp'] = array('bmp'); + } + if (!empty($video_dataformat)) { + return (isset($AcceptableExtensionsVideo[$fileformat][$video_dataformat]) ? $AcceptableExtensionsVideo[$fileformat][$video_dataformat] : array()); + } else { + return (isset($AcceptableExtensionsAudio[$fileformat][$audio_dataformat]) ? $AcceptableExtensionsAudio[$fileformat][$audio_dataformat] : array()); + } +} + + +if (!empty($_REQUEST['scan'])) { + if (mysql_table_exists('files')) { + $SQLquery = 'DROP TABLE files'; + safe_mysql_query($SQLquery); + } +} +if (!mysql_table_exists('files')) { + $SQLquery = 'CREATE TABLE `files` ('; + $SQLquery .= ' `ID` mediumint(8) unsigned NOT NULL auto_increment,'; + $SQLquery .= ' `filename` text NOT NULL,'; + $SQLquery .= ' `LastModified` text NOT NULL,'; + $SQLquery .= ' `md5_file` varchar(32) NOT NULL default "",'; + $SQLquery .= ' `md5_data` varchar(32) NOT NULL default "",'; + $SQLquery .= ' `md5_data_source` varchar(32) NOT NULL default "",'; + $SQLquery .= ' `filesize` int(10) unsigned NOT NULL default "0",'; + $SQLquery .= ' `fileformat` varchar(255) NOT NULL default "",'; + $SQLquery .= ' `audio_dataformat` varchar(255) NOT NULL default "",'; + $SQLquery .= ' `video_dataformat` varchar(255) NOT NULL default "",'; + $SQLquery .= ' `audio_bitrate` float NOT NULL default "0",'; + $SQLquery .= ' `video_bitrate` float NOT NULL default "0",'; + $SQLquery .= ' `playtime_seconds` varchar(255) NOT NULL default "",'; + $SQLquery .= ' `tags` varchar(255) NOT NULL default "",'; + $SQLquery .= ' `artist` varchar(255) NOT NULL default "",'; + $SQLquery .= ' `title` varchar(255) NOT NULL default "",'; + $SQLquery .= ' `album` varchar(255) NOT NULL default "",'; + $SQLquery .= ' `genre` varchar(255) NOT NULL default "",'; + $SQLquery .= ' `comment` varchar(255) NOT NULL default "",'; + $SQLquery .= ' `track` varchar(7) NOT NULL default "",'; + $SQLquery .= ' `comments_all` text NOT NULL,'; + $SQLquery .= ' `comments_id3v2` text NOT NULL,'; + $SQLquery .= ' `comments_ape` text NOT NULL,'; + $SQLquery .= ' `comments_lyrics3` text NOT NULL,'; + $SQLquery .= ' `comments_id3v1` text NOT NULL,'; + $SQLquery .= ' `warning` text NOT NULL,'; + $SQLquery .= ' `error` text NOT NULL,'; + $SQLquery .= ' `track_volume` float NOT NULL default "0",'; + $SQLquery .= ' `encoder_options` varchar(255) NOT NULL default "",'; + $SQLquery .= ' `vbr_method` varchar(255) NOT NULL default "",'; + $SQLquery .= ' PRIMARY KEY (`ID`)'; + $SQLquery .= ') TYPE=MyISAM;'; + + safe_mysql_query($SQLquery); +} + +$ExistingTableFields = array(); +$result = mysql_query('DESCRIBE `files`'); +while ($row = mysql_fetch_array($result)) { + $ExistingTableFields[$row['Field']] = $row; +} +if (!isset($ExistingTableFields['encoder_options'])) { // Added in 1.7.0b2 + echo '<B>adding field `encoder_options`</B><BR>'; + mysql_query('ALTER TABLE `files` ADD `encoder_options` VARCHAR(255) DEFAULT "" NOT NULL AFTER `error`'); + mysql_query('OPTIMIZE TABLE `files`'); +} +if (isset($ExistingTableFields['track']) && ($ExistingTableFields['track']['Type'] != 'varchar(7)')) { // Changed in 1.7.0b2 + echo '<B>changing field `track` to VARCHAR(7)</B><BR>'; + mysql_query('ALTER TABLE `files` CHANGE `track` `track` VARCHAR(7) DEFAULT "" NOT NULL'); + mysql_query('OPTIMIZE TABLE `files`'); +} +if (!isset($ExistingTableFields['track_volume'])) { // Added in 1.7.0b5 + echo '<H1><FONT COLOR="red">WARNING! You should erase your database and rescan everything because the comment storing has been changed since the last version</FONT></H1><HR>'; + echo '<B>adding field `track_volume`</B><BR>'; + mysql_query('ALTER TABLE `files` ADD `track_volume` FLOAT NOT NULL AFTER `error`'); + mysql_query('OPTIMIZE TABLE `files`'); +} + + +function SynchronizeAllTags($filename, $synchronizefrom='all', $synchronizeto='A12', &$errors) { + global $getID3; + + set_time_limit(30); + + $ThisFileInfo = $getID3->analyze($filename); + getid3_lib::CopyTagsToComments($ThisFileInfo); + + if ($synchronizefrom == 'all') { + $SourceArray = $ThisFileInfo['comments']; + } elseif (!empty($ThisFileInfo['tags'][$synchronizefrom])) { + $SourceArray = $ThisFileInfo['tags'][$synchronizefrom]; + } else { + die('ERROR: $ThisFileInfo[tags]['.$synchronizefrom.'] does not exist'); + } + + $SQLquery = 'DELETE FROM `files` WHERE (filename = "'.mysql_escape_string($filename).'")'; + safe_mysql_query($SQLquery); + + + $TagFormatsToWrite = array(); + if ((strpos($synchronizeto, '2') !== false) && ($synchronizefrom != 'id3v2')) { + $TagFormatsToWrite[] = 'id3v2.3'; + } + if ((strpos($synchronizeto, 'A') !== false) && ($synchronizefrom != 'ape')) { + $TagFormatsToWrite[] = 'ape'; + } + if ((strpos($synchronizeto, 'L') !== false) && ($synchronizefrom != 'lyrics3')) { + $TagFormatsToWrite[] = 'lyrics3'; + } + if ((strpos($synchronizeto, '1') !== false) && ($synchronizefrom != 'id3v1')) { + $TagFormatsToWrite[] = 'id3v1'; + } + + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.php', __FILE__, true); + $tagwriter = new getid3_writetags; + $tagwriter->filename = $filename; + $tagwriter->tagformats = $TagFormatsToWrite; + $tagwriter->overwrite_tags = true; + $tagwriter->tag_encoding = $getID3->encoding; + $tagwriter->tag_data = $SourceArray; + + if ($tagwriter->WriteTags()) { + $errors = $tagwriter->errors; + return true; + } + $errors = $tagwriter->errors; + return false; +} + +$IgnoreNoTagFormats = array('', 'png', 'jpg', 'gif', 'bmp', 'swf', 'zip', 'mid', 'mod', 'xm', 'it', 's3m'); + +if (!empty($_REQUEST['scan']) || !empty($_REQUEST['newscan']) || !empty($_REQUEST['rescanerrors'])) { + + $SQLquery = 'DELETE from `files` WHERE (fileformat = "")'; + safe_mysql_query($SQLquery); + + $FilesInDir = array(); + + if (!empty($_REQUEST['rescanerrors'])) { + + echo '<A HREF="'.$_SERVER['PHP_SELF'].'">abort</A><HR>'; + + echo 'Re-scanning all media files already in database that had errors and/or warnings in last scan<HR>'; + + $SQLquery = 'SELECT filename FROM `files` WHERE (error <> "") OR (warning <> "") ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + while ($row = mysql_fetch_array($result)) { + + if (!file_exists($row['filename'])) { + echo '<B>File missing: '.$row['filename'].'</B><BR>'; + $SQLquery = 'DELETE FROM `files` WHERE (filename = "'.mysql_escape_string($row['filename']).'")'; + safe_mysql_query($SQLquery); + } else { + $FilesInDir[] = $row['filename']; + } + + } + + } elseif (!empty($_REQUEST['scan']) || !empty($_REQUEST['newscan'])) { + + echo '<A HREF="'.$_SERVER['PHP_SELF'].'">abort</A><HR>'; + + echo 'Scanning all media files in <B>'.str_replace('\\', '/', realpath(!empty($_REQUEST['scan']) ? $_REQUEST['scan'] : $_REQUEST['newscan'])).'</B> (and subdirectories)<HR>'; + + $SQLquery = 'SELECT COUNT(*) AS num, filename'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' GROUP BY filename'; + $SQLquery .= ' ORDER BY num DESC'; + $result = safe_mysql_query($SQLquery); + $DupesDeleted = 0; + while ($row = mysql_fetch_array($result)) { + set_time_limit(30); + if ($row['num'] <= 1) { + break; + } + $SQLquery = 'DELETE FROM `files` WHERE filename LIKE "'.mysql_escape_string($row['filename']).'"'; + safe_mysql_query($SQLquery); + $DupesDeleted++; + } + if ($DupesDeleted > 0) { + echo 'Deleted <B>'.number_format($DupesDeleted).'</B> duplicate filenames<HR>'; + } + + if (!empty($_REQUEST['newscan'])) { + $AlreadyInDatabase = array(); + set_time_limit(60); + $SQLquery = 'SELECT filename FROM `files` ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + while ($row = mysql_fetch_array($result)) { + //$AlreadyInDatabase[] = strtolower($row['filename']); + $AlreadyInDatabase[] = $row['filename']; + } + } + + $DirectoriesToScan = array(realpath(!empty($_REQUEST['scan']) ? $_REQUEST['scan'] : $_REQUEST['newscan'])); + $DirectoriesScanned = array(); + while (count($DirectoriesToScan) > 0) { + foreach ($DirectoriesToScan as $DirectoryKey => $startingdir) { + if ($dir = @opendir($startingdir)) { + set_time_limit(30); + echo '<B>'.str_replace('\\', '/', $startingdir).'</B><BR>'; + flush(); + while (($file = readdir($dir)) !== false) { + if (($file != '.') && ($file != '..')) { + $RealPathName = realpath($startingdir.'/'.$file); + if (is_dir($RealPathName)) { + if (!in_array($RealPathName, $DirectoriesScanned) && !in_array($RealPathName, $DirectoriesToScan)) { + $DirectoriesToScan[] = $RealPathName; + } + } else if (is_file($RealPathName)) { + if (!empty($_REQUEST['newscan'])) { + //if (!in_array(strtolower(str_replace('\\', '/', $RealPathName)), $AlreadyInDatabase)) { + if (!in_array(str_replace('\\', '/', $RealPathName), $AlreadyInDatabase)) { + $FilesInDir[] = $RealPathName; + } else { + } + } elseif (!empty($_REQUEST['scan'])) { + $FilesInDir[] = $RealPathName; + } + } + } + } + closedir($dir); + } else { + echo '<FONT COLOR="RED">Failed to open directory <B>'.$startingdir.'</B></FONT><BR><BR>'; + } + $DirectoriesScanned[] = $startingdir; + unset($DirectoriesToScan[$DirectoryKey]); + } + } + echo '<I>List of files to scan complete (added '.number_format(count($FilesInDir)).' files to scan)</I><HR>'; + flush(); + } + + $FilesInDir = array_unique($FilesInDir); + sort($FilesInDir); + + $starttime = time(); + $rowcounter = 0; + $totaltoprocess = count($FilesInDir); + + foreach ($FilesInDir as $filename) { + set_time_limit(300); + + echo '<BR>'.date('H:i:s').' ['.number_format(++$rowcounter).' / '.number_format($totaltoprocess).'] '.str_replace('\\', '/', $filename); + + $ThisFileInfo = $getID3->analyze($filename); + getid3_lib::CopyTagsToComments($ThisFileInfo); + + if (file_exists($filename)) { + $ThisFileInfo['file_modified_time'] = filemtime($filename); + $ThisFileInfo['md5_file'] = ($getid3_demo_mysql_md5_file ? md5_file($filename) : ''); + } + + if (empty($ThisFileInfo['fileformat'])) { + + echo ' (<SPAN STYLE="color: #990099;">unknown file type</SPAN>)'; + + } else { + + if (!empty($ThisFileInfo['error'])) { + echo ' (<SPAN STYLE="color: #FF0000;">errors</SPAN>)'; + } elseif (!empty($ThisFileInfo['warning'])) { + echo ' (<SPAN STYLE="color: #FF9999;">warnings</SPAN>)'; + } else { + echo ' (<SPAN STYLE="color: #009900;">OK</SPAN>)'; + } + + if (!empty($_REQUEST['rescanerrors'])) { + + $SQLquery = 'UPDATE `files` SET '; + $SQLquery .= 'LastModified = "'.mysql_escape_string(@$ThisFileInfo['file_modified_time']).'", '; + $SQLquery .= 'md5_file = "'.mysql_escape_string(@$ThisFileInfo['md5_file']).'", '; + $SQLquery .= 'md5_data = "'.mysql_escape_string(@$ThisFileInfo['md5_data']).'", '; + $SQLquery .= 'md5_data_source = "'.mysql_escape_string(@$ThisFileInfo['md5_data_source']).'", '; + $SQLquery .= 'filesize = "'.mysql_escape_string(@$ThisFileInfo['filesize']).'", '; + $SQLquery .= 'fileformat = "'.mysql_escape_string(@$ThisFileInfo['fileformat']).'", '; + $SQLquery .= 'audio_dataformat = "'.mysql_escape_string(@$ThisFileInfo['audio']['dataformat']).'", '; + $SQLquery .= 'video_dataformat = "'.mysql_escape_string(@$ThisFileInfo['video']['dataformat']).'", '; + $SQLquery .= 'audio_bitrate = "'.mysql_escape_string(@$ThisFileInfo['audio']['bitrate']).'", '; + $SQLquery .= 'video_bitrate = "'.mysql_escape_string(@$ThisFileInfo['video']['bitrate']).'", '; + $SQLquery .= 'playtime_seconds = "'.mysql_escape_string(@$ThisFileInfo['playtime_seconds']).'", '; + $SQLquery .= 'tags = "'.mysql_escape_string(@implode("\t", @array_keys(@$ThisFileInfo['tags']))).'", '; + $SQLquery .= 'artist = "'.mysql_escape_string(@implode("\t", @$ThisFileInfo['comments']['artist'])).'", '; + $SQLquery .= 'title = "'.mysql_escape_string(@implode("\t", @$ThisFileInfo['comments']['title'])).'", '; + $SQLquery .= 'album = "'.mysql_escape_string(@implode("\t", @$ThisFileInfo['comments']['album'])).'", '; + $SQLquery .= 'genre = "'.mysql_escape_string(@implode("\t", @$ThisFileInfo['comments']['genre'])).'", '; + $SQLquery .= 'comment = "'.mysql_escape_string(@implode("\t", @$ThisFileInfo['comments']['comment'])).'", '; + $SQLquery .= 'track = "'.mysql_escape_string(@implode("\t", @$ThisFileInfo['comments']['track'])).'", '; + $SQLquery .= 'comments_all = "'.mysql_escape_string(@serialize(@$ThisFileInfo['comments'])).'", '; + $SQLquery .= 'comments_id3v2 = "'.mysql_escape_string(@serialize(@$ThisFileInfo['tags']['id3v2'])).'", '; + $SQLquery .= 'comments_ape = "'.mysql_escape_string(@serialize(@$ThisFileInfo['tags']['ape'])).'", '; + $SQLquery .= 'comments_lyrics3 = "'.mysql_escape_string(@serialize(@$ThisFileInfo['tags']['lyrics3'])).'", '; + $SQLquery .= 'comments_id3v1 = "'.mysql_escape_string(@serialize(@$ThisFileInfo['tags']['id3v1'])).'", '; + $SQLquery .= 'warning = "'.mysql_escape_string(@implode("\t", @$ThisFileInfo['warning'])).'", '; + $SQLquery .= 'error = "'.mysql_escape_string(@implode("\t", @$ThisFileInfo['error'])).'", '; + $SQLquery .= 'encoder_options = "'.mysql_escape_string(trim(@$ThisFileInfo['audio']['encoder'].' '.@$ThisFileInfo['audio']['encoder_options'])).'", '; + $SQLquery .= 'vbr_method = "'.mysql_escape_string(@$ThisFileInfo['mpeg']['audio']['VBR_method']).'", '; + $SQLquery .= 'track_volume = "'.mysql_escape_string(@$ThisFileInfo['replay_gain']['track']['volume']).'" '; + $SQLquery .= 'WHERE (filename = "'.mysql_escape_string(@$ThisFileInfo['filenamepath']).'")'; + + } elseif (!empty($_REQUEST['scan']) || !empty($_REQUEST['newscan'])) { + + $SQLquery = 'INSERT INTO `files` (filename, LastModified, md5_file, md5_data, md5_data_source, filesize, fileformat, audio_dataformat, video_dataformat, audio_bitrate, video_bitrate, playtime_seconds, tags, artist, title, album, genre, comment, track, comments_all, comments_id3v2, comments_ape, comments_lyrics3, comments_id3v1, warning, error, encoder_options, vbr_method, track_volume) VALUES ('; + $SQLquery .= '"'.mysql_escape_string(@$ThisFileInfo['filenamepath']).'", '; + $SQLquery .= '"'.mysql_escape_string(@$ThisFileInfo['file_modified_time']).'", '; + $SQLquery .= '"'.mysql_escape_string(@$ThisFileInfo['md5_file']).'", '; + $SQLquery .= '"'.mysql_escape_string(@$ThisFileInfo['md5_data']).'", '; + $SQLquery .= '"'.mysql_escape_string(@$ThisFileInfo['md5_data_source']).'", '; + $SQLquery .= '"'.mysql_escape_string(@$ThisFileInfo['filesize']).'", '; + $SQLquery .= '"'.mysql_escape_string(@$ThisFileInfo['fileformat']).'", '; + $SQLquery .= '"'.mysql_escape_string(@$ThisFileInfo['audio']['dataformat']).'", '; + $SQLquery .= '"'.mysql_escape_string(@$ThisFileInfo['video']['dataformat']).'", '; + $SQLquery .= '"'.mysql_escape_string(@$ThisFileInfo['audio']['bitrate']).'", '; + $SQLquery .= '"'.mysql_escape_string(@$ThisFileInfo['video']['bitrate']).'", '; + $SQLquery .= '"'.mysql_escape_string(@$ThisFileInfo['playtime_seconds']).'", '; + $SQLquery .= '"'.mysql_escape_string(@implode("\t", @array_keys(@$ThisFileInfo['tags']))).'", '; + $SQLquery .= '"'.mysql_escape_string(@implode("\t", @$ThisFileInfo['comments']['artist'])).'", '; + $SQLquery .= '"'.mysql_escape_string(@implode("\t", @$ThisFileInfo['comments']['title'])).'", '; + $SQLquery .= '"'.mysql_escape_string(@implode("\t", @$ThisFileInfo['comments']['album'])).'", '; + $SQLquery .= '"'.mysql_escape_string(@implode("\t", @$ThisFileInfo['comments']['genre'])).'", '; + $SQLquery .= '"'.mysql_escape_string(@implode("\t", @$ThisFileInfo['comments']['comment'])).'", '; + $SQLquery .= '"'.mysql_escape_string(@implode("\t", @$ThisFileInfo['comments']['track'])).'", '; + $SQLquery .= '"'.mysql_escape_string(@serialize(@$ThisFileInfo['comments'])).'", '; + $SQLquery .= '"'.mysql_escape_string(@serialize(@$ThisFileInfo['tags']['id3v2'])).'", '; + $SQLquery .= '"'.mysql_escape_string(@serialize(@$ThisFileInfo['tags']['ape'])).'", '; + $SQLquery .= '"'.mysql_escape_string(@serialize(@$ThisFileInfo['tags']['lyrics3'])).'", '; + $SQLquery .= '"'.mysql_escape_string(@serialize(@$ThisFileInfo['tags']['id3v1'])).'", '; + $SQLquery .= '"'.mysql_escape_string(@implode("\t", @$ThisFileInfo['warning'])).'", '; + $SQLquery .= '"'.mysql_escape_string(@implode("\t", @$ThisFileInfo['error'])).'", '; + $SQLquery .= '"'.mysql_escape_string(trim(@$ThisFileInfo['audio']['encoder'].' '.@$ThisFileInfo['audio']['encoder_options'])).'", '; + $SQLquery .= '"'.mysql_escape_string(!empty($ThisFileInfo['mpeg']['audio']['LAME']) ? 'LAME' : @$ThisFileInfo['mpeg']['audio']['VBR_method']).'", '; + $SQLquery .= '"'.mysql_escape_string(@$ThisFileInfo['replay_gain']['track']['volume']).'")'; + + } + flush(); + safe_mysql_query($SQLquery); + } + + } + + $SQLquery = 'OPTIMIZE TABLE `files`'; + safe_mysql_query($SQLquery); + + echo '<HR>Done scanning!<HR>'; + +} elseif (!empty($_REQUEST['missingtrackvolume'])) { + + $MissingTrackVolumeFilesScanned = 0; + $MissingTrackVolumeFilesAdjusted = 0; + $MissingTrackVolumeFilesDeleted = 0; + $SQLquery = 'SELECT filename'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (track_volume = "0")'; + $SQLquery .= ' AND (audio_bitrate > "0")'; + $result = safe_mysql_query($SQLquery); + echo 'Scanning <SPAN ID="missingtrackvolumeNowScanning">0</SPAN> / '.number_format(mysql_num_rows($result)).' files for track volume information:<HR>'; + while ($row = mysql_fetch_array($result)) { + set_time_limit(30); + echo '<SCRIPT>missingtrackvolumeNowScanning.innerHTML="'.number_format($MissingTrackVolumeFilesScanned++).'"</SCRIPT>. '; + flush(); + if (file_exists($row['filename'])) { + + $ThisFileInfo = $getID3->analyze($row['filename']); + if (!empty($ThisFileInfo['replay_gain']['track']['volume'])) { + $MissingTrackVolumeFilesAdjusted++; + $SQLquery = 'UPDATE `files`'; + $SQLquery .= ' SET track_volume = "'.$ThisFileInfo['replay_gain']['track']['volume'].'"'; + $SQLquery .= ' WHERE (filename = "'.mysql_escape_string($row['filename']).'")'; + safe_mysql_query($SQLquery); + } + + } else { + + $MissingTrackVolumeFilesDeleted++; + $SQLquery = 'DELETE FROM `files`'; + $SQLquery .= ' WHERE (filename = "'.mysql_escape_string($row['filename']).'")'; + safe_mysql_query($SQLquery); + + } + } + echo '<HR>Scanned '.number_format($MissingTrackVolumeFilesScanned).' files with no track volume information.<BR>'; + echo 'Found track volume information for '.number_format($MissingTrackVolumeFilesAdjusted).' of them (could not find info for '.number_format($MissingTrackVolumeFilesScanned - $MissingTrackVolumeFilesAdjusted).' files; deleted '.number_format($MissingTrackVolumeFilesDeleted).' records of missing files)<HR>'; + +} elseif (!empty($_REQUEST['deadfilescheck'])) { + + $SQLquery = 'SELECT COUNT(*) AS num, filename'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' GROUP BY filename'; + $SQLquery .= ' ORDER BY num DESC'; + $result = safe_mysql_query($SQLquery); + $DupesDeleted = 0; + while ($row = mysql_fetch_array($result)) { + set_time_limit(30); + if ($row['num'] <= 1) { + break; + } + echo FixTextFields($row['filename']).'<BR>'; + $SQLquery = 'DELETE FROM `files` WHERE filename LIKE "'.mysql_escape_string($row['filename']).'"'; + safe_mysql_query($SQLquery); + $DupesDeleted++; + } + if ($DupesDeleted > 0) { + echo '<HR>Deleted <B>'.number_format($DupesDeleted).'</B> duplicate filenames<HR>'; + } + + $SQLquery = 'SELECT filename, filesize, LastModified FROM `files` ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + $totalchecked = 0; + $totalremoved = 0; + while ($row = mysql_fetch_array($result)) { + $totalchecked++; + set_time_limit(30); + if (!file_exists($row['filename']) || ($row['LastModified'] != filemtime($row['filename'])) || (filesize($row['filename']) != $row['filesize'])) { + $totalremoved++; + echo FixTextFields($row['filename']).'<BR>'; + flush(); + $SQLquery = 'DELETE FROM `files` WHERE (filename = "'.mysql_escape_string($row['filename']).'")'; + safe_mysql_query($SQLquery); + } + } + + echo '<HR><B>'.number_format($totalremoved).' of '.number_format($totalchecked).' files in database no longer exist, or have been altered since last scan. Removed from database.</B><HR>'; + +} elseif (!empty($_REQUEST['encodedbydistribution'])) { + + if (!empty($_REQUEST['m3u'])) { + + header('Content-type: audio/x-mpegurl'); + echo '#EXTM3U'."\n"; + + $SQLquery = 'SELECT filename, comments_id3v2 FROM `files` WHERE (encoder_options = "'.mysql_escape_string($_REQUEST['encodedbydistribution']).'")'; + $result = mysql_query($SQLquery); + $NonBlankEncodedBy = ''; + $BlankEncodedBy = ''; + while ($row = mysql_fetch_array($result)) { + set_time_limit(30); + $CommentArray = unserialize($row['comments_id3v2']); + if (isset($CommentArray['encoded_by'][0])) { + $NonBlankEncodedBy .= WindowsShareSlashTranslate($row['filename'])."\n"; + } else { + $BlankEncodedBy .= WindowsShareSlashTranslate($row['filename'])."\n"; + } + } + echo $NonBlankEncodedBy; + echo $BlankEncodedBy; + exit; + + } elseif (!empty($_REQUEST['showfiles'])) { + + echo '<A HREF="'.$_SERVER['PHP_SELF'].'?encodedbydistribution='.urlencode('%').'">show all</A><BR>'; + echo '<TABLE BORDER="1">'; + + $SQLquery = 'SELECT filename, comments_id3v2 FROM `files`'; + $result = mysql_query($SQLquery); + while ($row = mysql_fetch_array($result)) { + set_time_limit(30); + $CommentArray = unserialize($row['comments_id3v2']); + if (($_REQUEST['encodedbydistribution'] == '%') || (!empty($CommentArray['encoded_by'][0]) && ($_REQUEST['encodedbydistribution'] == $CommentArray['encoded_by'][0]))) { + echo '<TR><TD><A HREF="'.$_SERVER['PHP_SELF'].'?m3ufilename='.urlencode($row['filename']).'">m3u</A></TD>'; + echo '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">'.FixTextFields($row['filename']).'</A></TD></TR>'; + } + } + echo '</TABLE>'; + + } else { + + $SQLquery = 'SELECT encoder_options, comments_id3v2 FROM `files` ORDER BY (encoder_options LIKE "LAME%") DESC, (encoder_options LIKE "CBR%") DESC'; + $result = mysql_query($SQLquery); + $EncodedBy = array(); + while ($row = mysql_fetch_array($result)) { + set_time_limit(30); + $CommentArray = unserialize($row['comments_id3v2']); + if (isset($EncodedBy[$row['encoder_options']][@$CommentArray['encoded_by'][0]])) { + $EncodedBy[$row['encoder_options']][@$CommentArray['encoded_by'][0]]++; + } else { + $EncodedBy[$row['encoder_options']][@$CommentArray['encoded_by'][0]] = 1; + } + } + echo '<A HREF="'.$_SERVER['PHP_SELF'].'?encodedbydistribution='.urlencode('%').'&m3u=1">.m3u version</A><BR>'; + echo '<TABLE BORDER="1"><TR><TH>m3u</TH><TH>Encoder Options</TH><TH>Encoded By (ID3v2)</TH></TR>'; + foreach ($EncodedBy as $key => $value) { + echo '<TR><TD VALIGN="TOP"><A HREF="'.$_SERVER['PHP_SELF'].'?encodedbydistribution='.urlencode($key).'&showfiles=1&m3u=1">m3u</A></TD>'; + echo '<TD VALIGN="TOP"><B>'.$key.'</B></TD>'; + echo '<TD><TABLE BORDER="0" WIDTH="100%">'; + arsort($value); + foreach ($value as $string => $count) { + echo '<TR><TD ALIGN="RIGHT" WIDTH="50"><I>'.number_format($count).'</I></TD><TD> </TD>'; + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?encodedbydistribution='.urlencode($string).'&showfiles=1">'.$string.'</A></TD></TR>'; + } + echo '</TABLE></TD></TR>'; + } + echo '</TABLE>'; + + } + +} elseif (!empty($_REQUEST['audiobitrates'])) { + + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); + $BitrateDistribution = array(); + $SQLquery = 'SELECT ROUND(audio_bitrate / 1000) AS RoundBitrate, COUNT(*) AS num'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (audio_bitrate > 0)'; + $SQLquery .= ' GROUP BY RoundBitrate'; + $result = safe_mysql_query($SQLquery); + while ($row = mysql_fetch_array($result)) { + @$BitrateDistribution[getid3_mp3::ClosestStandardMP3Bitrate($row['RoundBitrate'] * 1000)] += $row['num']; // safe_inc + } + + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="3">'; + echo '<TR><TH>Bitrate</TH><TH>Count</TH></TR>'; + foreach ($BitrateDistribution as $Bitrate => $Count) { + echo '<TR>'; + echo '<TD ALIGN="RIGHT">'.round($Bitrate / 1000).' kbps</TD>'; + echo '<TD ALIGN="RIGHT">'.number_format($Count).'</TD>'; + echo '</TR>'; + } + echo '</TABLE>'; + + +} elseif (!empty($_REQUEST['emptygenres'])) { + + $SQLquery = 'SELECT fileformat, filename, genre FROM `files` WHERE (genre = "") OR (genre = "Unknown") OR (genre = "Other") ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + + if (!empty($_REQUEST['m3u'])) { + + header('Content-type: audio/x-mpegurl'); + echo '#EXTM3U'."\n"; + while ($row = mysql_fetch_array($result)) { + if (!in_array($row['fileformat'], $IgnoreNoTagFormats)) { + echo WindowsShareSlashTranslate($row['filename'])."\n"; + } + } + exit; + + } else { + + echo '<A HREF="'.$_SERVER['PHP_SELF'].'?emptygenres='.urlencode($_REQUEST['emptygenres']).'&m3u=1">.m3u version</A><BR>'; + $EmptyGenreCounter = 0; + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="3">'; + echo '<TR><TH>m3u</TH><TH>filename</TH></TR>'; + while ($row = mysql_fetch_array($result)) { + if (!in_array($row['fileformat'], $IgnoreNoTagFormats)) { + $EmptyGenreCounter++; + echo '<TR>'; + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?m3ufilename='.urlencode($row['filename']).'">m3u</A></TD>'; + echo '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">'.FixTextFields($row['filename']).'</A></TD>'; + echo '</TR>'; + } + } + echo '</TABLE>'; + echo '<B>'.number_format($EmptyGenreCounter).'</B> files with empty genres'; + + } + +} elseif (!empty($_REQUEST['nonemptycomments'])) { + + $SQLquery = 'SELECT filename, comment FROM `files` WHERE (comment <> "") ORDER BY comment ASC'; + $result = safe_mysql_query($SQLquery); + + if (!empty($_REQUEST['m3u'])) { + + header('Content-type: audio/x-mpegurl'); + echo '#EXTM3U'."\n"; + while ($row = mysql_fetch_array($result)) { + echo WindowsShareSlashTranslate($row['filename'])."\n"; + } + exit; + + } else { + + $NonEmptyCommentsCounter = 0; + echo '<A HREF="'.$_SERVER['PHP_SELF'].'?nonemptycomments='.urlencode($_REQUEST['nonemptycomments']).'&m3u=1">.m3u version</A><BR>'; + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="3">'; + echo '<TR><TH>m3u</TH><TH>filename</TH><TH>comments</TH></TR>'; + while ($row = mysql_fetch_array($result)) { + $NonEmptyCommentsCounter++; + echo '<TR>'; + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?m3ufilename='.urlencode($row['filename']).'">m3u</A></TD>'; + echo '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">'.FixTextFields($row['filename']).'</A></TD>'; + if (strlen(trim($row['comment'])) > 0) { + echo '<TD>'.FixTextFields($row['comment']).'</TD>'; + } else { + echo '<TD><I>space</I></TD>'; + } + echo '</TR>'; + } + echo '</TABLE>'; + echo '<B>'.number_format($NonEmptyCommentsCounter).'</B> files with non-empty comments'; + + } + +} elseif (!empty($_REQUEST['trackzero'])) { + + $SQLquery = 'SELECT filename, track FROM `files` WHERE (track <> "") AND ((track < "1") OR (track > "99")) ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + + if (!empty($_REQUEST['m3u'])) { + + header('Content-type: audio/x-mpegurl'); + echo '#EXTM3U'."\n"; + while ($row = mysql_fetch_array($result)) { + if ((strlen($row['track']) > 0) && ($row['track'] < 1) || ($row['track'] > 99)) { + echo WindowsShareSlashTranslate($row['filename'])."\n"; + } + } + exit; + + } else { + + echo '<A HREF="'.$_SERVER['PHP_SELF'].'?trackzero='.urlencode($_REQUEST['trackzero']).'&m3u=1">.m3u version</A><BR>'; + $TrackZeroCounter = 0; + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="3">'; + echo '<TR><TH>m3u</TH><TH>filename</TH><TH>track</TH></TR>'; + while ($row = mysql_fetch_array($result)) { + if ((strlen($row['track']) > 0) && ($row['track'] < 1) || ($row['track'] > 99)) { + $TrackZeroCounter++; + echo '<TR>'; + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?m3ufilename='.urlencode($row['filename']).'">m3u</A></TD>'; + echo '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">'.FixTextFields($row['filename']).'</A></TD>'; + echo '<TD>'.FixTextFields($row['track']).'</TD>'; + echo '</TR>'; + } + } + echo '</TABLE>'; + echo '<B>'.number_format($TrackZeroCounter).'</B> files with track "zero"'; + + } + + +} elseif (!empty($_REQUEST['synchronizetagsfrom']) && !empty($_REQUEST['filename'])) { + + echo 'Applying new tags from <B>'.$_REQUEST['synchronizetagsfrom'].'</B> in <B>'.FixTextFields($_REQUEST['filename']).'</B><UL>'; + $errors = array(); + if (SynchronizeAllTags($_REQUEST['filename'], $_REQUEST['synchronizetagsfrom'], 'A12', $errors)) { + echo '<LI>Sucessfully wrote tags</LI>'; + } else { + echo '<LI>Tag writing had errors: <UL><LI>'.implode('</LI><LI>', $errors).'</LI></UL></LI>'; + } + echo '</UL>'; + + +} elseif (!empty($_REQUEST['unsynchronizedtags'])) { + + $NotOKfiles = 0; + $FieldsToCompare = array('title', 'artist', 'album', 'year', 'genre', 'comment', 'track'); + $TagsToCompare = array('id3v2'=>false, 'ape'=>false, 'lyrics3'=>false, 'id3v1'=>false); + $ID3v1FieldLengths = array('title'=>30, 'artist'=>30, 'album'=>30, 'year'=>4, 'genre'=>99, 'comment'=>28); + if (strpos($_REQUEST['unsynchronizedtags'], '2') !== false) { + $TagsToCompare['id3v2'] = true; + } + if (strpos($_REQUEST['unsynchronizedtags'], 'A') !== false) { + $TagsToCompare['ape'] = true; + } + if (strpos($_REQUEST['unsynchronizedtags'], 'L') !== false) { + $TagsToCompare['lyrics3'] = true; + } + if (strpos($_REQUEST['unsynchronizedtags'], '1') !== false) { + $TagsToCompare['id3v1'] = true; + } + + echo '<A HREF="'.$_SERVER['PHP_SELF'].'?unsynchronizedtags='.urlencode($_REQUEST['unsynchronizedtags']).'&autofix=1">Auto-fix empty tags</A><BR><BR>'; + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="3">'; + echo '<TR>'; + echo '<TH>View</TH>'; + echo '<TH>Filename</TH>'; + echo '<TH>Combined</TH>'; + if ($TagsToCompare['id3v2']) { + echo '<TH>ID3v2</TH>'; + } + if ($TagsToCompare['ape']) { + echo '<TH>APE</TH>'; + } + if ($TagsToCompare['lyrics3']) { + echo '<TH>Lyrics3</TH>'; + } + if ($TagsToCompare['id3v1']) { + echo '<TH>ID3v1</TH>'; + } + echo '</TR>'; + + $SQLquery = 'SELECT filename, comments_all, comments_id3v2, comments_ape, comments_lyrics3, comments_id3v1'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (fileformat = "mp3")'; + $SQLquery .= ' ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + while ($row = mysql_fetch_array($result)) { + + set_time_limit(30); + + $FileOK = true; + $Mismatched = array('id3v2'=>false, 'ape'=>false, 'lyrics3'=>false, 'id3v1'=>false); + $SemiMatched = array('id3v2'=>false, 'ape'=>false, 'lyrics3'=>false, 'id3v1'=>false); + $EmptyTags = array('id3v2'=>true, 'ape'=>true, 'lyrics3'=>true, 'id3v1'=>true); + + $Comments['all'] = @unserialize($row['comments_all']); + $Comments['id3v2'] = @unserialize($row['comments_id3v2']); + $Comments['ape'] = @unserialize($row['comments_ape']); + $Comments['lyrics3'] = @unserialize($row['comments_lyrics3']); + $Comments['id3v1'] = @unserialize($row['comments_id3v1']); + + if (isset($Comments['ape']['tracknumber'])) { + $Comments['ape']['track'] = $Comments['ape']['tracknumber']; + unset($Comments['ape']['tracknumber']); + } + + + $FileOK = true; + + $ThisLine = '<TR>'; + $ThisLine .= '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">view</A></TD>'; + $ThisLine .= '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?m3ufilename='.urlencode($row['filename']).'">'.FixTextFields($row['filename']).'</A></TD>'; + $tagvalues = ''; + foreach ($FieldsToCompare as $fieldname) { + $tagvalues .= $fieldname.' = '.@implode("\n", @$Comments['all'][$fieldname])."\n"; + } + $ThisLine .= '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?synchronizetagsfrom=all&filename='.urlencode($row['filename']).'" TITLE="'.htmlentities(rtrim($tagvalues, "\n"), ENT_QUOTES).'" TARGET="retagwindow">all</A></TD>'; + foreach ($TagsToCompare as $tagtype => $CompareThisTagType) { + if ($CompareThisTagType) { + $tagvalues = ''; + $tagempty = true; + foreach ($FieldsToCompare as $fieldname) { + + if ($tagtype == 'id3v1') { + + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true); + if (($fieldname == 'genre') && !getid3_id3v1::LookupGenreID(@$Comments['all'][$fieldname][0])) { + // non-standard genres can never match, so just ignore + $tagvalues .= $fieldname.' = '.@$Comments[$tagtype][$fieldname][0]."\n"; + } elseif ($fieldname == 'comment') { + if (rtrim(substr(@$Comments[$tagtype][$fieldname][0], 0, 28)) != rtrim(substr(@$Comments['all'][$fieldname][0], 0, 28))) { + $tagvalues .= $fieldname.' = [['.@$Comments[$tagtype][$fieldname][0].']]'."\n"; + if (trim(strtolower(RemoveAccents(substr(@$Comments[$tagtype][$fieldname][0], 0, 28)))) == trim(strtolower(RemoveAccents(substr(@$Comments['all'][$fieldname][0], 0, 28))))) { + $SemiMatched[$tagtype] = true; + } else { + $Mismatched[$tagtype] = true; + } + $FileOK = false; + } else { + $tagvalues .= $fieldname.' = '.@$Comments[$tagtype][$fieldname][0]."\n"; + } + } elseif (rtrim(substr(@$Comments[$tagtype][$fieldname][0], 0, 30)) != rtrim(substr(@$Comments['all'][$fieldname][0], 0, 30))) { + $tagvalues .= $fieldname.' = [['.@$Comments[$tagtype][$fieldname][0].']]'."\n"; + if (strtolower(RemoveAccents(trim(substr(@$Comments[$tagtype][$fieldname][0], 0, 30)))) == strtolower(RemoveAccents(trim(substr(@$Comments['all'][$fieldname][0], 0, 30))))) { + $SemiMatched[$tagtype] = true; + } else { + $Mismatched[$tagtype] = true; + } + $FileOK = false; + if (strlen(trim(@$Comments[$tagtype][$fieldname][0])) > 0) { + $EmptyTags[$tagtype] = false; + } + } else { + $tagvalues .= $fieldname.' = '.@$Comments[$tagtype][$fieldname][0]."\n"; + if (strlen(trim(@$Comments[$tagtype][$fieldname][0])) > 0) { + $EmptyTags[$tagtype] = false; + } + } + + } elseif (($tagtype == 'ape') && ($fieldname == 'year')) { + + if ((@$Comments['ape']['date'][0] != @$Comments['all']['year'][0]) && (@$Comments['ape']['year'][0] != @$Comments['all']['year'][0])) { + $tagvalues .= $fieldname.' = [['.@$Comments['ape']['date'][0].']]'."\n"; + $Mismatched[$tagtype] = true; + $FileOK = false; + if (strlen(trim(@$Comments['ape']['date'][0])) > 0) { + $EmptyTags[$tagtype] = false; + } + } else { + $tagvalues .= $fieldname.' = '.@$Comments[$tagtype][$fieldname][0]."\n"; + if (strlen(trim(@$Comments[$tagtype][$fieldname][0])) > 0) { + $EmptyTags[$tagtype] = false; + } + } + + } elseif (($fieldname == 'genre') && in_array($Comments[$tagtype][$fieldname][0], $Comments['all'][$fieldname])) { + + $tagvalues .= $fieldname.' = '.@$Comments[$tagtype][$fieldname][0]."\n"; + if (strlen(trim(@$Comments[$tagtype][$fieldname][0])) > 0) { + $EmptyTags[$tagtype] = false; + } + + } elseif (@$Comments[$tagtype][$fieldname][0] != @$Comments['all'][$fieldname][0]) { + + $tagvalues .= $fieldname.' = [['.@$Comments[$tagtype][$fieldname][0].']]'."\n"; + if (trim(strtolower(RemoveAccents(@$Comments[$tagtype][$fieldname][0]))) == trim(strtolower(RemoveAccents(@$Comments['all'][$fieldname][0])))) { + $SemiMatched[$tagtype] = true; + } else { + $Mismatched[$tagtype] = true; + } + $FileOK = false; + if (strlen(trim(@$Comments[$tagtype][$fieldname][0])) > 0) { + $EmptyTags[$tagtype] = false; + } + + } else { + + $tagvalues .= $fieldname.' = '.@$Comments[$tagtype][$fieldname][0]."\n"; + if (strlen(trim(@$Comments[$tagtype][$fieldname][0])) > 0) { + $EmptyTags[$tagtype] = false; + } + + } + } + + if ($EmptyTags[$tagtype]) { + $ThisLine .= '<TD BGCOLOR="#0099CC">'; + } elseif ($SemiMatched[$tagtype]) { + $ThisLine .= '<TD BGCOLOR="#FF9999">'; + } elseif ($Mismatched[$tagtype]) { + $ThisLine .= '<TD BGCOLOR="#FF0000">'; + } else { + $ThisLine .= '<TD BGCOLOR="#00CC00">'; + } + $ThisLine .= '<A HREF="'.$_SERVER['PHP_SELF'].'?synchronizetagsfrom='.$tagtype.'&filename='.urlencode($row['filename']).'" TITLE="'.htmlentities(rtrim($tagvalues, "\n"), ENT_QUOTES).'" TARGET="retagwindow">'.$tagtype.'</A>'; + $ThisLine .= '</TD>'; + } + } + $ThisLine .= '</TR>'; + + if (!$FileOK) { + $NotOKfiles++; + + if (!empty($_REQUEST['autofix'])) { + + $AnyMismatched = false; + foreach ($Mismatched as $key => $value) { + if ($value && ($EmptyTags["$key"] === false)) { + $AnyMismatched = true; + } + } + if ($AnyMismatched) { + + echo $ThisLine; + + } else { + + $TagsToSynch = ''; + foreach ($EmptyTags as $key => $value) { + if ($value) { + switch ($key) { + case 'id3v1': + $TagsToSynch .= '1'; + break; + case 'id3v2': + $TagsToSynch .= '2'; + break; + case 'ape': + $TagsToSynch .= 'A'; + break; + } + } + } + $errors = array(); + if (SynchronizeAllTags($row['filename'], 'all', $TagsToSynch, $errors)) { + echo '<TR BGCOLOR="#00CC00">'; + } else { + echo '<TR BGCOLOR="#FF0000">'; + } + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?m3ufilename='.urlencode($row['filename']).'" TITLE="'.FixTextFields(implode("\n", $errors)).'">'.FixTextFields($row['filename']).'</A></TD>'; + echo '<TD><TABLE BORDER="0">'; + echo '<TR><TD><B>'.$TagsToSynch.'</B></TD></TR>'; + echo '</TABLE></TD></TR>'; + } + + } else { + + echo $ThisLine; + + } + } + } + + echo '</TABLE><BR>'; + echo 'Found <B>'.number_format($NotOKfiles).'</B> files with unsynchronzed tags'; + +} elseif (!empty($_REQUEST['filenamepattern'])) { + + $patterns['A'] = 'artist'; + $patterns['T'] = 'title'; + $patterns['M'] = 'album'; + $patterns['N'] = 'track'; + $patterns['G'] = 'genre'; + + $FieldsToUse = explode(' ', wordwrap(eregi_replace('[^A-Z]', '', $_REQUEST['filenamepattern']), 1, ' ', 1)); + foreach ($FieldsToUse as $FieldID) { + $FieldNames[] = $patterns["$FieldID"]; + } + + $SQLquery = 'SELECT filename, fileformat, '.implode(', ', $FieldNames); + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (fileformat NOT LIKE "'.implode('") AND (fileformat NOT LIKE "', $IgnoreNoTagFormats).'")'; + $SQLquery .= ' ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + echo 'Files that do not match naming pattern:<BR>'; + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="3">'; + echo '<TR><TH>view</TH><TH>Why</TH><TD><B>Actual filename</B><BR>(click to play/edit file)</TD><TD><B>Correct filename (based on tags)</B><BR>(click to rename file to this)</TD></TR>'; + $nonmatchingfilenames = 0; + $Pattern = $_REQUEST['filenamepattern']; + $PatternLength = strlen($Pattern); + while ($row = mysql_fetch_array($result)) { + set_time_limit(10); + $PatternFilename = ''; + for ($i = 0; $i < $PatternLength; $i++) { + if (isset($patterns[$Pattern{$i}])) { + $PatternFilename .= trim(strtr($row[$patterns[$Pattern{$i}]], ':\\/*<>|', ';-~-[] '), ' '); + } else { + $PatternFilename .= $Pattern{$i}; + } + } + + // Replace "~" with "-" if characters immediately before and after are both numbers + // "/" has been replaced with "~" above which is good for multi-song medley dividers, + // but for things like 24/7, 7/8, etc it looks better if it's 24-7, 7-8, etc. + $tildepos = 0; + while ($tildepos = strpos($PatternFilename, '~', $tildepos)) { + if (ereg('[0-9]~[0-9]', substr($PatternFilename, $tildepos - 1, 3))) { + $PatternFilename{$tildepos} = '-'; + } else { + $tildepos++; + } + } + + // get rid of leading & trailing spaces if end items (artist or title for example) are missing + $PatternFilename = str_replace(' "', ' “', $PatternFilename); + $PatternFilename = str_replace('("', '(“', $PatternFilename); + $PatternFilename = str_replace('-"', '-“', $PatternFilename); + $PatternFilename = str_replace('" ', '” ', $PatternFilename.' '); + $PatternFilename = str_replace('"', '”', $PatternFilename); + $PatternFilename = str_replace('?', '', $PatternFilename); + $PatternFilename = str_replace(' ', ' ', $PatternFilename); + $PatternFilename = trim($PatternFilename, ' -'); + $PatternFilename .= '.'.$row['fileformat']; + $ActualFilename = basename($row['filename']); + if ($ActualFilename != $PatternFilename) { + + $NotMatchedReasons = ''; + if (strtolower($ActualFilename) === strtolower($PatternFilename)) { + $NotMatchedReasons .= 'Aa '; + } elseif (RemoveAccents($ActualFilename) === RemoveAccents($PatternFilename)) { + $NotMatchedReasons .= 'ée '; + } + $ShortestName = min(strlen($ActualFilename), strlen($PatternFilename)); + for ($DifferenceOffset = 0; $DifferenceOffset < $ShortestName; $DifferenceOffset++) { + if ($ActualFilename{$DifferenceOffset} !== $PatternFilename{$DifferenceOffset}) { + break; + } + } + echo '<TR>'; + echo '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">view</A></TD>'; + echo '<TD> '.$NotMatchedReasons.'</TD>'; + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?m3ufilename='.urlencode($row['filename']).'">'.FixTextFields($ActualFilename).'</A></TD>'; + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?filenamepattern='.urlencode($_REQUEST['filenamepattern']).'&renamefilefrom='.urlencode($row['filename']).'&renamefileto='.urlencode(dirname($row['filename']).'/'.$PatternFilename).'" TITLE="'.FixTextFields(basename($row['filename']))."\n".FixTextFields(basename($PatternFilename)).'" TARGET="renamewindow">'.substr($PatternFilename, 0, $DifferenceOffset).'<B>'.substr($PatternFilename, $DifferenceOffset).'</B></A></TD>'; + echo '</TR>'; + + $nonmatchingfilenames++; + } + } + echo '</TABLE><BR>'; + echo 'Found '.number_format($nonmatchingfilenames).' files that do not match naming pattern<BR>'; + + +} elseif (!empty($_REQUEST['encoderoptionsdistribution'])) { + + if (isset($_REQUEST['showtagfiles'])) { + $SQLquery = 'SELECT filename, encoder_options FROM `files`'; + $SQLquery .= ' WHERE (encoder_options LIKE "'.mysql_escape_string($_REQUEST['showtagfiles']).'")'; + $SQLquery .= ' AND (fileformat NOT LIKE "'.implode('") AND (fileformat NOT LIKE "', $IgnoreNoTagFormats).'")'; + $SQLquery .= ' ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + + if (!empty($_REQUEST['m3u'])) { + + header('Content-type: audio/x-mpegurl'); + echo '#EXTM3U'."\n"; + while ($row = mysql_fetch_array($result)) { + echo WindowsShareSlashTranslate($row['filename'])."\n"; + } + exit; + + } else { + + echo '<A HREF="'.$_SERVER['PHP_SELF'].'?encoderoptionsdistribution=1">Show all Encoder Options</A><HR>'; + echo 'Files with Encoder Options <B>'.$_REQUEST['showtagfiles'].'</B>:<BR>'; + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="3">'; + while ($row = mysql_fetch_array($result)) { + echo '<TR>'; + echo '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">'.FixTextFields($row['filename']).'</A></TD>'; + echo '<TD>'.$row['encoder_options'].'</TD>'; + echo '</TR>'; + } + echo '</TABLE>'; + + } + + } elseif (!isset($_REQUEST['m3u'])) { + + $SQLquery = 'SELECT encoder_options, COUNT(*) AS num FROM `files`'; + $SQLquery .= ' WHERE (fileformat NOT LIKE "'.implode('") AND (fileformat NOT LIKE "', $IgnoreNoTagFormats).'")'; + $SQLquery .= ' GROUP BY encoder_options'; + $SQLquery .= ' ORDER BY (encoder_options LIKE "LAME%") DESC, (encoder_options LIKE "CBR%") DESC, num DESC, encoder_options ASC'; + $result = safe_mysql_query($SQLquery); + echo 'Files with Encoder Options:<BR>'; + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="3">'; + echo '<TR><TH>Encoder Options</TH><TH>Count</TH><TH>M3U</TH></TR>'; + while ($row = mysql_fetch_array($result)) { + echo '<TR>'; + echo '<TD>'.$row['encoder_options'].'</TD>'; + echo '<TD ALIGN="RIGHT"><A HREF="'.$_SERVER['PHP_SELF'].'?encoderoptionsdistribution=1&showtagfiles='.($row['encoder_options'] ? urlencode($row['encoder_options']) : '').'">'.number_format($row['num']).'</A></TD>'; + echo '<TD ALIGN="RIGHT"><A HREF="'.$_SERVER['PHP_SELF'].'?encoderoptionsdistribution=1&showtagfiles='.($row['encoder_options'] ? urlencode($row['encoder_options']) : '').'&m3u=.m3u">m3u</A></TD>'; + echo '</TR>'; + } + echo '</TABLE><HR>'; + + } + +} elseif (!empty($_REQUEST['tagtypes'])) { + + if (!isset($_REQUEST['m3u'])) { + $SQLquery = 'SELECT tags, COUNT(*) AS num FROM `files`'; + $SQLquery .= ' WHERE (fileformat NOT LIKE "'.implode('") AND (fileformat NOT LIKE "', $IgnoreNoTagFormats).'")'; + $SQLquery .= ' GROUP BY tags'; + $SQLquery .= ' ORDER BY num DESC'; + $result = safe_mysql_query($SQLquery); + echo 'Files with tags:<BR>'; + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="3">'; + echo '<TR><TH>Tags</TH><TH>Count</TH><TH>M3U</TH></TR>'; + while ($row = mysql_fetch_array($result)) { + echo '<TR>'; + echo '<TD>'.$row['tags'].'</TD>'; + echo '<TD ALIGN="RIGHT"><A HREF="'.$_SERVER['PHP_SELF'].'?tagtypes=1&showtagfiles='.($row['tags'] ? urlencode($row['tags']) : '').'">'.number_format($row['num']).'</A></TD>'; + echo '<TD ALIGN="RIGHT"><A HREF="'.$_SERVER['PHP_SELF'].'?tagtypes=1&showtagfiles='.($row['tags'] ? urlencode($row['tags']) : '').'&m3u=.m3u">m3u</A></TD>'; + echo '</TR>'; + } + echo '</TABLE><HR>'; + } + + if (isset($_REQUEST['showtagfiles'])) { + $SQLquery = 'SELECT filename, tags FROM `files`'; + $SQLquery .= ' WHERE (tags LIKE "'.mysql_escape_string($_REQUEST['showtagfiles']).'")'; + $SQLquery .= ' AND (fileformat NOT LIKE "'.implode('") AND (fileformat NOT LIKE "', $IgnoreNoTagFormats).'")'; + $SQLquery .= ' ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + + if (!empty($_REQUEST['m3u'])) { + + header('Content-type: audio/x-mpegurl'); + echo '#EXTM3U'."\n"; + while ($row = mysql_fetch_array($result)) { + echo WindowsShareSlashTranslate($row['filename'])."\n"; + } + exit; + + } else { + + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="3">'; + while ($row = mysql_fetch_array($result)) { + echo '<TR>'; + echo '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">'.FixTextFields($row['filename']).'</A></TD>'; + echo '<TD>'.$row['tags'].'</TD>'; + echo '</TR>'; + } + echo '</TABLE>'; + + } + } + + +} elseif (!empty($_REQUEST['md5datadupes'])) { + + $OtherFormats = ''; + $AVFormats = ''; + + $SQLquery = 'SELECT md5_data, filename, COUNT(*) AS num'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (md5_data <> "")'; + $SQLquery .= ' GROUP BY md5_data'; + $SQLquery .= ' ORDER BY num DESC'; + $result = safe_mysql_query($SQLquery); + while (($row = mysql_fetch_array($result)) && ($row['num'] > 1)) { + set_time_limit(30); + + $filenames = array(); + $tags = array(); + $md5_data = array(); + $SQLquery = 'SELECT fileformat, filename, tags FROM `files`'; + $SQLquery .= ' WHERE (md5_data = "'.mysql_escape_string($row['md5_data']).'")'; + $SQLquery .= ' ORDER BY filename ASC'; + $result2 = safe_mysql_query($SQLquery); + while ($row2 = mysql_fetch_array($result2)) { + $thisfileformat = $row2['fileformat']; + $filenames[] = $row2['filename']; + $tags[] = $row2['tags']; + $md5_data[] = $row['md5_data']; + } + + $thisline = '<TR>'; + $thisline .= '<TD VALIGN="TOP" STYLE="font-family: monospace;">'.implode('<BR>', $md5_data).'</TD>'; + $thisline .= '<TD VALIGN="TOP" NOWRAP>'.implode('<BR>', $tags).'</TD>'; + $thisline .= '<TD VALIGN="TOP">'.implode('<BR>', $filenames).'</TD>'; + $thisline .= '</TR>'; + + if (in_array($thisfileformat, $IgnoreNoTagFormats)) { + $OtherFormats .= $thisline; + } else { + $AVFormats .= $thisline; + } + } + echo 'Duplicated MD5_DATA (Audio/Video files):<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="2">'; + echo $AVFormats.'</TABLE><HR>'; + echo 'Duplicated MD5_DATA (Other files):<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="2">'; + echo $OtherFormats.'</TABLE><HR>'; + + +} elseif (!empty($_REQUEST['artisttitledupes'])) { + + if (isset($_REQUEST['m3uartist']) && isset($_REQUEST['m3utitle'])) { + + header('Content-type: audio/x-mpegurl'); + echo '#EXTM3U'."\n"; + $SQLquery = 'SELECT filename FROM `files`'; + $SQLquery .= ' WHERE (artist = "'.mysql_escape_string($_REQUEST['m3uartist']).'")'; + $SQLquery .= ' AND (title = "'.mysql_escape_string($_REQUEST['m3utitle']).'")'; + $SQLquery .= ' ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + while ($row = mysql_fetch_array($result)) { + echo WindowsShareSlashTranslate($row['filename'])."\n"; + } + exit; + + } + + $SQLquery = 'SELECT artist, title, filename, COUNT(*) AS num FROM `files`'; + $SQLquery .= ' WHERE (artist <> "")'; + $SQLquery .= ' AND (title <> "")'; + $SQLquery .= ' GROUP BY artist, title'; + $SQLquery .= ' ORDER BY num DESC, artist ASC, title ASC'; + $result = safe_mysql_query($SQLquery); + $uniquetitles = 0; + $uniquefiles = 0; + + if (!empty($_REQUEST['m3u'])) { + + header('Content-type: audio/x-mpegurl'); + echo '#EXTM3U'."\n"; + while (($row = mysql_fetch_array($result)) && ($row['num'] > 1)) { + $SQLquery = 'SELECT filename FROM `files`'; + $SQLquery .= ' WHERE (artist = "'.mysql_escape_string($row['artist']).'")'; + $SQLquery .= ' AND (title = "'.mysql_escape_string($row['title']).'")'; + $SQLquery .= ' ORDER BY filename ASC'; + $result2 = safe_mysql_query($SQLquery); + while ($row2 = mysql_fetch_array($result2)) { + echo WindowsShareSlashTranslate($row2['filename'])."\n"; + } + } + exit; + + } else { + + echo 'Duplicated aritst + title:<BR>'; + echo '(<A HREF="'.$_SERVER['PHP_SELF'].'?artisttitledupes=1&m3u=.m3u">.m3u version</A>)<BR>'; + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="2">'; + + while (($row = mysql_fetch_array($result)) && ($row['num'] > 1)) { + $uniquetitles++; + set_time_limit(30); + + $filenames = array(); + $artists = array(); + $titles = array(); + $bitrates = array(); + $playtimes = array(); + $SQLquery = 'SELECT filename, artist, title, audio_bitrate, vbr_method, playtime_seconds, encoder_options FROM `files`'; + $SQLquery .= ' WHERE (artist = "'.mysql_escape_string($row['artist']).'")'; + $SQLquery .= ' AND (title = "'.mysql_escape_string($row['title']).'")'; + $SQLquery .= ' ORDER BY filename ASC'; + $result2 = safe_mysql_query($SQLquery); + while ($row2 = mysql_fetch_array($result2)) { + $uniquefiles++; + $filenames[] = $row2['filename']; + $artists[] = $row2['artist']; + $titles[] = $row2['title']; + if ($row2['vbr_method']) { + $bitrates[] = '<B'.($row2['encoder_options'] ? ' STYLE="text-decoration: underline; cursor: help;" TITLE="'.$row2['encoder_options'] : '').'">'.BitrateText($row2['audio_bitrate'] / 1000).'</B>'; + } else { + $bitrates[] = BitrateText($row2['audio_bitrate'] / 1000); + } + $playtimes[] = getid3_lib::PlaytimeString($row2['playtime_seconds']); + } + + echo '<TR>'; + echo '<TD NOWRAP VALIGN="TOP">'; + foreach ($filenames as $file) { + echo '<A HREF="demo.browse.php?deletefile='.urlencode($file).'&noalert=1" onClick="return confirm(\'Are you sure you want to delete '.addslashes($file).'? \n(this action cannot be un-done)\');" TITLE="Permanently delete '."\n".FixTextFields($file)."\n".'" TARGET="deletedupewindow">delete</A><BR>'; + } + echo '</TD>'; + echo '<TD NOWRAP VALIGN="TOP">'; + foreach ($filenames as $file) { + echo '<A HREF="'.$_SERVER['PHP_SELF'].'?m3ufilename='.urlencode($file).'">play</A><BR>'; + } + echo '</TD>'; + echo '<TD VALIGN="MIDDLE" ALIGN="CENTER" ><A HREF="'.$_SERVER['PHP_SELF'].'?artisttitledupes=1&m3uartist='.urlencode($artists[0]).'&m3utitle='.urlencode($titles[0]).'">play all</A></TD>'; + echo '<TD VALIGN="TOP" NOWRAP>'.implode('<BR>', $artists).'</TD>'; + echo '<TD VALIGN="TOP" NOWRAP>'.implode('<BR>', $titles).'</TD>'; + echo '<TD VALIGN="TOP" NOWRAP ALIGN="RIGHT">'.implode('<BR>', $bitrates).'</TD>'; + echo '<TD VALIGN="TOP" NOWRAP ALIGN="RIGHT">'.implode('<BR>', $playtimes).'</TD>'; + + echo '<TD VALIGN="TOP" NOWRAP ALIGN="LEFT"><TABLE BORDER="0" CELLSPACING="0" CELLPADDING="0">'; + foreach ($filenames as $file) { + echo '<TR><TD NOWRAP ALIGN="RIGHT"><A HREF="demo.browse.php?filename='.rawurlencode($file).'"><SPAN STYLE="color: #339966;">'.dirname($file).'/</SPAN>'.basename($file).'</A></TD></TR>'; + } + echo '</TABLE></TD>'; + + echo '</TR>'; + } + + } + echo '</TABLE>'; + echo number_format($uniquefiles).' files with '.number_format($uniquetitles).' unique <I>aritst + title</I><BR>'; + echo '<HR>'; + +} elseif (!empty($_REQUEST['filetypelist'])) { + + list($fileformat, $audioformat) = explode('|', $_REQUEST['filetypelist']); + $SQLquery = 'SELECT filename, fileformat, audio_dataformat'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (fileformat = "'.mysql_escape_string($fileformat).'")'; + $SQLquery .= ' AND (audio_dataformat = "'.mysql_escape_string($audioformat).'")'; + $SQLquery .= ' ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + echo 'Files of format <B>'.$fileformat.'.'.$audioformat.'</B>:<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="4">'; + echo '<TR><TH>file</TH><TH>audio</TH><TH>filename</TH></TR>'; + while ($row = mysql_fetch_array($result)) { + echo '<TR>'; + echo '<TD>'.$row['fileformat'].'</TD>'; + echo '<TD>'.$row['audio_dataformat'].'</TD>'; + echo '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">'.FixTextFields($row['filename']).'</A></TD>'; + echo '</TR>'; + } + echo '</TABLE><HR>'; + +} elseif (!empty($_REQUEST['trackinalbum'])) { + + $SQLquery = 'SELECT filename, album'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (album LIKE "% [%")'; + $SQLquery .= ' ORDER BY album ASC, filename ASC'; + $result = safe_mysql_query($SQLquery); + if (!empty($_REQUEST['m3u'])) { + + header('Content-type: audio/x-mpegurl'); + echo '#EXTM3U'."\n"; + while ($row = mysql_fetch_array($result)) { + echo WindowsShareSlashTranslate($row['filename'])."\n"; + } + exit; + + } elseif (!empty($_REQUEST['autofix'])) { + + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true); + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); + + while ($row = mysql_fetch_array($result)) { + set_time_limit(30); + $ThisFileInfo = $getID3->analyze($filename); + getid3_lib::CopyTagsToComments($ThisFileInfo); + + if (!empty($ThisFileInfo['tags'])) { + + $Album = trim(str_replace(strstr($ThisFileInfo['comments']['album'][0], ' ['), '', $ThisFileInfo['comments']['album'][0])); + $Track = (string) intval(str_replace(' [', '', str_replace(']', '', strstr($ThisFileInfo['comments']['album'][0], ' [')))); + if ($Track == '0') { + $Track = ''; + } + if ($Album && $Track) { + echo '<HR>'.FixTextFields($row['filename']).'<BR>'; + echo '<I>'.$Album.'</I> (track #'.$Track.')<BR>'; + echo '<B>ID3v2:</B> '.(RemoveID3v2($row['filename'], false) ? 'removed' : 'REMOVAL FAILED!').', '; + echo '<B>ID3v1:</B> '.(WriteID3v1($row['filename'], @$ThisFileInfo['comments']['title'][0], @$ThisFileInfo['comments']['artist'][0], $Album, @$ThisFileInfo['comments']['year'][0], @$ThisFileInfo['comments']['comment'][0], @$ThisFileInfo['comments']['genreid'][0], $Track, false) ? 'updated' : 'UPDATE FAILED').'<BR>'; + } else { + echo ' . '; + } + + } else { + + echo '<HR>FAILED<BR>'.FixTextFields($row['filename']).'<HR>'; + + } + flush(); + } + + } else { + + echo '<B>'.number_format(mysql_num_rows($result)).'</B> files with <B>[??]</B>-format track numbers in album field:<BR>'; + if (mysql_num_rows($result) > 0) { + echo '(<A HREF="'.$_SERVER['PHP_SELF'].'?trackinalbum=1&m3u=.m3u">.m3u version</A>)<BR>'; + echo '<A HREF="'.$_SERVER['PHP_SELF'].'?trackinalbum=1&autofix=1">Try to auto-fix</A><BR>'; + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="4">'; + while ($row = mysql_fetch_array($result)) { + echo '<TR>'; + echo '<TD>'.$row['album'].'</TD>'; + echo '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">'.FixTextFields($row['filename']).'</A></TD>'; + echo '</TR>'; + } + echo '</TABLE>'; + } + echo '<HR>'; + + } + +} elseif (!empty($_REQUEST['fileextensions'])) { + + $SQLquery = 'SELECT filename, fileformat, audio_dataformat, video_dataformat, tags'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + $invalidextensionfiles = 0; + $invalidextensionline = '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="4">'; + $invalidextensionline .= '<TR><TH>file</TH><TH>audio</TH><TH>video</TH><TH>tags</TH><TH>actual</TH><TH>correct</TH><TH>filename</TH></TR>'; + while ($row = mysql_fetch_array($result)) { + set_time_limit(30); + + $acceptableextensions = AcceptableExtensions($row['fileformat'], $row['audio_dataformat'], $row['video_dataformat']); + $actualextension = strtolower(fileextension($row['filename'])); + if ($acceptableextensions && !in_array($actualextension, $acceptableextensions)) { + $invalidextensionfiles++; + + $invalidextensionline .= '<TR>'; + $invalidextensionline .= '<TD>'.$row['fileformat'].'</TD>'; + $invalidextensionline .= '<TD>'.$row['audio_dataformat'].'</TD>'; + $invalidextensionline .= '<TD>'.$row['video_dataformat'].'</TD>'; + $invalidextensionline .= '<TD>'.$row['tags'].'</TD>'; + $invalidextensionline .= '<TD>'.$actualextension.'</TD>'; + $invalidextensionline .= '<TD>'.implode('; ', $acceptableextensions).'</TD>'; + $invalidextensionline .= '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">'.FixTextFields($row['filename']).'</A></TD>'; + $invalidextensionline .= '</TR>'; + } + } + $invalidextensionline .= '</TABLE><HR>'; + echo number_format($invalidextensionfiles).' files with incorrect filename extension:<BR>'; + echo $invalidextensionline; + +} elseif (isset($_REQUEST['genredistribution'])) { + + if (!empty($_REQUEST['m3u'])) { + + header('Content-type: audio/x-mpegurl'); + echo '#EXTM3U'."\n"; + $SQLquery = 'SELECT filename'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (BINARY genre = "'.$_REQUEST['genredistribution'].'")'; + $SQLquery .= ' AND (fileformat NOT LIKE "'.implode('") AND (fileformat NOT LIKE "', $IgnoreNoTagFormats).'")'; + $SQLquery .= ' ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + while ($row = mysql_fetch_array($result)) { + echo WindowsShareSlashTranslate($row['filename'])."\n"; + } + exit; + + } else { + + if ($_REQUEST['genredistribution'] == '%') { + + $SQLquery = 'SELECT COUNT(*) AS num, genre'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (fileformat NOT LIKE "'.implode('") AND (fileformat NOT LIKE "', $IgnoreNoTagFormats).'")'; + $SQLquery .= ' GROUP BY genre'; + $SQLquery .= ' ORDER BY num DESC'; + $result = safe_mysql_query($SQLquery); + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true); + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="4">'; + echo '<TR><TH>Count</TH><TH>Genre</TH><TH>m3u</TH></TR>'; + while ($row = mysql_fetch_array($result)) { + $GenreID = getid3_id3v1::LookupGenreID($row['genre']); + if (is_numeric($GenreID)) { + echo '<TR BGCOLOR="#00FF00;">'; + } else { + echo '<TR BGCOLOR="#FF9999;">'; + } + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?genredistribution='.urlencode($row['genre']).'">'.number_format($row['num']).'</A></TD>'; + echo '<TD NOWRAP>'.str_replace("\t", '<BR>', $row['genre']).'</TD>'; + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?m3u=.m3u&genredistribution='.urlencode($row['genre']).'">.m3u</A></TD>'; + echo '</TR>'; + } + echo '</TABLE><HR>'; + + } else { + + $SQLquery = 'SELECT filename, genre'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (genre LIKE "'.mysql_escape_string($_REQUEST['genredistribution']).'")'; + $SQLquery .= ' ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + echo '<A HREF="'.$_SERVER['PHP_SELF'].'?genredistribution='.urlencode('%').'">All Genres</A><BR>'; + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="4">'; + echo '<TR><TH>Genre</TH><TH>m3u</TH><TH>Filename</TH></TR>'; + while ($row = mysql_fetch_array($result)) { + echo '<TR>'; + echo '<TD NOWRAP>'.str_replace("\t", '<BR>', $row['genre']).'</TD>'; + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?m3ufilename='.urlencode($row['filename']).'">m3u</A></TD>'; + echo '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">'.FixTextFields($row['filename']).'</A></TD>'; + echo '</TR>'; + } + echo '</TABLE><HR>'; + + } + + + } + +} elseif (!empty($_REQUEST['formatdistribution'])) { + + $SQLquery = 'SELECT fileformat, audio_dataformat, COUNT(*) AS num'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' GROUP BY fileformat, audio_dataformat'; + $SQLquery .= ' ORDER BY num DESC'; + $result = safe_mysql_query($SQLquery); + echo 'File format distribution:<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="4">'; + echo '<TR><TH>Number</TH><TH>Format</TH></TR>'; + while ($row = mysql_fetch_array($result)) { + echo '<TR>'; + echo '<TD ALIGN="RIGHT">'.number_format($row['num']).'</TD>'; + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?filetypelist='.$row['fileformat'].'|'.$row['audio_dataformat'].'">'.($row['fileformat'] ? $row['fileformat'] : '<I>unknown</I>').(($row['audio_dataformat'] && ($row['audio_dataformat'] != $row['fileformat'])) ? '.'.$row['audio_dataformat'] : '').'</A></TD>'; + echo '</TR>'; + } + echo '</TABLE><HR>'; + +} elseif (!empty($_REQUEST['errorswarnings'])) { + + $SQLquery = 'SELECT filename, error, warning'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (error <> "")'; + $SQLquery .= ' OR (warning <> "")'; + $SQLquery .= ' ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + + if (!empty($_REQUEST['m3u'])) { + + header('Content-type: audio/x-mpegurl'); + echo '#EXTM3U'."\n"; + while ($row = mysql_fetch_array($result)) { + echo WindowsShareSlashTranslate($row['filename'])."\n"; + } + exit; + + } else { + + echo number_format(mysql_num_rows($result)).' files with errors or warnings:<BR>'; + echo '(<A HREF="'.$_SERVER['PHP_SELF'].'?errorswarnings=1&m3u=.m3u">.m3u version</A>)<BR>'; + echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="4">'; + echo '<TR><TH>Filename</TH><TH>Error</TH><TH>Warning</TH></TR>'; + while ($row = mysql_fetch_array($result)) { + echo '<TR>'; + echo '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">'.FixTextFields($row['filename']).'</A></TD>'; + echo '<TD>'.(!empty($row['error']) ? '<LI>'.str_replace("\t", '<LI>', FixTextFields($row['error'])).'</LI>' : ' ').'</TD>'; + echo '<TD>'.(!empty($row['warning']) ? '<LI>'.str_replace("\t", '<LI>', FixTextFields($row['warning'])).'</LI>' : ' ').'</TD>'; + echo '</TR>'; + } + } + echo '</TABLE><HR>'; + +} elseif (!empty($_REQUEST['fixid3v1padding'])) { + + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.id3v1.php', __FILE__, true); + $id3v1_writer = new getid3_write_id3v1; + + $SQLquery = 'SELECT filename, error, warning'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (fileformat = "mp3")'; + $SQLquery .= ' AND ((error <> "")'; + $SQLquery .= ' OR (warning <> ""))'; + $SQLquery .= ' ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + $totaltofix = mysql_num_rows($result); + $rowcounter = 0; + while ($row = mysql_fetch_array($result)) { + set_time_limit(30); + if (strpos($row['warning'], 'Some ID3v1 fields do not use NULL characters for padding') !== false) { + set_time_limit(30); + $id3v1_writer->filename = $row['filename']; + echo ($id3v1_writer->FixID3v1Padding() ? '<SPAN STYLE="color: #009900;">fixed - ' : '<SPAN STYLE="color: #FF0000;">error - '); + } else { + echo '<SPAN STYLE="color: #0000FF;">No error? - '; + } + echo '['.++$rowcounter.' / '.$totaltofix.'] '; + echo FixTextFields($row['filename']).'</SPAN><BR>'; + flush(); + } + +} elseif (!empty($_REQUEST['vbrmethod'])) { + + if ($_REQUEST['vbrmethod'] == '1') { + + $SQLquery = 'SELECT COUNT(*) AS num, vbr_method'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' GROUP BY vbr_method'; + $SQLquery .= ' ORDER BY vbr_method'; + $result = safe_mysql_query($SQLquery); + echo 'VBR methods:<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="4">'; + echo '<TR><TH>Count</TH><TH>VBR Method</TH></TR>'; + while ($row = mysql_fetch_array($result)) { + echo '<TR>'; + echo '<TD ALIGN="RIGHT">'.FixTextFields(number_format($row['num'])).'</TD>'; + if ($row['vbr_method']) { + echo '<TD><A HREF="'.$_SERVER['PHP_SELF'].'?vbrmethod='.$row['vbr_method'].'">'.FixTextFields($row['vbr_method']).'</A></TD>'; + } else { + echo '<TD><I>CBR</I></TD>'; + } + echo '</TR>'; + } + echo '</TABLE>'; + + } else { + + $SQLquery = 'SELECT filename'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (vbr_method = "'.mysql_escape_string($_REQUEST['vbrmethod']).'")'; + $result = safe_mysql_query($SQLquery); + echo number_format(mysql_num_rows($result)).' files with VBR_method of "'.$_REQUEST['vbrmethod'].'":<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="3">'; + while ($row = mysql_fetch_array($result)) { + echo '<TR><TD><A HREF="'.$_SERVER['PHP_SELF'].'?m3ufilename='.urlencode($row['filename']).'">m3u</A></TD>'; + echo '<TD><A HREF="demo.browse.php?filename='.rawurlencode($row['filename']).'">'.FixTextFields($row['filename']).'</A></TD></TR>'; + } + echo '</TABLE>'; + + } + echo '<HR>'; + +} elseif (!empty($_REQUEST['correctcase'])) { + + $SQLquery = 'SELECT filename, fileformat'; + $SQLquery .= ' FROM `files`'; + $SQLquery .= ' WHERE (fileformat <> "")'; + $SQLquery .= ' ORDER BY filename ASC'; + $result = safe_mysql_query($SQLquery); + echo 'Copy and paste the following into a DOS batch file. You may have to run this script more than once to catch all the changes (remember to scan for deleted/changed files and rescan directory between scans)<HR>'; + echo '<PRE>'; + $lastdir = ''; + while ($row = mysql_fetch_array($result)) { + set_time_limit(30); + $CleanedFilename = CleanUpFileName($row['filename']); + if ($row['filename'] != $CleanedFilename) { + if (strtolower($lastdir) != strtolower(str_replace('/', '\\', dirname($row['filename'])))) { + $lastdir = str_replace('/', '\\', dirname($row['filename'])); + echo 'cd "'.$lastdir.'"'."\n"; + } + echo 'ren "'.basename($row['filename']).'" "'.basename(CleanUpFileName($row['filename'])).'"'."\n"; + } + } + echo '</PRE>'; + echo '<HR>'; + +} + +function CleanUpFileName($filename) { + $DirectoryName = dirname($filename); + $FileExtension = fileextension(basename($filename)); + $BaseFilename = basename($filename, '.'.$FileExtension); + + $BaseFilename = strtolower($BaseFilename); + $BaseFilename = str_replace('_', ' ', $BaseFilename); + //$BaseFilename = str_replace('-', ' - ', $BaseFilename); + $BaseFilename = str_replace('(', ' (', $BaseFilename); + $BaseFilename = str_replace('( ', '(', $BaseFilename); + $BaseFilename = str_replace(')', ') ', $BaseFilename); + $BaseFilename = str_replace(' )', ')', $BaseFilename); + $BaseFilename = str_replace(' \'\'', ' “', $BaseFilename); + $BaseFilename = str_replace('\'\' ', '” ', $BaseFilename); + $BaseFilename = str_replace(' vs ', ' vs. ', $BaseFilename); + while (strstr($BaseFilename, ' ') !== false) { + $BaseFilename = str_replace(' ', ' ', $BaseFilename); + } + $BaseFilename = trim($BaseFilename); + + return $DirectoryName.'/'.BetterUCwords($BaseFilename).'.'.strtolower($FileExtension); +} + +function BetterUCwords($string) { + $stringlength = strlen($string); + + $string{0} = strtoupper($string{0}); + for ($i = 1; $i < $stringlength; $i++) { + if (($string{$i - 1} == '\'') && ($i > 1) && (($string{$i - 2} == 'O') || ($string{$i - 2} == ' '))) { + // O'Clock, 'Em + $string{$i} = strtoupper($string{$i}); + } elseif (ereg('^[\'A-Za-z0-9À-ÿ]$', $string{$i - 1})) { + $string{$i} = strtolower($string{$i}); + } else { + $string{$i} = strtoupper($string{$i}); + } + } + + static $LowerCaseWords = array('vs.', 'feat.'); + static $UpperCaseWords = array('DJ', 'USA', 'II', 'MC', 'CD', 'TV', '\'N\''); + + $OutputListOfWords = array(); + $ListOfWords = explode(' ', $string); + foreach ($ListOfWords as $ThisWord) { + if (in_array(strtolower(str_replace('(', '', $ThisWord)), $LowerCaseWords)) { + $ThisWord = strtolower($ThisWord); + } elseif (in_array(strtoupper(str_replace('(', '', $ThisWord)), $UpperCaseWords)) { + $ThisWord = strtoupper($ThisWord); + } elseif ((substr($ThisWord, 0, 2) == 'Mc') && (strlen($ThisWord) > 2)) { + $ThisWord{2} = strtoupper($ThisWord{2}); + } elseif ((substr($ThisWord, 0, 3) == 'Mac') && (strlen($ThisWord) > 3)) { + $ThisWord{3} = strtoupper($ThisWord{3}); + } + $OutputListOfWords[] = $ThisWord; + } + $UCstring = implode(' ', $OutputListOfWords); + $UCstring = str_replace(' From “', ' from “', $UCstring); + $UCstring = str_replace(' \'n\' ', ' \'N\' ', $UCstring); + + return $UCstring; +} + + + +echo '<HR><FORM ACTION="'.FixTextFields($_SERVER['PHP_SELF']).'">'; +echo '<B>Warning:</B> Scanning a new directory will erase all previous entries in the database!<BR>'; +echo 'Directory: <INPUT TYPE="TEXT" NAME="scan" VALUE="'.FixTextFields(!empty($_REQUEST['scan']) ? $_REQUEST['scan'] : '').'"> '; +echo '<INPUT TYPE="SUBMIT" VALUE="Go" onClick="return confirm(\'Are you sure you want to erase all entries in the database and start scanning again?\');">'; +echo '</FORM>'; +echo '<HR><FORM ACTION="'.FixTextFields($_SERVER['PHP_SELF']).'">'; +echo 'Re-scanning a new directory will only add new, previously unscanned files into the list (and not erase the database).<BR>'; +echo 'Directory: <INPUT TYPE="TEXT" NAME="newscan" VALUE="'.FixTextFields(!empty($_REQUEST['newscan']) ? $_REQUEST['newscan'] : '').'"> '; +echo '<INPUT TYPE="SUBMIT" VALUE="Go">'; +echo '</FORM><HR>'; +echo '<UL>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?deadfilescheck=1">Remove deleted or changed files from database</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?md5datadupes=1">List files with identical MD5_DATA values</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?artisttitledupes=1">List files with identical artist + title</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?fileextensions=1">File with incorrect file extension</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?formatdistribution=1">File Format Distribution</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?audiobitrates=1">Audio Bitrate Distribution</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?vbrmethod=1">VBR_Method Distribution</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?tagtypes=1">Tag Type Distribution</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?genredistribution='.urlencode('%').'">Genre Distribution</A></LI>'; +//echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?missingtrackvolume=1">Scan for missing track volume information (update database from pre-v1.7.0b5)</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?encoderoptionsdistribution=1">Encoder Options Distribution</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?encodedbydistribution='.urlencode('%').'">Encoded By (ID3v2) Distribution</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?trackinalbum=1">Track number in Album field</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?emptygenres=1">Blank genres</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?trackzero=1">Track "zero"</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?nonemptycomments=1">non-empty comments</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?unsynchronizedtags=2A1">Tags that are not synchronized</A> (<A HREF="'.$_SERVER['PHP_SELF'].'?unsynchronizedtags=2A1&autofix=1">autofix</A>)</LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?filenamepattern='.urlencode('A - T').'">Filenames that don\'t match pattern</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?correctcase=1">Correct filename case (Win/DOS)</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?fixid3v1padding=1">Fix ID3v1 invalid padding</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?errorswarnings=1">Files with Errors and/or Warnings</A></LI>'; +echo '<LI><A HREF="'.$_SERVER['PHP_SELF'].'?rescanerrors=1">Re-scan only files with Errors and/or Warnings</A></LI>'; +echo '</UL>'; + +$SQLquery = 'SELECT COUNT(*) AS TotalFiles, SUM(playtime_seconds) AS TotalPlaytime, SUM(filesize) AS TotalFilesize, AVG(playtime_seconds) AS AvgPlaytime, AVG(filesize) AS AvgFilesize, AVG(audio_bitrate + video_bitrate) AS AvgBitrate FROM `files`'; +$result = mysql_query($SQLquery); +if ($row = mysql_fetch_array($result)) { + echo '<HR><B>Currently in the database:</B><TABLE>'; + echo '<TR><TH ALIGN="LEFT">Total Files</TH><TD>'.number_format($row['TotalFiles']).'</TD></TR>'; + echo '<TR><TH ALIGN="LEFT">Total Filesize</TH><TD>'.number_format($row['TotalFilesize'] / 1048576).' MB</TD></TR>'; + echo '<TR><TH ALIGN="LEFT">Total Playtime</TH><TD>'.number_format($row['TotalPlaytime'] / 3600, 1).' hours</TD></TR>'; + echo '<TR><TH ALIGN="LEFT">Average Filesize</TH><TD>'.number_format($row['AvgFilesize'] / 1048576, 1).' MB</TD></TR>'; + echo '<TR><TH ALIGN="LEFT">Average Playtime</TH><TD>'.getid3_lib::PlaytimeString($row['AvgPlaytime']).'</TD></TR>'; + echo '<TR><TH ALIGN="LEFT">Average Bitrate</TH><TD>'.BitrateText($row['AvgBitrate'] / 1000, 1).'</TD></TR>'; + echo '</TABLE>'; +} + +?> +</BODY> +</HTML>
\ No newline at end of file diff --git a/modules/id3/demos/demo.simple.php b/modules/id3/demos/demo.simple.php new file mode 100644 index 00000000..db937f1e --- /dev/null +++ b/modules/id3/demos/demo.simple.php @@ -0,0 +1,53 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// /demo/demo.simple.php - part of getID3() // +// Sample script for scanning a single directory and // +// displaying a few pieces of information for each file // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + +echo '<HTML><HEAD>'; +echo '<TITLE>getID3() - /demo/demo.simple.php (sample script)</TITLE>'; +echo '<STYLE>BODY,TD,TH { font-family: sans-serif; font-size: 9pt; }</STYLE>'; +echo '</HEAD><BODY>'; + + +// include getID3() library (can be in a different directory if full path is specified) +require_once('../getid3/getid3.php'); + +// Initialize getID3 engine +$getID3 = new getID3; + +$DirectoryToScan = '/change/to/directory/you/want/to/scan'; // change to whatever directory you want to scan +$dir = opendir($DirectoryToScan); +echo '<TABLE BORDER="1" CELLSPACING="0" CELLPADDING="3">'; +echo '<TR><TH>Filename</TH><TH>Artist</TH><TH>Title</TH><TH>Bitrate</TH><TH>Playtime</TH></TR>'; +while (($file = readdir($dir)) !== false) { + $FullFileName = realpath($DirectoryToScan.'/'.$file); + if (is_file($FullFileName)) { + set_time_limit(30); + + $ThisFileInfo = $getID3->analyze($FullFileName); + + getid3_lib::CopyTagsToComments($ThisFileInfo); + + // output desired information in whatever format you want + echo '<TR>'; + echo '<TD>'.$ThisFileInfo['filenamepath'].'</TD>'; + echo '<TD>'.(!empty($ThisFileInfo['comments_html']['artist']) ? implode('<BR>', $ThisFileInfo['comments_html']['artist']) : ' ').'</TD>'; + echo '<TD>'.(!empty($ThisFileInfo['comments_html']['title']) ? implode('<BR>', $ThisFileInfo['comments_html']['title']) : ' ').'</TD>'; + echo '<TD ALIGN="RIGHT">'.(!empty($ThisFileInfo['audio']['bitrate']) ? round($ThisFileInfo['audio']['bitrate'] / 1000).' kbps' : ' ').'</TD>'; + echo '<TD ALIGN="RIGHT">'.(!empty($ThisFileInfo['playtime_string']) ? $ThisFileInfo['playtime_string'] : ' ').'</TD>'; + echo '</TR>'; + } +} + +?> +</BODY> +</HTML>
\ No newline at end of file diff --git a/modules/id3/demos/demo.write.php b/modules/id3/demos/demo.write.php new file mode 100644 index 00000000..40a6e630 --- /dev/null +++ b/modules/id3/demos/demo.write.php @@ -0,0 +1,254 @@ +<HTML> +<HEAD> +<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8"/> +<TITLE>getID3() - Sample tag writer</TITLE> +</HEAD> +<BODY> +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// /demo/demo.write.php - part of getID3() // +// sample script for demonstrating writing ID3v1 and ID3v2 // +// tags for MP3, or Ogg comment tags for Ogg Vorbis // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + +require_once('../getid3/getid3.php'); +// Initialize getID3 engine +$getID3 = new getID3; + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.php', __FILE__, true); + + +function FixTextFields($text) { + return htmlentities(getid3_lib::SafeStripSlashes($text), ENT_QUOTES); +} + +$Filename = (isset($_REQUEST['Filename']) ? getid3_lib::SafeStripSlashes($_REQUEST['Filename']) : ''); + + + +if (isset($_POST['WriteTags'])) { + + $TagFormatsToWrite = (isset($_POST['TagFormatsToWrite']) ? $_POST['TagFormatsToWrite'] : array()); + if (!empty($TagFormatsToWrite)) { + echo 'starting to write tag(s)<BR>'; + + $tagwriter = new getid3_writetags; + $tagwriter->filename = $Filename; + $tagwriter->tagformats = $TagFormatsToWrite; + $tagwriter->overwrite_tags = true; + $tagwriter->tag_encoding = 'UTF-8'; // this is set in the META tag at the top of this file + + $commonkeysarray = array('Title', 'Artist', 'Album', 'Year', 'Comment'); + foreach ($commonkeysarray as $key) { + if (!empty($_POST[$key])) { + $TagData[strtolower($key)][] = getid3_lib::SafeStripSlashes($_POST[$key]); + } + } + if (!empty($_POST['Genre'])) { + $TagData['genre'][] = getid3_lib::SafeStripSlashes($_POST['Genre']); + } + if (!empty($_POST['GenreOther'])) { + $TagData['genre'][] = getid3_lib::SafeStripSlashes($_POST['GenreOther']); + } + if (!empty($_POST['Track'])) { + $TagData['track'][] = getid3_lib::SafeStripSlashes($_POST['Track'].(!empty($_POST['TracksTotal']) ? '/'.$_POST['TracksTotal'] : '')); + } + + if (!empty($_FILES['userfile']['tmp_name'])) { + if (in_array('id3v2.4', $tagwriter->tagformats) || in_array('id3v2.3', $tagwriter->tagformats) || in_array('id3v2.2', $tagwriter->tagformats)) { + if (is_uploaded_file($_FILES['userfile']['tmp_name'])) { + if ($fd = @fopen($_FILES['userfile']['tmp_name'], 'rb')) { + $APICdata = fread($fd, filesize($_FILES['userfile']['tmp_name'])); + fclose ($fd); + + list($APIC_width, $APIC_height, $APIC_imageTypeID) = GetImageSize($_FILES['userfile']['tmp_name']); + $imagetypes = array(1=>'gif', 2=>'jpeg', 3=>'png'); + if (isset($imagetypes[$APIC_imageTypeID])) { + + $TagData['attached_picture'][0]['data'] = $APICdata; + $TagData['attached_picture'][0]['picturetypeid'] = $_POST['APICpictureType']; + $TagData['attached_picture'][0]['description'] = $_FILES['userfile']['name']; + $TagData['attached_picture'][0]['mime'] = 'image/'.$imagetypes[$APIC_imageTypeID]; + + } else { + echo '<B>invalid image format (only GIF, JPEG, PNG)</B><BR>'; + } + } else { + echo '<B>cannot open '.$_FILES['userfile']['tmp_name'].'</B><BR>'; + } + } else { + echo '<B>!is_uploaded_file('.$_FILES['userfile']['tmp_name'].')</B><BR>'; + } + } else { + echo '<B>WARNING:</B> Can only embed images for ID3v2<BR>'; + } + } + + $tagwriter->tag_data = $TagData; + if ($tagwriter->WriteTags()) { + echo 'Successfully wrote tags<BR>'; + if (!empty($tagwriter->warnings)) { + echo 'There were some warnings:<BLOCKQUOTE STYLE="background-color:#FFCC33; padding: 10px;">'.implode('<BR><BR>', $tagwriter->warnings).'</BLOCKQUOTE>'; + } + } else { + echo 'Failed to write tags!<BLOCKQUOTE STYLE="background-color:#FF9999; padding: 10px;">'.implode('<BR><BR>', $tagwriter->errors).'</BLOCKQUOTE>'; + } + + } else { + + echo 'WARNING: no tag formats selected for writing - nothing written'; + + } + echo '<HR>'; + +} + +echo '<H4>Sample tag editor/writer</H4>'; +echo '<A HREF="demo.check.php?listdirectory='.rawurlencode(realpath(dirname($Filename))).'">Browse current directory</A><BR>'; +if (!empty($Filename)) { + echo '<A HREF="'.$_SERVER['PHP_SELF'].'">Start Over</A><BR><BR>'; + echo '<TABLE BORDER="3" CELLSPACING="0" CELLPADDING="4"><FORM ACTION="'.$_SERVER['PHP_SELF'].'" METHOD="POST" ENCTYPE="multipart/form-data">'; + echo '<TR><TD ALIGN="RIGHT"><B>Filename: </B></TD><TD><INPUT TYPE="HIDDEN" NAME="Filename" VALUE="'.FixTextFields($Filename).'"><A HREF="demo.check.php?filename='.rawurlencode($Filename).'" TARGET="_blank">'.$Filename.'</A></TD></TR>'; + if (file_exists($Filename)) { + + // Initialize getID3 engine + $getID3 = new getID3; + $OldThisFileInfo = $getID3->analyze($Filename); + getid3_lib::CopyTagsToComments($OldThisFileInfo); + + switch ($OldThisFileInfo['fileformat']) { + case 'mp3': + case 'mp2': + case 'mp1': + $ValidTagTypes = array('id3v1', 'id3v2.3', 'ape'); + break; + + case 'mpc': + $ValidTagTypes = array('ape'); + break; + + case 'ogg': + if (@$OldThisFileInfo['audio']['dataformat'] == 'flac') { + //$ValidTagTypes = array('metaflac'); + // metaflac doesn't (yet) work with OggFLAC files + $ValidTagTypes = array(); + } else { + $ValidTagTypes = array('vorbiscomment'); + } + break; + + case 'flac': + $ValidTagTypes = array('metaflac'); + break; + + default: + $ValidTagTypes = array(); + break; + } + echo '<TR><TD ALIGN="RIGHT"><B>Title</B></TD> <TD><INPUT TYPE="TEXT" SIZE="40" NAME="Title" VALUE="'.FixTextFields(isset($OldThisFileInfo['comments']['title']) ? @implode(', ', $OldThisFileInfo['comments']['title']) : '').'"></TD></TR>'; + echo '<TR><TD ALIGN="RIGHT"><B>Artist</B></TD><TD><INPUT TYPE="TEXT" SIZE="40" NAME="Artist" VALUE="'.FixTextFields(isset($OldThisFileInfo['comments']['artist']) ? @implode(', ', $OldThisFileInfo['comments']['artist']) : '').'"></TD></TR>'; + echo '<TR><TD ALIGN="RIGHT"><B>Album</B></TD> <TD><INPUT TYPE="TEXT" SIZE="40" NAME="Album" VALUE="'.FixTextFields(isset($OldThisFileInfo['comments']['album']) ? @implode(', ', $OldThisFileInfo['comments']['album']) : '').'"></TD></TR>'; + echo '<TR><TD ALIGN="RIGHT"><B>Year</B></TD> <TD><INPUT TYPE="TEXT" SIZE="4" NAME="Year" VALUE="'.FixTextFields(isset($OldThisFileInfo['comments']['year']) ? @implode(', ', $OldThisFileInfo['comments']['year']) : '').'"></TD></TR>'; + + $TracksTotal = ''; + $TrackNumber = ''; + if (!empty($OldThisFileInfo['comments']['tracknumber']) && is_array($OldThisFileInfo['comments']['tracknumber'])) { + $RawTrackNumberArray = $OldThisFileInfo['comments']['tracknumber']; + } elseif (!empty($OldThisFileInfo['comments']['track']) && is_array($OldThisFileInfo['comments']['track'])) { + $RawTrackNumberArray = $OldThisFileInfo['comments']['track']; + } else { + $RawTrackNumberArray = array(); + } + foreach ($RawTrackNumberArray as $key => $value) { + if (strlen($value) > strlen($TrackNumber)) { + // ID3v1 may store track as "3" but ID3v2/APE would store as "03/16" + $TrackNumber = $value; + } + } + if (strstr($TrackNumber, '/')) { + list($TrackNumber, $TracksTotal) = explode('/', $TrackNumber); + } + echo '<TR><TD ALIGN="RIGHT"><B>Track</B></TD><TD><INPUT TYPE="TEXT" SIZE="2" NAME="Track" VALUE="'.FixTextFields($TrackNumber).'"> of <INPUT TYPE="TEXT" SIZE="2" NAME="TracksTotal" VALUE="'.FixTextFields($TracksTotal).'"></TD></TR>'; + + $ArrayOfGenresTemp = getid3_id3v1::ArrayOfGenres(); // get the array of genres + foreach ($ArrayOfGenresTemp as $key => $value) { // change keys to match displayed value + $ArrayOfGenres[$value] = $value; + } + unset($ArrayOfGenresTemp); // remove temporary array + unset($ArrayOfGenres['Cover']); // take off these special cases + unset($ArrayOfGenres['Remix']); + unset($ArrayOfGenres['Unknown']); + $ArrayOfGenres[''] = '- Unknown -'; // Add special cases back in with renamed key/value + $ArrayOfGenres['Cover'] = '-Cover-'; + $ArrayOfGenres['Remix'] = '-Remix-'; + asort($ArrayOfGenres); // sort into alphabetical order + echo '<TR><TD ALIGN="RIGHT"><B>Genre</B></TD><TD><SELECT NAME="Genre">'; + $AllGenresArray = (!empty($OldThisFileInfo['comments']['genre']) ? $OldThisFileInfo['comments']['genre'] : array()); + foreach ($ArrayOfGenres as $key => $value) { + echo '<OPTION VALUE="'.$key.'"'; + if (in_array($key, $AllGenresArray)) { + echo ' SELECTED'; + unset($AllGenresArray[array_search($key, $AllGenresArray)]); + sort($AllGenresArray); + } + echo '>'.$value.'</OPTION>'; + //echo '<OPTION VALUE="'.FixTextFields($value).'"'.((@$OldThisFileInfo['comments']['genre'][0] == $value) ? ' SELECTED' : '').'>'.$value.'</OPTION>'; + } + echo '</SELECT><INPUT TYPE="TEXT" NAME="GenreOther" SIZE="10" VALUE="'.FixTextFields(@$AllGenresArray[0]).'"></TD></TR>'; + + echo '<TR><TD ALIGN="RIGHT"><B>Write Tags</B></TD><TD>'; + foreach ($ValidTagTypes as $ValidTagType) { + echo '<INPUT TYPE="CHECKBOX" NAME="TagFormatsToWrite[]" VALUE="'.$ValidTagType.'"'; + if (count($ValidTagTypes) == 1) { + echo ' CHECKED'; + } else { + switch ($ValidTagType) { + case 'id3v2.2': + case 'id3v2.3': + case 'id3v2.4': + if (isset($OldThisFileInfo['tags']['id3v2'])) { + echo ' CHECKED'; + } + break; + default: + if (isset($OldThisFileInfo['tags'][$ValidTagType])) { + echo ' CHECKED'; + } + break; + } + } + echo '>'.$ValidTagType.'<BR>'; + } + echo '</TD></TR>'; + + echo '<TR><TD ALIGN="RIGHT"><B>Comment</B></TD><TD><TEXTAREA COLS="30" ROWS="3" NAME="Comment" WRAP="VIRTUAL">'.(isset($OldThisFileInfo['comments']['comment']) ? @implode("\n", $OldThisFileInfo['comments']['comment']) : '').'</TEXTAREA></TD></TR>'; + + echo '<TR><TD ALIGN="RIGHT"><B>Picture</B><BR>(ID3v2 only)</TD><TD><INPUT TYPE="FILE" NAME="userfile" ACCEPT="image/jpeg, image/gif, image/png"><BR>'; + echo '<SELECT NAME="APICpictureType">'; + $APICtypes = getid3_id3v2::APICPictureTypeLookup('', true); + foreach ($APICtypes as $key => $value) { + echo '<OPTION VALUE="'.FixTextFields($key).'">'.FixTextFields($value).'</OPTION>'; + } + echo '</SELECT></TD></TR>'; + echo '<TR><TD ALIGN="CENTER" COLSPAN="2"><INPUT TYPE="SUBMIT" NAME="WriteTags" VALUE="Save Changes"> '; + echo '<INPUT TYPE="RESET" VALUE="Reset"></TD></TR>'; + + } else { + + echo '<TR><TD ALIGN="RIGHT"><B>Error</B></TD><TD>'.FixTextFields($Filename).' does not exist</TD></TR>'; + + } + echo '</FORM></TABLE>'; + +} + +?> +</BODY> +</HTML>
\ No newline at end of file diff --git a/modules/id3/demos/index.php b/modules/id3/demos/index.php new file mode 100644 index 00000000..13783f0d --- /dev/null +++ b/modules/id3/demos/index.php @@ -0,0 +1 @@ +In this directory are a number of examples of how to use <A HREF="http://www.getid3.org">getID3()</A> - if you don't know what to run, take a look at <A HREF="demo.browse.php">demo.browse.php</A>
\ No newline at end of file diff --git a/modules/id3/dependencies.txt b/modules/id3/dependencies.txt new file mode 100644 index 00000000..34a72ba8 --- /dev/null +++ b/modules/id3/dependencies.txt @@ -0,0 +1,24 @@ +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// dependencies.txt - part of getID3() // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + +lyrics3 depends on apetag (optional) +ogg depends on flac +id3v2 depends on id3v1 +apetag depends on id3v1 (optional, writing only) +bonk depends on id3v2 (optional) +riff depends on mp3 +mpeg depends on mp3 +quicktime depends on mp3 +flac depends on ogg +optimfrog depends on riff +la depends on riff +lpac depends on riff +asf depends on riff, id3v1 (optional) diff --git a/modules/id3/getid3/extension.cache.dbm.php b/modules/id3/getid3/extension.cache.dbm.php new file mode 100644 index 00000000..051bb1f0 --- /dev/null +++ b/modules/id3/getid3/extension.cache.dbm.php @@ -0,0 +1,222 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// extension.cache.dbm.php - part of getID3() // +// Please see readme.txt for more information // +// /// +///////////////////////////////////////////////////////////////// +// // +// This extension written by Allan Hansen <ahØartemis*dk> // +// /// +///////////////////////////////////////////////////////////////// + + +/** +* This is a caching extension for getID3(). It works the exact same +* way as the getID3 class, but return cached information very fast +* +* Example: +* +* Normal getID3 usage (example): +* +* require_once 'getid3/getid3.php'; +* $getID3 = new getID3; +* $getID3->encoding = 'UTF-8'; +* $info1 = $getID3->analyze('file1.flac'); +* $info2 = $getID3->analyze('file2.wv'); +* +* getID3_cached usage: +* +* require_once 'getid3/getid3.php'; +* require_once 'getid3/getid3/extension.cache.dbm.php'; +* $getID3 = new getID3_cached('db3', '/tmp/getid3_cache.dbm', +* '/tmp/getid3_cache.lock'); +* $getID3->encoding = 'UTF-8'; +* $info1 = $getID3->analyze('file1.flac'); +* $info2 = $getID3->analyze('file2.wv'); +* +* +* Supported Cache Types +* +* SQL Databases: (use extension.cache.mysql) +* +* cache_type cache_options +* ------------------------------------------------------------------- +* mysql host, database, username, password +* +* +* DBM-Style Databases: (this extension) +* +* cache_type cache_options +* ------------------------------------------------------------------- +* gdbm dbm_filename, lock_filename +* ndbm dbm_filename, lock_filename +* db2 dbm_filename, lock_filename +* db3 dbm_filename, lock_filename +* db4 dbm_filename, lock_filename (PHP5 required) +* +* PHP must have write access to both dbm_filename and lock_filename. +* +* +* Recommended Cache Types +* +* Infrequent updates, many reads any DBM +* Frequent updates mysql +*/ + + +class getID3_cached_dbm extends getID3 +{ + + // public: constructor - see top of this file for cache type and cache_options + function getID3_cached_dbm($cache_type, $dbm_filename, $lock_filename) { + + // Check for dba extension + if (!extension_loaded('dba')) { + die('PHP is not compiled with dba support, required to use DBM style cache.'); + } + + // Check for specific dba driver + if (function_exists('dba_handlers')) { // PHP 4.3.0+ + if (!in_array('db3', dba_handlers())) { + die('PHP is not compiled --with '.$cache_type.' support, required to use DBM style cache.'); + } + } + else { // PHP <= 4.2.3 + ob_start(); // nasty, buy the only way to check... + phpinfo(); + $contents = ob_get_contents(); + ob_end_clean(); + if (!strstr($contents, $cache_type)) { + die('PHP is not compiled --with '.$cache_type.' support, required to use DBM style cache.'); + } + } + + // Create lock file if needed + if (!file_exists($lock_filename)) { + if (!touch($lock_filename)) { + die('failed to create lock file: ' . $lock_filename); + } + } + + // Open lock file for writing + if (!is_writeable($lock_filename)) { + die('lock file: ' . $lock_filename . ' is not writable'); + } + $this->lock = fopen($lock_filename, 'w'); + + // Acquire exclusive write lock to lock file + flock($this->lock, LOCK_EX); + + // Create dbm-file if needed + if (!file_exists($dbm_filename)) { + if (!touch($dbm_filename)) { + die('failed to create dbm file: ' . $dbm_filename); + } + } + + // Try to open dbm file for writing + $this->dba = @dba_open($dbm_filename, 'w', $cache_type); + if (!$this->dba) { + + // Failed - create new dbm file + $this->dba = dba_open($dbm_filename, 'n', $cache_type); + + if (!$this->dba) { + die('failed to create dbm file: ' . $dbm_filename); + } + + // Insert getID3 version number + dba_insert(GETID3_VERSION, GETID3_VERSION, $this->dba); + } + + // Init misc values + $this->cache_type = $cache_type; + $this->dbm_filename = $dbm_filename; + + // Register destructor + register_shutdown_function(array($this, '__destruct')); + + // Check version number and clear cache if changed + if (dba_fetch(GETID3_VERSION, $this->dba) != GETID3_VERSION) { + $this->clear_cache(); + } + + parent::getID3(); + } + + + + // public: destuctor + function __destruct() { + + // Close dbm file + @dba_close($this->dba); + + // Release exclusive lock + @flock($this->lock, LOCK_UN); + + // Close lock file + @fclose($this->lock); + } + + + + // public: clear cache + function clear_cache() { + + // Close dbm file + dba_close($this->dba); + + // Create new dbm file + $this->dba = dba_open($this->dbm_filename, 'n', $this->cache_type); + + if (!$this->dba) { + die('failed to clear cache/recreate dbm file: ' . $this->dbm_filename); + } + + // Insert getID3 version number + dba_insert(GETID3_VERSION, GETID3_VERSION, $this->dba); + + // Reregister shutdown function + register_shutdown_function(array($this, '__destruct')); + } + + + + // public: analyze file + function analyze($filename) { + + if (file_exists($filename)) { + + // Calc key filename::mod_time::size - should be unique + $key = $filename . '::' . filemtime($filename) . '::' . filesize($filename); + + // Loopup key + $result = dba_fetch($key, $this->dba); + + // Hit + if ($result !== false) { + return unserialize($result); + } + } + + // Miss + $result = parent::analyze($filename); + + // Save result + if (file_exists($filename)) { + dba_insert($key, serialize($result), $this->dba); + } + + return $result; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/extension.cache.mysql.php b/modules/id3/getid3/extension.cache.mysql.php new file mode 100644 index 00000000..40ea6883 --- /dev/null +++ b/modules/id3/getid3/extension.cache.mysql.php @@ -0,0 +1,171 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// extension.cache.mysql.php - part of getID3() // +// Please see readme.txt for more information // +// /// +///////////////////////////////////////////////////////////////// +// // +// This extension written by Allan Hansen <ahØartemis*dk> // +// /// +///////////////////////////////////////////////////////////////// + + +/** +* This is a caching extension for getID3(). It works the exact same +* way as the getID3 class, but return cached information very fast +* +* Example: (see also demo.cache.mysql.php in /demo/) +* +* Normal getID3 usage (example): +* +* require_once 'getid3/getid3.php'; +* $getID3 = new getID3; +* $getID3->encoding = 'UTF-8'; +* $info1 = $getID3->analyze('file1.flac'); +* $info2 = $getID3->analyze('file2.wv'); +* +* getID3_cached usage: +* +* require_once 'getid3/getid3.php'; +* require_once 'getid3/getid3/extension.cache.mysql.php'; +* $getID3 = new getID3_cached_mysql('localhost', 'database', +* 'username', 'password'); +* $getID3->encoding = 'UTF-8'; +* $info1 = $getID3->analyze('file1.flac'); +* $info2 = $getID3->analyze('file2.wv'); +* +* +* Supported Cache Types (this extension) +* +* SQL Databases: +* +* cache_type cache_options +* ------------------------------------------------------------------- +* mysql host, database, username, password +* +* +* DBM-Style Databases: (use extension.cache.dbm) +* +* cache_type cache_options +* ------------------------------------------------------------------- +* gdbm dbm_filename, lock_filename +* ndbm dbm_filename, lock_filename +* db2 dbm_filename, lock_filename +* db3 dbm_filename, lock_filename +* db4 dbm_filename, lock_filename (PHP5 required) +* +* PHP must have write access to both dbm_filename and lock_filename. +* +* +* Recommended Cache Types +* +* Infrequent updates, many reads any DBM +* Frequent updates mysql +*/ + + +class getID3_cached_mysql extends getID3 +{ + + // private vars + var $cursor; + var $connection; + + + // public: constructor - see top of this file for cache type and cache_options + function getID3_cached_mysql($host, $database, $username, $password) { + + // Check for mysql support + if (!function_exists('mysql_pconnect')) { + die('PHP not compiled with mysql support.'); + } + + // Connect to database + $this->connection = mysql_pconnect($host, $username, $password); + if (!$this->connection) { + die('mysql_pconnect() failed - check permissions and spelling.'); + } + + // Select database + if (!mysql_select_db($database, $this->connection)) { + die('Cannot use database '.$database); + } + + // Create cache table if not exists + $this->create_table(); + + // Check version number and clear cache if changed + $this->cursor = mysql_query("SELECT `value` FROM `getid3_cache` WHERE (`filename` = '".GETID3_VERSION."') AND (`filesize` = '-1') AND (`filetime` = '-1') AND (`analyzetime` = '-1')", $this->connection); + list($version) = @mysql_fetch_array($this->cursor); + if ($version != GETID3_VERSION) { + $this->clear_cache(); + } + + parent::getID3(); + } + + + + // public: clear cache + function clear_cache() { + + $this->cursor = mysql_query("DELETE FROM `getid3_cache`", $this->connection); + $this->cursor = mysql_query("INSERT INTO `getid3_cache` VALUES ('".GETID3_VERSION."', -1, -1, -1, '".GETID3_VERSION."')", $this->connection); + } + + + + // public: analyze file + function analyze($filename) { + + if (file_exists($filename)) { + + // Short-hands + $filetime = filemtime($filename); + $filesize = filesize($filename); + $filenam2 = mysql_escape_string($filename); + + // Loopup file + $this->cursor = mysql_query("SELECT `value` FROM `getid3_cache` WHERE (`filename`='".$filenam2."') AND (`filesize`='".$filesize."') AND (`filetime`='".$filetime."')", $this->connection); + list($result) = @mysql_fetch_array($this->cursor); + + // Hit + if ($result) { + return unserialize($result); + } + } + + // Miss + $result = parent::analyze($filename); + + // Save result + if (file_exists($filename)) { + $res2 = mysql_escape_string(serialize($result)); + $this->cursor = mysql_query("INSERT INTO `getid3_cache` (`filename`, `filesize`, `filetime`, `analyzetime`, `value`) VALUES ('".$filenam2."', '".$filesize."', '".$filetime."', '".time()."', '".$res2."')", $this->connection); + } + return $result; + } + + + + // private: (re)create sql table + function create_table($drop = false) { + + $this->cursor = mysql_query("CREATE TABLE IF NOT EXISTS `getid3_cache` ( + `filename` VARCHAR(255) NOT NULL DEFAULT '', + `filesize` INT(11) NOT NULL DEFAULT '0', + `filetime` INT(11) NOT NULL DEFAULT '0', + `analyzetime` INT(11) NOT NULL DEFAULT '0', + `value` TEXT NOT NULL, + PRIMARY KEY (`filename`,`filesize`,`filetime`)) TYPE=MyISAM", $this->connection); + echo mysql_error($this->connection); + } +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/getid3.lib.php b/modules/id3/getid3/getid3.lib.php new file mode 100644 index 00000000..be7b113f --- /dev/null +++ b/modules/id3/getid3/getid3.lib.php @@ -0,0 +1,1427 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// getid3.lib.php - part of getID3() // +// See readme.txt for more details // +// // +///////////////////////////////////////////////////////////////// +// getid3_lib::GetURLImageSize( $urlpic ) determines the // +// dimensions of local/remote URL pictures. // +// returns array with ($width, $height, $type) // +// // +// Thanks to: Oyvind Hallsteinsen aka Gosub / ELq - // +// gosubØelq*org for the original size determining code // +// // +// PHP Hack by Filipe Laborde-Basto Oct 21/2000 // +// FREELY DISTRIBUTABLE -- use at your sole discretion! :) // +// Enjoy. (Not to be sold in commercial packages though, // +// keep it free!) Feel free to contact me at filØrezox*com // +// (http://www.rezox.com) // +// // +// Modified by James Heinrich <getid3Øusers*sourceforge*net> // +// June 1, 2001 - created GetDataImageSize($imgData) by // +// seperating the fopen() stuff to GetURLImageSize($urlpic) // +// which then calls GetDataImageSize($imgData). The idea being // +// you can call GetDataImageSize($imgData) with image data // +// from a database etc. // +// /// +///////////////////////////////////////////////////////////////// + + +define('GETID3_GIF_SIG', "\x47\x49\x46"); // 'GIF' +define('GETID3_PNG_SIG', "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"); +define('GETID3_JPG_SIG', "\xFF\xD8\xFF"); +define('GETID3_JPG_SOS', "\xDA"); // Start Of Scan - image data start +define('GETID3_JPG_SOF0', "\xC0"); // Start Of Frame N +define('GETID3_JPG_SOF1', "\xC1"); // N indicates which compression process +define('GETID3_JPG_SOF2', "\xC2"); // Only SOF0-SOF2 are now in common use +define('GETID3_JPG_SOF3', "\xC3"); +// NB: codes C4 and CC are *not* SOF markers +define('GETID3_JPG_SOF5', "\xC5"); +define('GETID3_JPG_SOF6', "\xC6"); +define('GETID3_JPG_SOF7', "\xC7"); +define('GETID3_JPG_SOF9', "\xC9"); +define('GETID3_JPG_SOF10', "\xCA"); +define('GETID3_JPG_SOF11', "\xCB"); +// NB: codes C4 and CC are *not* SOF markers +define('GETID3_JPG_SOF13', "\xCD"); +define('GETID3_JPG_SOF14', "\xCE"); +define('GETID3_JPG_SOF15', "\xCF"); +define('GETID3_JPG_EOI', "\xD9"); // End Of Image (end of datastream) + + + +class getid3_lib +{ + + function PrintHexBytes($string, $hex=true, $spaces=true, $htmlsafe=true) { + $returnstring = ''; + for ($i = 0; $i < strlen($string); $i++) { + if ($hex) { + $returnstring .= str_pad(dechex(ord($string{$i})), 2, '0', STR_PAD_LEFT); + } else { + $returnstring .= ' '.(ereg("[\x20-\x7E]", $string{$i}) ? $string{$i} : '¤'); + } + if ($spaces) { + $returnstring .= ' '; + } + } + if ($htmlsafe) { + $returnstring = htmlentities($returnstring); + } + return $returnstring; + } + + function SafeStripSlashes($text) { + if (get_magic_quotes_gpc()) { + return stripslashes($text); + } + return $text; + } + + + function trunc($floatnumber) { + // truncates a floating-point number at the decimal point + // returns int (if possible, otherwise float) + if ($floatnumber >= 1) { + $truncatednumber = floor($floatnumber); + } elseif ($floatnumber <= -1) { + $truncatednumber = ceil($floatnumber); + } else { + $truncatednumber = 0; + } + if ($truncatednumber <= 1073741824) { // 2^30 + $truncatednumber = (int) $truncatednumber; + } + return $truncatednumber; + } + + + function CastAsInt($floatnum) { + // convert to float if not already + $floatnum = (float) $floatnum; + + // convert a float to type int, only if possible + if (getid3_lib::trunc($floatnum) == $floatnum) { + // it's not floating point + if ($floatnum <= 1073741824) { // 2^30 + // it's within int range + $floatnum = (int) $floatnum; + } + } + return $floatnum; + } + + + function DecimalBinary2Float($binarynumerator) { + $numerator = getid3_lib::Bin2Dec($binarynumerator); + $denominator = getid3_lib::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator))); + return ($numerator / $denominator); + } + + + function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) { + // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html + if (strpos($binarypointnumber, '.') === false) { + $binarypointnumber = '0.'.$binarypointnumber; + } elseif ($binarypointnumber{0} == '.') { + $binarypointnumber = '0'.$binarypointnumber; + } + $exponent = 0; + while (($binarypointnumber{0} != '1') || (substr($binarypointnumber, 1, 1) != '.')) { + if (substr($binarypointnumber, 1, 1) == '.') { + $exponent--; + $binarypointnumber = substr($binarypointnumber, 2, 1).'.'.substr($binarypointnumber, 3); + } else { + $pointpos = strpos($binarypointnumber, '.'); + $exponent += ($pointpos - 1); + $binarypointnumber = str_replace('.', '', $binarypointnumber); + $binarypointnumber = $binarypointnumber{0}.'.'.substr($binarypointnumber, 1); + } + } + $binarypointnumber = str_pad(substr($binarypointnumber, 0, $maxbits + 2), $maxbits + 2, '0', STR_PAD_RIGHT); + return array('normalized'=>$binarypointnumber, 'exponent'=>(int) $exponent); + } + + + function Float2BinaryDecimal($floatvalue) { + // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html + $maxbits = 128; // to how many bits of precision should the calculations be taken? + $intpart = getid3_lib::trunc($floatvalue); + $floatpart = abs($floatvalue - $intpart); + $pointbitstring = ''; + while (($floatpart != 0) && (strlen($pointbitstring) < $maxbits)) { + $floatpart *= 2; + $pointbitstring .= (string) getid3_lib::trunc($floatpart); + $floatpart -= getid3_lib::trunc($floatpart); + } + $binarypointnumber = decbin($intpart).'.'.$pointbitstring; + return $binarypointnumber; + } + + + function Float2String($floatvalue, $bits) { + // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html + switch ($bits) { + case 32: + $exponentbits = 8; + $fractionbits = 23; + break; + + case 64: + $exponentbits = 11; + $fractionbits = 52; + break; + + default: + return false; + break; + } + if ($floatvalue >= 0) { + $signbit = '0'; + } else { + $signbit = '1'; + } + $normalizedbinary = getid3_lib::NormalizeBinaryPoint(getid3_lib::Float2BinaryDecimal($floatvalue), $fractionbits); + $biasedexponent = pow(2, $exponentbits - 1) - 1 + $normalizedbinary['exponent']; // (127 or 1023) +/- exponent + $exponentbitstring = str_pad(decbin($biasedexponent), $exponentbits, '0', STR_PAD_LEFT); + $fractionbitstring = str_pad(substr($normalizedbinary['normalized'], 2), $fractionbits, '0', STR_PAD_RIGHT); + + return getid3_lib::BigEndian2String(getid3_lib::Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false); + } + + + function LittleEndian2Float($byteword) { + return getid3_lib::BigEndian2Float(strrev($byteword)); + } + + + function BigEndian2Float($byteword) { + // ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic + // http://www.psc.edu/general/software/packages/ieee/ieee.html + // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html + + $bitword = getid3_lib::BigEndian2Bin($byteword); + $signbit = $bitword{0}; + + switch (strlen($byteword) * 8) { + case 32: + $exponentbits = 8; + $fractionbits = 23; + break; + + case 64: + $exponentbits = 11; + $fractionbits = 52; + break; + + case 80: + // 80-bit Apple SANE format + // http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/ + $exponentstring = substr($bitword, 1, 15); + $isnormalized = intval($bitword{16}); + $fractionstring = substr($bitword, 17, 63); + $exponent = pow(2, getid3_lib::Bin2Dec($exponentstring) - 16383); + $fraction = $isnormalized + getid3_lib::DecimalBinary2Float($fractionstring); + $floatvalue = $exponent * $fraction; + if ($signbit == '1') { + $floatvalue *= -1; + } + return $floatvalue; + break; + + default: + return false; + break; + } + $exponentstring = substr($bitword, 1, $exponentbits); + $fractionstring = substr($bitword, $exponentbits + 1, $fractionbits); + $exponent = getid3_lib::Bin2Dec($exponentstring); + $fraction = getid3_lib::Bin2Dec($fractionstring); + + if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) { + // Not a Number + $floatvalue = false; + } elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) { + if ($signbit == '1') { + $floatvalue = '-infinity'; + } else { + $floatvalue = '+infinity'; + } + } elseif (($exponent == 0) && ($fraction == 0)) { + if ($signbit == '1') { + $floatvalue = -0; + } else { + $floatvalue = 0; + } + $floatvalue = ($signbit ? 0 : -0); + } elseif (($exponent == 0) && ($fraction != 0)) { + // These are 'unnormalized' values + $floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * getid3_lib::DecimalBinary2Float($fractionstring); + if ($signbit == '1') { + $floatvalue *= -1; + } + } elseif ($exponent != 0) { + $floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + getid3_lib::DecimalBinary2Float($fractionstring)); + if ($signbit == '1') { + $floatvalue *= -1; + } + } + return (float) $floatvalue; + } + + + function BigEndian2Int($byteword, $synchsafe=false, $signed=false) { + $intvalue = 0; + $bytewordlen = strlen($byteword); + for ($i = 0; $i < $bytewordlen; $i++) { + if ($synchsafe) { // disregard MSB, effectively 7-bit bytes + $intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); + } else { + $intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i)); + } + } + if ($signed && !$synchsafe) { + // synchsafe ints are not allowed to be signed + switch ($bytewordlen) { + case 1: + case 2: + case 3: + case 4: + $signmaskbit = 0x80 << (8 * ($bytewordlen - 1)); + if ($intvalue & $signmaskbit) { + $intvalue = 0 - ($intvalue & ($signmaskbit - 1)); + } + break; + + default: + die('ERROR: Cannot have signed integers larger than 32-bits in getid3_lib::BigEndian2Int()'); + break; + } + } + return getid3_lib::CastAsInt($intvalue); + } + + + function LittleEndian2Int($byteword, $signed=false) { + return getid3_lib::BigEndian2Int(strrev($byteword), false, $signed); + } + + + function BigEndian2Bin($byteword) { + $binvalue = ''; + $bytewordlen = strlen($byteword); + for ($i = 0; $i < $bytewordlen; $i++) { + $binvalue .= str_pad(decbin(ord($byteword{$i})), 8, '0', STR_PAD_LEFT); + } + return $binvalue; + } + + + function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) { + if ($number < 0) { + return false; + } + $maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF); + $intstring = ''; + if ($signed) { + if ($minbytes > 4) { + die('ERROR: Cannot have signed integers larger than 32-bits in getid3_lib::BigEndian2String()'); + } + $number = $number & (0x80 << (8 * ($minbytes - 1))); + } + while ($number != 0) { + $quotient = ($number / ($maskbyte + 1)); + $intstring = chr(ceil(($quotient - floor($quotient)) * $maskbyte)).$intstring; + $number = floor($quotient); + } + return str_pad($intstring, $minbytes, "\x00", STR_PAD_LEFT); + } + + + function Dec2Bin($number) { + while ($number >= 256) { + $bytes[] = (($number / 256) - (floor($number / 256))) * 256; + $number = floor($number / 256); + } + $bytes[] = $number; + $binstring = ''; + for ($i = 0; $i < count($bytes); $i++) { + $binstring = (($i == count($bytes) - 1) ? decbin($bytes[$i]) : str_pad(decbin($bytes[$i]), 8, '0', STR_PAD_LEFT)).$binstring; + } + return $binstring; + } + + + function Bin2Dec($binstring, $signed=false) { + $signmult = 1; + if ($signed) { + if ($binstring{0} == '1') { + $signmult = -1; + } + $binstring = substr($binstring, 1); + } + $decvalue = 0; + for ($i = 0; $i < strlen($binstring); $i++) { + $decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i); + } + return getid3_lib::CastAsInt($decvalue * $signmult); + } + + + function Bin2String($binstring) { + // return 'hi' for input of '0110100001101001' + $string = ''; + $binstringreversed = strrev($binstring); + for ($i = 0; $i < strlen($binstringreversed); $i += 8) { + $string = chr(getid3_lib::Bin2Dec(strrev(substr($binstringreversed, $i, 8)))).$string; + } + return $string; + } + + + function LittleEndian2String($number, $minbytes=1, $synchsafe=false) { + $intstring = ''; + while ($number > 0) { + if ($synchsafe) { + $intstring = $intstring.chr($number & 127); + $number >>= 7; + } else { + $intstring = $intstring.chr($number & 255); + $number >>= 8; + } + } + return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT); + } + + + function array_merge_clobber($array1, $array2) { + // written by kcØhireability*com + // taken from http://www.php.net/manual/en/function.array-merge-recursive.php + if (!is_array($array1) || !is_array($array2)) { + return false; + } + $newarray = $array1; + foreach ($array2 as $key => $val) { + if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { + $newarray[$key] = getid3_lib::array_merge_clobber($newarray[$key], $val); + } else { + $newarray[$key] = $val; + } + } + return $newarray; + } + + + function array_merge_noclobber($array1, $array2) { + if (!is_array($array1) || !is_array($array2)) { + return false; + } + $newarray = $array1; + foreach ($array2 as $key => $val) { + if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) { + $newarray[$key] = getid3_lib::array_merge_noclobber($newarray[$key], $val); + } elseif (!isset($newarray[$key])) { + $newarray[$key] = $val; + } + } + return $newarray; + } + + + function fileextension($filename, $numextensions=1) { + if (strstr($filename, '.')) { + $reversedfilename = strrev($filename); + $offset = 0; + for ($i = 0; $i < $numextensions; $i++) { + $offset = strpos($reversedfilename, '.', $offset + 1); + if ($offset === false) { + return ''; + } + } + return strrev(substr($reversedfilename, 0, $offset)); + } + return ''; + } + + + function PlaytimeString($playtimeseconds) { + $contentseconds = round((($playtimeseconds / 60) - floor($playtimeseconds / 60)) * 60); + $contentminutes = floor($playtimeseconds / 60); + if ($contentseconds >= 60) { + $contentseconds -= 60; + $contentminutes++; + } + return intval($contentminutes).':'.str_pad($contentseconds, 2, 0, STR_PAD_LEFT); + } + + + function image_type_to_mime_type($imagetypeid) { + // only available in PHP v4.3.0+ + static $image_type_to_mime_type = array(); + if (empty($image_type_to_mime_type)) { + $image_type_to_mime_type[1] = 'image/gif'; // GIF + $image_type_to_mime_type[2] = 'image/jpeg'; // JPEG + $image_type_to_mime_type[3] = 'image/png'; // PNG + $image_type_to_mime_type[4] = 'application/x-shockwave-flash'; // Flash + $image_type_to_mime_type[5] = 'image/psd'; // PSD + $image_type_to_mime_type[6] = 'image/bmp'; // BMP + $image_type_to_mime_type[7] = 'image/tiff'; // TIFF: little-endian (Intel) + $image_type_to_mime_type[8] = 'image/tiff'; // TIFF: big-endian (Motorola) + //$image_type_to_mime_type[9] = 'image/jpc'; // JPC + //$image_type_to_mime_type[10] = 'image/jp2'; // JPC + //$image_type_to_mime_type[11] = 'image/jpx'; // JPC + //$image_type_to_mime_type[12] = 'image/jb2'; // JPC + $image_type_to_mime_type[13] = 'application/x-shockwave-flash'; // Shockwave + $image_type_to_mime_type[14] = 'image/iff'; // IFF + } + return (isset($image_type_to_mime_type[$imagetypeid]) ? $image_type_to_mime_type[$imagetypeid] : 'application/octet-stream'); + } + + + function DateMac2Unix($macdate) { + // Macintosh timestamp: seconds since 00:00h January 1, 1904 + // UNIX timestamp: seconds since 00:00h January 1, 1970 + return getid3_lib::CastAsInt($macdate - 2082844800); + } + + + function FixedPoint8_8($rawdata) { + return getid3_lib::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (getid3_lib::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8)); + } + + + function FixedPoint16_16($rawdata) { + return getid3_lib::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (getid3_lib::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16)); + } + + + function FixedPoint2_30($rawdata) { + $binarystring = getid3_lib::BigEndian2Bin($rawdata); + return getid3_lib::Bin2Dec(substr($binarystring, 0, 2)) + (float) (getid3_lib::Bin2Dec(substr($binarystring, 2, 30)) / 1073741824); + } + + + function CreateDeepArray($ArrayPath, $Separator, $Value) { + // assigns $Value to a nested array path: + // $foo = getid3_lib::CreateDeepArray('/path/to/my', '/', 'file.txt') + // is the same as: + // $foo = array('path'=>array('to'=>'array('my'=>array('file.txt')))); + // or + // $foo['path']['to']['my'] = 'file.txt'; + while ($ArrayPath{0} == $Separator) { + $ArrayPath = substr($ArrayPath, 1); + } + if (($pos = strpos($ArrayPath, $Separator)) !== false) { + $ReturnedArray[substr($ArrayPath, 0, $pos)] = getid3_lib::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value); + } else { + $ReturnedArray[$ArrayPath] = $Value; + } + return $ReturnedArray; + } + + function array_max($arraydata, $returnkey=false) { + $maxvalue = false; + $maxkey = false; + foreach ($arraydata as $key => $value) { + if (!is_array($value)) { + if ($value > $maxvalue) { + $maxvalue = $value; + $maxkey = $key; + } + } + } + return ($returnkey ? $maxkey : $maxvalue); + } + + function array_min($arraydata, $returnkey=false) { + $minvalue = false; + $minkey = false; + foreach ($arraydata as $key => $value) { + if (!is_array($value)) { + if ($value > $minvalue) { + $minvalue = $value; + $minkey = $key; + } + } + } + return ($returnkey ? $minkey : $minvalue); + } + + + function md5_file($file) { + + // md5_file() exists in PHP 4.2.0+. + if (function_exists('md5_file')) { + return md5_file($file); + } + + if (GETID3_OS_ISWINDOWS) { + + $RequiredFiles = array('cygwin1.dll', 'md5sum.exe'); + foreach ($RequiredFiles as $required_file) { + if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { + die(implode(' and ', $RequiredFiles).' are required in '.GETID3_HELPERAPPSDIR.' for getid3_lib::md5_file() to function under Windows in PHP < v4.2.0'); + } + } + $commandline = GETID3_HELPERAPPSDIR.'md5sum.exe "'.str_replace('/', GETID3_OS_DIRSLASH, $file).'"'; + if (ereg("^[\\]?([0-9a-f]{32})", strtolower(`$commandline`), $r)) { + return $r[1]; + } + + } else { + + // The following works under UNIX only + $file = str_replace('`', '\\`', $file); + if (ereg("^([0-9a-f]{32})[ \t\n\r]", `md5sum "$file"`, $r)) { + return $r[1]; + } + + } + return false; + } + + + function sha1_file($file) { + + // sha1_file() exists in PHP 4.3.0+. + if (function_exists('sha1_file')) { + return sha1_file($file); + } + + $file = str_replace('`', '\\`', $file); + + if (GETID3_OS_ISWINDOWS) { + + $RequiredFiles = array('cygwin1.dll', 'sha1sum.exe'); + foreach ($RequiredFiles as $required_file) { + if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { + die(implode(' and ', $RequiredFiles).' are required in '.GETID3_HELPERAPPSDIR.' for getid3_lib::sha1_file() to function under Windows in PHP < v4.3.0'); + } + } + $commandline = GETID3_HELPERAPPSDIR.'sha1sum.exe "'.str_replace('/', GETID3_OS_DIRSLASH, $file).'"'; + if (ereg("^sha1=([0-9a-f]{40})", strtolower(`$commandline`), $r)) { + return $r[1]; + } + + } else { + + $commandline = 'sha1sum "'.$file.'"'; + if (ereg("^([0-9a-f]{40})[ \t\n\r]", strtolower(`$commandline`), $r)) { + return $r[1]; + } + + } + + return false; + } + + + // Allan Hansen <ahØartemis*dk> + // getid3_lib::md5_data() - returns md5sum for a file from startuing position to absolute end position + function hash_data($file, $offset, $end, $algorithm) { + + switch ($algorithm) { + case 'md5': + $hash_function = 'md5_file'; + $unix_call = 'md5sum'; + $windows_call = 'md5sum.exe'; + $hash_length = 32; + break; + + case 'sha1': + $hash_function = 'sha1_file'; + $unix_call = 'sha1sum'; + $windows_call = 'sha1sum.exe'; + $hash_length = 40; + break; + + default: + die('Invalid algorithm ('.$algorithm.') in getid3_lib::hash_data()'); + break; + } + $size = $end - $offset; + while (true) { + if (GETID3_OS_ISWINDOWS) { + + // It seems that sha1sum.exe for Windows only works on physical files, does not accept piped data + // Fall back to create-temp-file method: + if ($algorithm == 'sha1') { + break; + } + + $RequiredFiles = array('cygwin1.dll', 'head.exe', 'tail.exe', $windows_call); + foreach ($RequiredFiles as $required_file) { + if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { + // helper apps not available - fall back to old method + break; + } + } + $commandline = GETID3_HELPERAPPSDIR.'head.exe -c '.$end.' "'.str_replace('/', GETID3_OS_DIRSLASH, $file).'" | '; + $commandline .= GETID3_HELPERAPPSDIR.'tail.exe -c '.$size.' | '; + $commandline .= GETID3_HELPERAPPSDIR.$windows_call; + + } else { + + $commandline = 'head -c '.$end.' "'.$file.'" | '; + $commandline .= 'tail -c '.$size.' | '; + $commandline .= $unix_call; + + } + if ((bool) ini_get('safe_mode')) { + $ThisFileInfo['warning'][] = 'PHP running in Safe Mode - backtick operator not available, using slower non-system-call '.$algorithm.' algorithm'; + break; + } + return substr(`$commandline`, 0, $hash_length); + } + + // try to create a temporary file in the system temp directory - invalid dirname should force to system temp dir + if (($data_filename = tempnam('*', 'getID3')) === false) { + // can't find anywhere to create a temp file, just die + return false; + } + + // Init + $result = false; + + // copy parts of file + if ($fp = @fopen($file, 'rb')) { + + if ($fp_data = @fopen($data_filename, 'wb')) { + + fseek($fp, $offset, SEEK_SET); + $byteslefttowrite = $end - $offset; + while (($byteslefttowrite > 0) && ($buffer = fread($fp, GETID3_FREAD_BUFFER_SIZE))) { + $byteswritten = fwrite($fp_data, $buffer, $byteslefttowrite); + $byteslefttowrite -= $byteswritten; + } + fclose($fp_data); + $result = getid3_lib::$hash_function($data_filename); + + } + fclose($fp); + } + unlink($data_filename); + return $result; + } + + + function iconv_fallback_int_utf8($charval) { + if ($charval < 128) { + // 0bbbbbbb + $newcharstring = chr($charval); + } elseif ($charval < 2048) { + // 110bbbbb 10bbbbbb + $newcharstring = chr(($charval >> 6) | 0xC0); + $newcharstring .= chr(($charval & 0x3F) | 0x80); + } elseif ($charval < 65536) { + // 1110bbbb 10bbbbbb 10bbbbbb + $newcharstring = chr(($charval >> 12) | 0xE0); + $newcharstring .= chr(($charval >> 6) | 0xC0); + $newcharstring .= chr(($charval & 0x3F) | 0x80); + } else { + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + $newcharstring = chr(($charval >> 18) | 0xF0); + $newcharstring .= chr(($charval >> 12) | 0xC0); + $newcharstring .= chr(($charval >> 6) | 0xC0); + $newcharstring .= chr(($charval & 0x3F) | 0x80); + } + return $newcharstring; + } + + // ISO-8859-1 => UTF-8 + function iconv_fallback_iso88591_utf8($string, $bom=false) { + if (function_exists('utf8_encode')) { + return utf8_encode($string); + } + // utf8_encode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support) + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xEF\xBB\xBF"; + } + for ($i = 0; $i < strlen($string); $i++) { + $charval = ord($string{$i}); + $newcharstring .= getid3_lib::iconv_fallback_int_utf8($charval); + } + return $newcharstring; + } + + // ISO-8859-1 => UTF-16BE + function iconv_fallback_iso88591_utf16be($string, $bom=false) { + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xFE\xFF"; + } + for ($i = 0; $i < strlen($string); $i++) { + $newcharstring .= "\x00".$string{$i}; + } + return $newcharstring; + } + + // ISO-8859-1 => UTF-16LE + function iconv_fallback_iso88591_utf16le($string, $bom=false) { + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xFF\xFE"; + } + for ($i = 0; $i < strlen($string); $i++) { + $newcharstring .= $string{$i}."\x00"; + } + return $newcharstring; + } + + // ISO-8859-1 => UTF-16LE (BOM) + function iconv_fallback_iso88591_utf16($string) { + return getid3_lib::iconv_fallback_iso88591_utf16le($string, true); + } + + // UTF-8 => ISO-8859-1 + function iconv_fallback_utf8_iso88591($string) { + if (function_exists('utf8_decode')) { + return utf8_decode($string); + } + // utf8_decode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support) + $newcharstring = ''; + $offset = 0; + $stringlength = strlen($string); + while ($offset < $stringlength) { + if ((ord($string{$offset}) & 0x07) == 0xF7) { + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & + ((ord($string{($offset + 1)}) & 0x3F) << 12) & + ((ord($string{($offset + 2)}) & 0x3F) << 6) & + (ord($string{($offset + 3)}) & 0x3F); + $offset += 4; + } elseif ((ord($string{$offset}) & 0x0F) == 0xEF) { + // 1110bbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & + ((ord($string{($offset + 1)}) & 0x3F) << 6) & + (ord($string{($offset + 2)}) & 0x3F); + $offset += 3; + } elseif ((ord($string{$offset}) & 0x1F) == 0xDF) { + // 110bbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & + (ord($string{($offset + 1)}) & 0x3F); + $offset += 2; + } elseif ((ord($string{$offset}) & 0x7F) == 0x7F) { + // 0bbbbbbb + $charval = ord($string{$offset}); + $offset += 1; + } else { + // error? throw some kind of warning here? + $charval = false; + $offset += 1; + } + if ($charval !== false) { + $newcharstring .= (($charval < 256) ? chr($charval) : '?'); + } + } + return $newcharstring; + } + + // UTF-8 => UTF-16BE + function iconv_fallback_utf8_utf16be($string, $bom=false) { + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xFE\xFF"; + } + $offset = 0; + $stringlength = strlen($string); + while ($offset < $stringlength) { + if ((ord($string{$offset}) & 0x07) == 0xF7) { + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & + ((ord($string{($offset + 1)}) & 0x3F) << 12) & + ((ord($string{($offset + 2)}) & 0x3F) << 6) & + (ord($string{($offset + 3)}) & 0x3F); + $offset += 4; + } elseif ((ord($string{$offset}) & 0x0F) == 0xEF) { + // 1110bbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & + ((ord($string{($offset + 1)}) & 0x3F) << 6) & + (ord($string{($offset + 2)}) & 0x3F); + $offset += 3; + } elseif ((ord($string{$offset}) & 0x1F) == 0xDF) { + // 110bbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & + (ord($string{($offset + 1)}) & 0x3F); + $offset += 2; + } elseif ((ord($string{$offset}) & 0x7F) == 0x7F) { + // 0bbbbbbb + $charval = ord($string{$offset}); + $offset += 1; + } else { + // error? throw some kind of warning here? + $charval = false; + $offset += 1; + } + if ($charval !== false) { + $newcharstring .= (($charval < 65536) ? getid3_lib::BigEndian2String($charval, 2) : "\x00".'?'); + } + } + return $newcharstring; + } + + // UTF-8 => UTF-16LE + function iconv_fallback_utf8_utf16le($string, $bom=false) { + $newcharstring = ''; + if ($bom) { + $newcharstring .= "\xFF\xFE"; + } + $offset = 0; + $stringlength = strlen($string); + while ($offset < $stringlength) { + if ((ord($string{$offset}) & 0x07) == 0xF7) { + // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) & + ((ord($string{($offset + 1)}) & 0x3F) << 12) & + ((ord($string{($offset + 2)}) & 0x3F) << 6) & + (ord($string{($offset + 3)}) & 0x3F); + $offset += 4; + } elseif ((ord($string{$offset}) & 0x0F) == 0xEF) { + // 1110bbbb 10bbbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) & + ((ord($string{($offset + 1)}) & 0x3F) << 6) & + (ord($string{($offset + 2)}) & 0x3F); + $offset += 3; + } elseif ((ord($string{$offset}) & 0x1F) == 0xDF) { + // 110bbbbb 10bbbbbb + $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) & + (ord($string{($offset + 1)}) & 0x3F); + $offset += 2; + } elseif ((ord($string{$offset}) & 0x7F) == 0x7F) { + // 0bbbbbbb + $charval = ord($string{$offset}); + $offset += 1; + } else { + // error? maybe throw some warning here? + $charval = false; + $offset += 1; + } + if ($charval !== false) { + $newcharstring .= (($charval < 65536) ? getid3_lib::LittleEndian2String($charval, 2) : '?'."\x00"); + } + } + return $newcharstring; + } + + // UTF-8 => UTF-16LE (BOM) + function iconv_fallback_utf8_utf16($string) { + return getid3_lib::iconv_fallback_utf8_utf16le($string, true); + } + + // UTF-16BE => UTF-8 + function iconv_fallback_utf16be_utf8($string) { + if (substr($string, 0, 2) == "\xFE\xFF") { + // strip BOM + $string = substr($string, 2); + } + $newcharstring = ''; + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = getid3_lib::BigEndian2Int(substr($string, $i, 2)); + $newcharstring .= getid3_lib::iconv_fallback_int_utf8($charval); + } + return $newcharstring; + } + + // UTF-16LE => UTF-8 + function iconv_fallback_utf16le_utf8($string) { + if (substr($string, 0, 2) == "\xFF\xFE") { + // strip BOM + $string = substr($string, 2); + } + $newcharstring = ''; + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = getid3_lib::LittleEndian2Int(substr($string, $i, 2)); + $newcharstring .= getid3_lib::iconv_fallback_int_utf8($charval); + } + return $newcharstring; + } + + // UTF-16BE => ISO-8859-1 + function iconv_fallback_utf16be_iso88591($string) { + if (substr($string, 0, 2) == "\xFE\xFF") { + // strip BOM + $string = substr($string, 2); + } + $newcharstring = ''; + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = getid3_lib::BigEndian2Int(substr($string, $i, 2)); + $newcharstring .= (($charval < 256) ? chr($charval) : '?'); + } + return $newcharstring; + } + + // UTF-16LE => ISO-8859-1 + function iconv_fallback_utf16le_iso88591($string) { + if (substr($string, 0, 2) == "\xFF\xFE") { + // strip BOM + $string = substr($string, 2); + } + $newcharstring = ''; + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = getid3_lib::LittleEndian2Int(substr($string, $i, 2)); + $newcharstring .= (($charval < 256) ? chr($charval) : '?'); + } + return $newcharstring; + } + + // UTF-16 (BOM) => ISO-8859-1 + function iconv_fallback_utf16_iso88591($string) { + $bom = substr($string, 0, 2); + if ($bom == "\xFE\xFF") { + return getid3_lib::iconv_fallback_utf16be_iso88591(substr($string, 2)); + } elseif ($bom == "\xFF\xFE") { + return getid3_lib::iconv_fallback_utf16le_iso88591(substr($string, 2)); + } + return $string; + } + + // UTF-16 (BOM) => UTF-8 + function iconv_fallback_utf16_utf8($string) { + $bom = substr($string, 0, 2); + if ($bom == "\xFE\xFF") { + return getid3_lib::iconv_fallback_utf16be_utf8(substr($string, 2)); + } elseif ($bom == "\xFF\xFE") { + return getid3_lib::iconv_fallback_utf16le_utf8(substr($string, 2)); + } + return $string; + } + + function iconv_fallback($in_charset, $out_charset, $string) { + + if ($in_charset == $out_charset) { + return $string; + } + + static $iconv_broken_or_unavailable = array(); + if (is_null(@$iconv_broken_or_unavailable[$in_charset.'_'.$out_charset])) { + $GETID3_ICONV_TEST_STRING = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'; + + // Check iconv() + if (function_exists('iconv')) { + if (@iconv($in_charset, 'ISO-8859-1', @iconv('ISO-8859-1', $in_charset, $GETID3_ICONV_TEST_STRING)) == $GETID3_ICONV_TEST_STRING) { + if (@iconv($out_charset, 'ISO-8859-1', @iconv('ISO-8859-1', $out_charset, $GETID3_ICONV_TEST_STRING)) == $GETID3_ICONV_TEST_STRING) { + // everything works, use iconv() + $iconv_broken_or_unavailable[$in_charset.'_'.$out_charset] = false; + } else { + // iconv() available, but broken. Use getID3()'s iconv_fallback() conversions instead + // known issue in PHP v4.1.x + $iconv_broken_or_unavailable[$in_charset.'_'.$out_charset] = true; + } + } else { + // iconv() available, but broken. Use getID3()'s iconv_fallback() conversions instead + // known issue in PHP v4.1.x + $iconv_broken_or_unavailable[$in_charset.'_'.$out_charset] = true; + } + } else { + // iconv() unavailable, use getID3()'s iconv_fallback() conversions + $iconv_broken_or_unavailable[$in_charset.'_'.$out_charset] = true; + } + } + + if ($iconv_broken_or_unavailable[$in_charset.'_'.$out_charset]) { + static $ConversionFunctionList = array(); + if (empty($ConversionFunctionList)) { + $ConversionFunctionList['ISO-8859-1']['UTF-8'] = 'iconv_fallback_iso88591_utf8'; + $ConversionFunctionList['ISO-8859-1']['UTF-16'] = 'iconv_fallback_iso88591_utf16'; + $ConversionFunctionList['ISO-8859-1']['UTF-16BE'] = 'iconv_fallback_iso88591_utf16be'; + $ConversionFunctionList['ISO-8859-1']['UTF-16LE'] = 'iconv_fallback_iso88591_utf16le'; + $ConversionFunctionList['UTF-8']['ISO-8859-1'] = 'iconv_fallback_utf8_iso88591'; + $ConversionFunctionList['UTF-8']['UTF-16'] = 'iconv_fallback_utf8_utf16'; + $ConversionFunctionList['UTF-8']['UTF-16BE'] = 'iconv_fallback_utf8_utf16be'; + $ConversionFunctionList['UTF-8']['UTF-16LE'] = 'iconv_fallback_utf8_utf16le'; + $ConversionFunctionList['UTF-16']['ISO-8859-1'] = 'iconv_fallback_utf16_iso88591'; + $ConversionFunctionList['UTF-16']['UTF-8'] = 'iconv_fallback_utf16_utf8'; + $ConversionFunctionList['UTF-16LE']['ISO-8859-1'] = 'iconv_fallback_utf16le_iso88591'; + $ConversionFunctionList['UTF-16LE']['UTF-8'] = 'iconv_fallback_utf16le_utf8'; + $ConversionFunctionList['UTF-16BE']['ISO-8859-1'] = 'iconv_fallback_utf16be_iso88591'; + $ConversionFunctionList['UTF-16BE']['UTF-8'] = 'iconv_fallback_utf16be_utf8'; + } + if (isset($ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)])) { + $ConversionFunction = $ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)]; + return getid3_lib::$ConversionFunction($string); + } + die('PHP does not have iconv() support - cannot convert from '.$in_charset.' to '.$out_charset); + } + + if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) { + switch ($out_charset) { + case 'ISO-8859-1': + $converted_string = rtrim($converted_string, "\x00"); + break; + } + return $converted_string; + } + + // iconv() may sometimes fail with "illegal character in input string" error message + // and return an empty string, but returning the unconverted string is more useful + return $string; + } + + + function MultiByteCharString2HTML($string, $charset='ISO-8859-1') { + $HTMLstring = ''; + + switch ($charset) { + case 'ISO-8859-1': + case 'ISO8859-1': + case 'ISO-8859-15': + case 'ISO8859-15': + case 'cp866': + case 'ibm866': + case '866': + case 'cp1251': + case 'Windows-1251': + case 'win-1251': + case '1251': + case 'cp1252': + case 'Windows-1252': + case '1252': + case 'KOI8-R': + case 'koi8-ru': + case 'koi8r': + case 'BIG5': + case '950': + case 'GB2312': + case '936': + case 'BIG5-HKSCS': + case 'Shift_JIS': + case 'SJIS': + case '932': + case 'EUC-JP': + case 'EUCJP': + $HTMLstring = htmlentities($string, ENT_COMPAT, $charset); + break; + + case 'UTF-8': + $strlen = strlen($string); + for ($i = 0; $i < $strlen; $i++) { + $char_ord_val = ord($string{$i}); + $charval = 0; + if ($char_ord_val < 0x80) { + $charval = $char_ord_val; + } elseif ((($char_ord_val & 0xF0) >> 4) == 0x0F) { + $charval = (($char_ord_val & 0x07) << 18); + $charval += ((ord($string{++$i}) & 0x3F) << 12); + $charval += ((ord($string{++$i}) & 0x3F) << 6); + $charval += (ord($string{++$i}) & 0x3F); + } elseif ((($char_ord_val & 0xE0) >> 5) == 0x07) { + $charval = (($char_ord_val & 0x0F) << 12); + $charval += ((ord($string{++$i}) & 0x3F) << 6); + $charval += (ord($string{++$i}) & 0x3F); + } elseif ((($char_ord_val & 0xC0) >> 6) == 0x03) { + $charval = (($char_ord_val & 0x1F) << 6); + $charval += (ord($string{++$i}) & 0x3F); + } + if (($charval >= 32) && ($charval <= 127)) { + $HTMLstring .= chr($charval); + } else { + $HTMLstring .= '&#'.$charval.';'; + } + } + break; + + case 'UTF-16LE': + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = getid3_lib::LittleEndian2Int(substr($string, $i, 2)); + if (($charval >= 32) && ($charval <= 127)) { + $HTMLstring .= chr($charval); + } else { + $HTMLstring .= '&#'.$charval.';'; + } + } + break; + + case 'UTF-16BE': + for ($i = 0; $i < strlen($string); $i += 2) { + $charval = getid3_lib::BigEndian2Int(substr($string, $i, 2)); + if (($charval >= 32) && ($charval <= 127)) { + $HTMLstring .= chr($charval); + } else { + $HTMLstring .= '&#'.$charval.';'; + } + } + break; + + default: + $HTMLstring = 'ERROR: Character set "'.$charset.'" not supported in MultiByteCharString2HTML()'; + break; + } + return $HTMLstring; + } + + + + function RGADnameLookup($namecode) { + static $RGADname = array(); + if (empty($RGADname)) { + $RGADname[0] = 'not set'; + $RGADname[1] = 'Track Gain Adjustment'; + $RGADname[2] = 'Album Gain Adjustment'; + } + + return (isset($RGADname[$namecode]) ? $RGADname[$namecode] : ''); + } + + + function RGADoriginatorLookup($originatorcode) { + static $RGADoriginator = array(); + if (empty($RGADoriginator)) { + $RGADoriginator[0] = 'unspecified'; + $RGADoriginator[1] = 'pre-set by artist/producer/mastering engineer'; + $RGADoriginator[2] = 'set by user'; + $RGADoriginator[3] = 'determined automatically'; + } + + return (isset($RGADoriginator[$originatorcode]) ? $RGADoriginator[$originatorcode] : ''); + } + + + function RGADadjustmentLookup($rawadjustment, $signbit) { + $adjustment = $rawadjustment / 10; + if ($signbit == 1) { + $adjustment *= -1; + } + return (float) $adjustment; + } + + + function RGADgainString($namecode, $originatorcode, $replaygain) { + if ($replaygain < 0) { + $signbit = '1'; + } else { + $signbit = '0'; + } + $storedreplaygain = intval(round($replaygain * 10)); + $gainstring = str_pad(decbin($namecode), 3, '0', STR_PAD_LEFT); + $gainstring .= str_pad(decbin($originatorcode), 3, '0', STR_PAD_LEFT); + $gainstring .= $signbit; + $gainstring .= str_pad(decbin($storedreplaygain), 9, '0', STR_PAD_LEFT); + + return $gainstring; + } + + function RGADamplitude2dB($amplitude) { + return 20 * log10($amplitude); + } + + + function GetURLImageSize($urlpic) { + if ($fd = @fopen($urlpic, 'rb')){ + $imgData = fread($fd, filesize($urlpic)); + fclose($fd); + return getid3_lib::GetDataImageSize($imgData); + } + return array('', '', ''); + } + + + function GetDataImageSize($imgData) { + $height = ''; + $width = ''; + $type = ''; + if ((substr($imgData, 0, 3) == GETID3_GIF_SIG) && (strlen($imgData) > 10)) { + $dim = unpack('v2dim', substr($imgData, 6, 4)); + $width = $dim['dim1']; + $height = $dim['dim2']; + $type = 1; + } elseif ((substr($imgData, 0, 8) == GETID3_PNG_SIG) && (strlen($imgData) > 24)) { + $dim = unpack('N2dim', substr($imgData, 16, 8)); + $width = $dim['dim1']; + $height = $dim['dim2']; + $type = 3; + } elseif ((substr($imgData, 0, 3) == GETID3_JPG_SIG) && (strlen($imgData) > 4)) { + ///////////////// JPG CHUNK SCAN //////////////////// + $imgPos = 2; + $type = 2; + $buffer = strlen($imgData) - 2; + while ($imgPos < strlen($imgData)) { + // synchronize to the marker 0xFF + $imgPos = strpos($imgData, 0xFF, $imgPos) + 1; + $marker = $imgData[$imgPos]; + do { + $marker = ord($imgData[$imgPos++]); + } while ($marker == 255); + // find dimensions of block + switch (chr($marker)) { + // Grab width/height from SOF segment (these are acceptable chunk types) + case GETID3_JPG_SOF0: + case GETID3_JPG_SOF1: + case GETID3_JPG_SOF2: + case GETID3_JPG_SOF3: + case GETID3_JPG_SOF5: + case GETID3_JPG_SOF6: + case GETID3_JPG_SOF7: + case GETID3_JPG_SOF9: + case GETID3_JPG_SOF10: + case GETID3_JPG_SOF11: + case GETID3_JPG_SOF13: + case GETID3_JPG_SOF14: + case GETID3_JPG_SOF15: + $dim = unpack('n2dim', substr($imgData, $imgPos + 3, 4)); + $height = $dim['dim1']; + $width = $dim['dim2']; + break 2; // found it so exit + case GETID3_JPG_EOI: + case GETID3_JPG_SOS: + return false; // End loop in case we find one of these markers + default: // We're not interested in other markers + $skiplen = (ord($imgData[$imgPos++]) << 8) + ord($imgData[$imgPos++]) - 2; + // if the skip is more than what we've read in, read more + $buffer -= $skiplen; + if ($buffer < 512) { // if the buffer of data is too low, read more file. + // $imgData .= fread($fd, $skiplen + 1024); + // $buffer += $skiplen + 1024; + return false; // End loop in case we find run out of data + } + $imgPos += $skiplen; + break; + } // endswitch check marker type + } // endif loop through JPG chunks + } // endif chk for valid file types + + return array($width, $height, $type); + } // end function + + + function ImageTypesLookup($imagetypeid) { + static $ImageTypesLookup = array(); + if (empty($ImageTypesLookup)) { + $ImageTypesLookup[1] = 'gif'; + $ImageTypesLookup[2] = 'jpeg'; + $ImageTypesLookup[3] = 'png'; + $ImageTypesLookup[4] = 'swf'; + $ImageTypesLookup[5] = 'psd'; + $ImageTypesLookup[6] = 'bmp'; + $ImageTypesLookup[7] = 'tiff (little-endian)'; + $ImageTypesLookup[8] = 'tiff (big-endian)'; + $ImageTypesLookup[9] = 'jpc'; + $ImageTypesLookup[10] = 'jp2'; + $ImageTypesLookup[11] = 'jpx'; + $ImageTypesLookup[12] = 'jb2'; + $ImageTypesLookup[13] = 'swc'; + $ImageTypesLookup[14] = 'iff'; + } + return (isset($ImageTypesLookup[$imagetypeid]) ? $ImageTypesLookup[$imagetypeid] : ''); + } + + function CopyTagsToComments(&$ThisFileInfo) { + // Copy all entries from ['tags'] into common ['comments'] and ['comments_html'] + if (!empty($ThisFileInfo['tags'])) { + foreach ($ThisFileInfo['tags'] as $tagtype => $tagarray) { + foreach ($tagarray as $tagname => $tagdata) { + foreach ($tagdata as $key => $value) { + if (!empty($value)) { + if (empty($ThisFileInfo['comments'][$tagname])) { + + // fall through and append value + + } elseif ($tagtype == 'id3v1') { + + $newvaluelength = strlen(trim($value)); + foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { + $oldvaluelength = strlen(trim($existingvalue)); + if (($newvaluelength <= $oldvaluelength) && (substr($existingvalue, 0, $newvaluelength) == trim($value))) { + // new value is identical but shorter-than (or equal-length to) one already in comments - skip + break 2; + } + } + + } else { + + $newvaluelength = strlen(trim($value)); + foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) { + $oldvaluelength = strlen(trim($existingvalue)); + if (($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) { + $ThisFileInfo['comments'][$tagname][$existingkey] = trim($value); + break 2; + } + } + + } + if (empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) { + $ThisFileInfo['comments'][$tagname][] = trim($value); + if (isset($ThisFileInfo['tags_html'][$tagtype][$tagname][$key])) { + $ThisFileInfo['comments_html'][$tagname][] = $ThisFileInfo['tags_html'][$tagtype][$tagname][$key]; + } + } + } + } + } + } + } + } + + + function EmbeddedLookup($key, $begin, $end, $file, $name) { + + // Cached + static $cache; + if (isset($cache[$file][$name])) { + return @$cache[$file][$name][$key]; + } + + // Init + $keylength = strlen($key); + $line_count = $end - $begin - 7; + + // Open php file + $fp = fopen($file, 'r'); + + // Discard $begin lines + for ($i = 0; $i < ($begin + 3); $i++) { + fgets($fp, 1024); + } + + // Loop thru line + while (0 < $line_count--) { + + // Read line + $line = ltrim(fgets($fp, 1024), "\t "); + + // METHOD A: only cache the matching key - less memory but slower on next lookup of not-previously-looked-up key + //$keycheck = substr($line, 0, $keylength); + //if ($key == $keycheck) { + // $cache[$file][$name][$keycheck] = substr($line, $keylength + 1); + // break; + //} + + // METHOD B: cache all keys in this lookup - more memory but faster on next lookup of not-previously-looked-up key + //$cache[$file][$name][substr($line, 0, $keylength)] = trim(substr($line, $keylength + 1)); + @list($ThisKey, $ThisValue) = explode("\t", $line, 2); + $cache[$file][$name][$ThisKey] = trim($ThisValue); + } + + // Close and return + fclose($fp); + return @$cache[$file][$name][$key]; + } + + function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) { + global $GETID3_ERRORARRAY; + + if (file_exists($filename)) { + if (@include_once($filename)) { + return true; + } else { + $diemessage = basename($sourcefile).' depends on '.$filename.', which has errors'; + } + } else { + $diemessage = basename($sourcefile).' depends on '.$filename.', which is missing'; + } + if ($DieOnFailure) { + die($diemessage); + } else { + $GETID3_ERRORARRAY[] = $diemessage; + } + return false; + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/getid3.php b/modules/id3/getid3/getid3.php new file mode 100644 index 00000000..2cbd5002 --- /dev/null +++ b/modules/id3/getid3/getid3.php @@ -0,0 +1,1259 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// Please see readme.txt for more information // +// /// +///////////////////////////////////////////////////////////////// + +// Defines +define('GETID3_VERSION', '1.7.3'); +define('GETID3_FREAD_BUFFER_SIZE', 16384); // read buffer size in bytes + + + +class getID3 +{ + // public: Settings + var $encoding = 'ISO-8859-1'; // CASE SENSITIVE! - i.e. (must be supported by iconv()) + // Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE + + var $encoding_id3v1 = 'ISO-8859-1'; // Should always be 'ISO-8859-1', but some tags may be written + // in other encodings such as 'EUC-CN' + + // public: Optional tag checks - disable for speed. + var $option_tag_id3v1 = true; // Read and process ID3v1 tags + var $option_tag_id3v2 = true; // Read and process ID3v2 tags + var $option_tag_lyrics3 = true; // Read and process Lyrics3 tags + var $option_tag_apetag = true; // Read and process APE tags + var $option_tags_process = true; // Copy tags to root key 'tags' and encode to $this->encoding + var $option_tags_html = true; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities + + // public: Optional tag/comment calucations + var $option_extra_info = true; // Calculate additional info such as bitrate, channelmode etc + + // public: Optional calculations + var $option_md5_data = false; // Get MD5 sum of data part - slow + var $option_md5_data_source = false; // Use MD5 of source file if availble - only FLAC and OptimFROG + var $option_sha1_data = false; // Get SHA1 sum of data part - slow + var $option_max_2gb_check = true; // Check whether file is larger than 2 Gb and thus not supported by PHP + + // private + var $filename; + + + // public: constructor + function getID3() + { + + $this->startup_error = ''; + $this->startup_warning = ''; + + // Check for PHP version >= 4.1.0 + if (phpversion() < '4.1.0') { + $this->startup_error .= 'getID3() requires PHP v4.1.0 or higher - you are running v'.phpversion(); + } + + // Check memory + $memory_limit = ini_get('memory_limit'); + if (eregi('([0-9]+)M', $memory_limit, $matches)) { + // could be stored as "16M" rather than 16777216 for example + $memory_limit = $matches[1] * 1048576; + } + if ($memory_limit <= 0) { + // memory limits probably disabled + } elseif ($memory_limit <= 3145728) { + $this->startup_error .= 'PHP has less than 3MB available memory and will very likely run out. Increase memory_limit in php.ini'; + } elseif ($memory_limit <= 12582912) { + $this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'; + } + + // Check safe_mode off + if ((bool) ini_get('safe_mode')) { + $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.'); + } + + + // define a constant rather than looking up every time it is needed + if (!defined('GETID3_OS_ISWINDOWS')) { + if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') { + define('GETID3_OS_ISWINDOWS', true); + } else { + define('GETID3_OS_ISWINDOWS', false); + } + } + + // Get base path of getID3() - ONCE + if (!defined('GETID3_INCLUDEPATH')) { + define('GETID3_OS_DIRSLASH', (GETID3_OS_ISWINDOWS ? '\\' : '/')); + + foreach (get_included_files() as $key => $val) { + if (basename($val) == 'getid3.php') { + define('GETID3_INCLUDEPATH', dirname($val).GETID3_OS_DIRSLASH); + break; + } + } + } + + // Load support library + if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) { + $this->startup_error .= 'getid3.lib.php is missing or corrupt'; + } + + + // Needed for Windows only: + // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC + // as well as other helper functions such as head, tail, md5sum, etc + // IMPORTANT: This path cannot have spaces in it. If neccesary, use the 8dot3 equivalent + // ie for "C:/Program Files/Apache/" put "C:/PROGRA~1/APACHE/" + // IMPORTANT: This path must include the trailing slash + if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) { + + $helperappsdir = GETID3_INCLUDEPATH.'..'.GETID3_OS_DIRSLASH.'helperapps'; // must not have any space in this path + + if (!is_dir($helperappsdir)) { + $this->startup_error .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'; + } elseif (strpos(realpath($helperappsdir), ' ') !== false) { + $DirPieces = explode(GETID3_OS_DIRSLASH, realpath($helperappsdir)); + foreach ($DirPieces as $key => $value) { + if ((strpos($value, '.') !== false) && (strpos($value, ' ') === false)) { + if (strpos($value, '.') > 8) { + $value = substr($value, 0, 6).'~1'; + } + } elseif ((strpos($value, ' ') !== false) || strlen($value) > 8) { + $value = substr($value, 0, 6).'~1'; + } + $DirPieces[$key] = strtoupper($value); + } + $this->startup_error .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary (on this server that would be something like "'.implode(GETID3_OS_DIRSLASH, $DirPieces).'" - NOTE: this may or may not be the actual 8.3 equivalent of "'.$helperappsdir.'", please double-check). You can run "dir /x" from the commandline to see the correct 8.3-style names. You need to edit the file "'.GETID3_INCLUDEPATH.'/getid3.php" around line '.(__LINE__ - 16); + } + define('GETID3_HELPERAPPSDIR', realpath($helperappsdir).GETID3_OS_DIRSLASH); + } + + } + + + + // public: analyze file - replaces GetAllFileInfo() and GetTagOnly() + function analyze($filename) { + + if (!empty($this->startup_error)) { + return $this->error($this->startup_error); + } + if (!empty($this->startup_warning)) { + $this->warning($this->startup_warning); + } + + // init result array and set parameters + $this->info = array(); + $this->info['GETID3_VERSION'] = GETID3_VERSION; + + // Check encoding/iconv support + if (!function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) { + $errormessage = 'iconv() support is needed for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. '; + if (GETID3_OS_ISWINDOWS) { + $errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32'; + } else { + $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch'; + } + return $this->error($errormessage); + } + + // Disable magic_quotes_runtime, if neccesary + $old_magic_quotes_runtime = get_magic_quotes_runtime(); // store current setting of magic_quotes_runtime + if ($old_magic_quotes_runtime) { + set_magic_quotes_runtime(0); // turn off magic_quotes_runtime + if (get_magic_quotes_runtime()) { + return $this->error('Could not disable magic_quotes_runtime - getID3() cannot work properly with this setting enabled'); + } + } + + // remote files not supported + if (preg_match('/^(ht|f)tp:\/\//', $filename)) { + return $this->error('Remote files are not supported in this version of getID3() - please copy the file locally first'); + } + + // open local file + if (!$fp = @fopen($filename, 'rb')) { + return $this->error('Could not open file "'.$filename.'"'); + } + + // set parameters + $this->info['filesize'] = filesize($filename); + + // option_max_2gb_check + if ($this->option_max_2gb_check) { + // PHP doesn't support integers larger than 31-bit (~2GB) + // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize + // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer + fseek($fp, 0, SEEK_END); + if ((($this->info['filesize'] != 0) && (ftell($fp) == 0)) || + ($this->info['filesize'] < 0) || + (ftell($fp) < 0)) { + unset($this->info['filesize']); + fclose($fp); + return $this->error('File is most likely larger than 2GB and is not supported by PHP'); + } + } + + // set more parameters + $this->info['avdataoffset'] = 0; + $this->info['avdataend'] = $this->info['filesize']; + $this->info['fileformat'] = ''; // filled in later + $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used + $this->info['video']['dataformat'] = ''; // filled in later, unset if not used + $this->info['tags'] = array(); // filled in later, unset if not used + $this->info['error'] = array(); // filled in later, unset if not used + $this->info['warning'] = array(); // filled in later, unset if not used + $this->info['comments'] = array(); // filled in later, unset if not used + $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired + + // set redundant parameters - might be needed in some include file + $this->info['filename'] = basename($filename); + $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename))); + $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename']; + + + // handle ID3v2 tag - done first - already at beginning of file + // ID3v2 detection (even if not parsing) is always done otherwise fileformat is much harder to detect + if ($this->option_tag_id3v2) { + + $GETID3_ERRORARRAY = &$this->info['warning']; + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, false)) { + $tag = new getid3_id3v2($fp, $this->info); + } + + } else { + + fseek($fp, 0, SEEK_SET); + $header = fread($fp, 10); + if (substr($header, 0, 3) == 'ID3') { + $this->info['id3v2']['header'] = true; + $this->info['id3v2']['majorversion'] = ord($header{3}); + $this->info['id3v2']['minorversion'] = ord($header{4}); + $this->info['id3v2']['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length + + $this->info['id3v2']['tag_offset_start'] = 0; + $this->info['id3v2']['tag_offset_end'] = $this->info['id3v2']['tag_offset_start'] + $this->info['id3v2']['headerlength']; + $this->info['avdataoffset'] = $this->info['id3v2']['tag_offset_end']; + } + + } + + + // handle ID3v1 tag + if ($this->option_tag_id3v1) { + if (!@include_once(GETID3_INCLUDEPATH.'module.tag.id3v1.php')) { + return $this->error('module.tag.id3v1.php is missing - you may disable option_tag_id3v1.'); + } + $tag = new getid3_id3v1($fp, $this->info); + } + + // handle APE tag + if ($this->option_tag_apetag) { + if (!@include_once(GETID3_INCLUDEPATH.'module.tag.apetag.php')) { + return $this->error('module.tag.apetag.php is missing - you may disable option_tag_apetag.'); + } + $tag = new getid3_apetag($fp, $this->info); + } + + // handle lyrics3 tag + if ($this->option_tag_lyrics3) { + if (!@include_once(GETID3_INCLUDEPATH.'module.tag.lyrics3.php')) { + return $this->error('module.tag.lyrics3.php is missing - you may disable option_tag_lyrics3.'); + } + $tag = new getid3_lyrics3($fp, $this->info); + } + + // read 32 kb file data + fseek($fp, $this->info['avdataoffset'], SEEK_SET); + $formattest = fread($fp, 32774); + + // determine format + $determined_format = $this->GetFileFormat($formattest, $filename); + + // unable to determine file format + if (!$determined_format) { + fclose($fp); + return $this->error('unable to determine file format'); + } + + // check for illegal ID3 tags + if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { + if ($determined_format['fail_id3'] === 'ERROR') { + fclose($fp); + return $this->error('ID3 tags not allowed on this file type.'); + } elseif ($determined_format['fail_id3'] === 'WARNING') { + $this->info['warning'][] = 'ID3 tags not allowed on this file type.'; + } + } + + // check for illegal APE tags + if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { + if ($determined_format['fail_ape'] === 'ERROR') { + fclose($fp); + return $this->error('APE tags not allowed on this file type.'); + } elseif ($determined_format['fail_ape'] === 'WARNING') { + $this->info['warning'][] = 'APE tags not allowed on this file type.'; + } + } + + // set mime type + $this->info['mime_type'] = $determined_format['mime_type']; + + // supported format signature pattern detected, but module deleted + if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) { + fclose($fp); + return $this->error('Format not supported, module, '.$determined_format['include'].', was removed.'); + } + + // module requires iconv support + if (!function_exists('iconv') && @$determined_format['iconv_req']) { + return $this->error('iconv support is required for this module ('.$determined_format['include'].').'); + } + + // include module + include_once(GETID3_INCLUDEPATH.$determined_format['include']); + + // instantiate module class + $class_name = 'getid3_'.$determined_format['module']; + if (!class_exists($class_name)) { + return $this->error('Format not supported, module, '.$determined_format['include'].', is corrupt.'); + } + if (isset($determined_format['option'])) { + $class = new $class_name($fp, $this->info, $determined_format['option']); + } else { + $class = new $class_name($fp, $this->info); + } + + // close file + fclose($fp); + + // process all tags - copy to 'tags' and convert charsets + if ($this->option_tags_process) { + $this->HandleAllTags(); + } + + // perform more calculations + if ($this->option_extra_info) { + $this->ChannelsBitratePlaytimeCalculations(); + $this->CalculateCompressionRatioVideo(); + $this->CalculateCompressionRatioAudio(); + $this->CalculateReplayGain(); + $this->ProcessAudioStreams(); + } + + // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags + if ($this->option_md5_data) { + // do not cald md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too + if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { + $this->getHashdata('md5'); + } + } + + // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags + if ($this->option_sha1_data) { + $this->getHashdata('sha1'); + } + + // remove undesired keys + $this->CleanUp(); + + // restore magic_quotes_runtime setting + set_magic_quotes_runtime($old_magic_quotes_runtime); + + // return info array + return $this->info; + } + + + // private: error handling + function error($message) { + + $this->CleanUp(); + + $this->info['error'][] = $message; + return $this->info; + } + + + // private: warning handling + function warning($message) { + $this->info['warning'][] = $message; + return true; + } + + + // private: CleanUp + function CleanUp() { + + // remove possible empty keys + $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams'); + foreach ($AVpossibleEmptyKeys as $key) { + if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) { + unset($this->info['audio'][$key]); + } + if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) { + unset($this->info['video'][$key]); + } + } + + // remove empty root keys + if (!empty($this->info)) { + foreach ($this->info as $key => $value) { + if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) { + unset($this->info[$key]); + } + } + } + + // remove meaningless entries from unknown-format files + if (empty($this->info['fileformat'])) { + if (isset($this->info['avdataoffset'])) { + unset($this->info['avdataoffset']); + } + if (isset($this->info['avdataend'])) { + unset($this->info['avdataend']); + } + } + } + + + // return array containing information about all supported formats + function GetFileFormatArray() { + static $format_info = array(); + if (empty($format_info)) { + $format_info = array( + + // Audio formats + + // AC-3 - audio - Dolby AC-3 / Dolby Digital + 'ac3' => array( + 'pattern' => '^\x0B\x77', + 'group' => 'audio', + 'module' => 'ac3', + 'mime_type' => 'audio/ac3', + ), + + // AAC - audio - Advanced Audio Coding (AAC) - ADIF format + 'adif' => array( + 'pattern' => '^ADIF', + 'group' => 'audio', + 'module' => 'aac', + 'option' => 'adif', + 'mime_type' => 'application/octet-stream', + 'fail_ape' => 'WARNING', + ), + + + // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3) + 'adts' => array( + 'pattern' => '^\xFF[\xF0-\xF1\xF8-\xF9]', + 'group' => 'audio', + 'module' => 'aac', + 'option' => 'adts', + 'mime_type' => 'application/octet-stream', + 'fail_ape' => 'WARNING', + ), + + + // AU - audio - NeXT/Sun AUdio (AU) + 'au' => array( + 'pattern' => '^\.snd', + 'group' => 'audio', + 'module' => 'au', + 'mime_type' => 'audio/basic', + ), + + // AVR - audio - Audio Visual Research + 'avr' => array( + 'pattern' => '^2BIT', + 'group' => 'audio', + 'module' => 'avr', + 'mime_type' => 'application/octet-stream', + ), + + // BONK - audio - Bonk v0.9+ + 'bonk' => array( + 'pattern' => '^\x00(BONK|INFO|META| ID3)', + 'group' => 'audio', + 'module' => 'bonk', + 'mime_type' => 'audio/xmms-bonk', + ), + + // FLAC - audio - Free Lossless Audio Codec + 'flac' => array( + 'pattern' => '^fLaC', + 'group' => 'audio', + 'module' => 'flac', + 'mime_type' => 'audio/x-flac', + ), + + // LA - audio - Lossless Audio (LA) + 'la' => array( + 'pattern' => '^LA0[2-4]', + 'group' => 'audio', + 'module' => 'la', + 'mime_type' => 'application/octet-stream', + ), + + // LPAC - audio - Lossless Predictive Audio Compression (LPAC) + 'lpac' => array( + 'pattern' => '^LPAC', + 'group' => 'audio', + 'module' => 'lpac', + 'mime_type' => 'application/octet-stream', + ), + + // MIDI - audio - MIDI (Musical Instrument Digital Interface) + 'midi' => array( + 'pattern' => '^MThd', + 'group' => 'audio', + 'module' => 'midi', + 'mime_type' => 'audio/midi', + ), + + // MAC - audio - Monkey's Audio Compressor + 'mac' => array( + 'pattern' => '^MAC ', + 'group' => 'audio', + 'module' => 'monkey', + 'mime_type' => 'application/octet-stream', + ), + + // MOD - audio - MODule (assorted sub-formats) + 'mod' => array( + 'pattern' => '^.{1080}(M.K.|[5-9]CHN|[1-3][0-9]CH)', + 'group' => 'audio', + 'module' => 'mod', + 'option' => 'mod', + 'mime_type' => 'audio/mod', + ), + + // MOD - audio - MODule (Impulse Tracker) + 'it' => array( + 'pattern' => '^IMPM', + 'group' => 'audio', + 'module' => 'mod', + 'option' => 'it', + 'mime_type' => 'audio/it', + ), + + // MOD - audio - MODule (eXtended Module, various sub-formats) + 'xm' => array( + 'pattern' => '^Extended Module', + 'group' => 'audio', + 'module' => 'mod', + 'option' => 'xm', + 'mime_type' => 'audio/xm', + ), + + // MOD - audio - MODule (ScreamTracker) + 's3m' => array( + 'pattern' => '^.{44}SCRM', + 'group' => 'audio', + 'module' => 'mod', + 'option' => 's3m', + 'mime_type' => 'audio/s3m', + ), + + // MPC - audio - Musepack / MPEGplus + 'mpc' => array( + 'pattern' => '^(MP\+|[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0])', + 'group' => 'audio', + 'module' => 'mpc', + 'mime_type' => 'application/octet-stream', + ), + + // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS) + 'mp3' => array( + 'pattern' => '^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]', + 'group' => 'audio', + 'module' => 'mp3', + 'mime_type' => 'audio/mpeg', + ), + + // OFR - audio - OptimFROG + 'ofr' => array( + 'pattern' => '^(\*RIFF|OFR)', + 'group' => 'audio', + 'module' => 'optimfrog', + 'mime_type' => 'application/octet-stream', + ), + + // RKAU - audio - RKive AUdio compressor + 'rkau' => array( + 'pattern' => '^RKA', + 'group' => 'audio', + 'module' => 'rkau', + 'mime_type' => 'application/octet-stream', + ), + + // SHN - audio - Shorten + 'shn' => array( + 'pattern' => '^ajkg', + 'group' => 'audio', + 'module' => 'shorten', + 'mime_type' => 'audio/xmms-shn', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // TTA - audio - TTA Lossless Audio Compressor (http://tta.corecodec.org) + 'tta' => array( + 'pattern' => '^TTA', // could also be '^TTA(\x01|\x02|\x03|2|1)' + 'group' => 'audio', + 'module' => 'tta', + 'mime_type' => 'application/octet-stream', + ), + + // VOC - audio - Creative Voice (VOC) + 'voc' => array( + 'pattern' => '^Creative Voice File', + 'group' => 'audio', + 'module' => 'voc', + 'mime_type' => 'audio/voc', + ), + + // VQF - audio - transform-domain weighted interleave Vector Quantization Format (VQF) + 'vqf' => array( + 'pattern' => '^TWIN', + 'group' => 'audio', + 'module' => 'vqf', + 'mime_type' => 'application/octet-stream', + ), + + // WV - audio - WavPack (v4.0+) + 'wv' => array( + 'pattern' => '^wvpk', + 'group' => 'audio', + 'module' => 'wavpack', + 'mime_type' => 'application/octet-stream', + ), + + + // Audio-Video formats + + // ASF - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio + 'asf' => array( + 'pattern' => '^\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C', + 'group' => 'audio-video', + 'module' => 'asf', + 'mime_type' => 'video/x-ms-asf', + 'iconv_req' => false, + ), + + // BINK - audio/video - Bink / Smacker + 'bink' => array( + 'pattern' => '^(BIK|SMK)', + 'group' => 'audio-video', + 'module' => 'bink', + 'mime_type' => 'application/octet-stream', + ), + + // MKAV - audio/video - Mastroka + 'matroska' => array( + 'pattern' => '^\x1A\x45\xDF\xA3', + 'group' => 'audio-video', + 'module' => 'matroska', + 'mime_type' => 'application/octet-stream', + ), + + // MPEG - audio/video - MPEG (Moving Pictures Experts Group) + 'mpeg' => array( + 'pattern' => '^\x00\x00\x01(\xBA|\xB3)', + 'group' => 'audio-video', + 'module' => 'mpeg', + 'mime_type' => 'video/mpeg', + ), + + // NSV - audio/video - Nullsoft Streaming Video (NSV) + 'nsv' => array( + 'pattern' => '^NSV[sf]', + 'group' => 'audio-video', + 'module' => 'nsv', + 'mime_type' => 'application/octet-stream', + ), + + // Ogg - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*)) + 'ogg' => array( + 'pattern' => '^OggS', + 'group' => 'audio', + 'module' => 'ogg', + 'mime_type' => 'application/ogg', + 'fail_id3' => 'WARNING', + 'fail_ape' => 'WARNING', + ), + + // QT - audio/video - Quicktime + 'quicktime' => array( + 'pattern' => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)', + 'group' => 'audio-video', + 'module' => 'quicktime', + 'mime_type' => 'video/quicktime', + ), + + // RIFF - audio/video - Resource Interchange File Format (RIFF) / WAV / AVI / CD-audio / SDSS = renamed variant used by SmartSound QuickTracks (www.smartsound.com) / FORM = Audio Interchange File Format (AIFF) + 'riff' => array( + 'pattern' => '^(RIFF|SDSS|FORM)', + 'group' => 'audio-video', + 'module' => 'riff', + 'mime_type' => 'audio/x-wave', + 'fail_ape' => 'WARNING', + ), + + // Real - audio/video - RealAudio, RealVideo + 'real' => array( + 'pattern' => '^(\.RMF|.ra)', + 'group' => 'audio-video', + 'module' => 'real', + 'mime_type' => 'audio/x-realaudio', + ), + + // SWF - audio/video - ShockWave Flash + 'swf' => array( + 'pattern' => '^(F|C)WS', + 'group' => 'audio-video', + 'module' => 'swf', + 'mime_type' => 'application/x-shockwave-flash', + ), + + + // Still-Image formats + + // BMP - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4) + 'bmp' => array( + 'pattern' => '^BM', + 'group' => 'graphic', + 'module' => 'bmp', + 'mime_type' => 'image/bmp', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // GIF - still image - Graphics Interchange Format + 'gif' => array( + 'pattern' => '^GIF', + 'group' => 'graphic', + 'module' => 'gif', + 'mime_type' => 'image/gif', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // JPEG - still image - Joint Photographic Experts Group (JPEG) + 'jpg' => array( + 'pattern' => '^\xFF\xD8\xFF', + 'group' => 'graphic', + 'module' => 'jpg', + 'mime_type' => 'image/jpeg', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // PCD - still image - Kodak Photo CD + 'pcd' => array( + 'pattern' => '^.{2048}PCD_IPI\x00', + 'group' => 'graphic', + 'module' => 'pcd', + 'mime_type' => 'image/x-photo-cd', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // PNG - still image - Portable Network Graphics (PNG) + 'png' => array( + 'pattern' => '^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A', + 'group' => 'graphic', + 'module' => 'png', + 'mime_type' => 'image/png', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // TIFF - still image - Tagged Information File Format (TIFF) + 'tiff' => array( + 'pattern' => '^(II\x2A\x00|MM\x00\x2A)', + 'group' => 'graphic', + 'module' => 'tiff', + 'mime_type' => 'image/tiff', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + + // Data formats + + // ISO - data - International Standards Organization (ISO) CD-ROM Image + 'iso' => array( + 'pattern' => '^.{32769}CD001', + 'group' => 'misc', + 'module' => 'iso', + 'mime_type' => 'application/octet-stream', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + 'iconv_req' => false, + ), + + // RAR - data - RAR compressed data + 'rar' => array( + 'pattern' => '^Rar\!', + 'group' => 'archive', + 'module' => 'rar', + 'mime_type' => 'application/octet-stream', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // SZIP - audio - SZIP compressed data + 'szip' => array( + 'pattern' => '^SZ\x0A\x04', + 'group' => 'archive', + 'module' => 'szip', + 'mime_type' => 'application/octet-stream', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // TAR - data - TAR compressed data + 'tar' => array( + 'pattern' => '^.{100}[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20\x00]{12}[0-9\x20\x00]{12}', + 'group' => 'archive', + 'module' => 'tar', + 'mime_type' => 'application/x-tar', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // GZIP - data - GZIP compressed data + 'gz' => array( + 'pattern' => '^\x1F\x8B\x08', + 'group' => 'archive', + 'module' => 'gzip', + 'mime_type' => 'application/x-gzip', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ), + + // ZIP - data - ZIP compressed data + 'zip' => array( + 'pattern' => '^PK\x03\x04', + 'group' => 'archive', + 'module' => 'zip', + 'mime_type' => 'application/zip', + 'fail_id3' => 'ERROR', + 'fail_ape' => 'ERROR', + ) + ); + } + + return $format_info; + } + + + + function GetFileFormat(&$filedata, $filename='') { + // this function will determine the format of a file based on usually + // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG, + // and in the case of ISO CD image, 6 bytes offset 32kb from the start + // of the file). + + // Identify file format - loop through $format_info and detect with reg expr + foreach ($this->GetFileFormatArray() as $format_name => $info) { + // Using preg_match() instead of ereg() - much faster + // The /s switch on preg_match() forces preg_match() NOT to treat + // newline (0x0A) characters as special chars but do a binary match + if (preg_match('/'.$info['pattern'].'/s', $filedata)) { + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; + return $info; + } + } + + + if (preg_match('/\.mp[123a]$/i', $filename)) { + + // Too many mp3 encoders on the market put gabage in front of mpeg files + // use assume format on these if format detection failed + $GetFileFormatArray = $this->GetFileFormatArray(); + $info = $GetFileFormatArray['mp3']; + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; + return $info; + + //} elseif (preg_match('/\.tar$/i', $filename)) { + // + // // TAR files don't have any useful header to work from + // // TAR - data - TAR compressed data + // $info = array( + // 'pattern' => '^.{512}', + // 'group' => 'archive', + // 'module' => 'tar', + // 'mime_type' => 'application/octet-stream', + // 'fail_id3' => 'ERROR', + // 'fail_ape' => 'ERROR', + // ); + // $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; + // return $info; + + } + + return false; + } + + + // converts array to $encoding charset from $this->encoding + function CharConvert(&$array, $encoding) { + + // identical encoding - end here + if ($encoding == $this->encoding) { + return; + } + + // loop thru array + foreach ($array as $key => $value) { + + // go recursive + if (is_array($value)) { + $this->CharConvert($array[$key], $encoding); + } + + // convert string + elseif (is_string($value)) { + $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value)); + } + } + } + + + function HandleAllTags() { + + // key name => array (tag name, character encoding) + static $tags; + if (empty($tags)) { + $tags = array( + 'asf' => array('asf' , 'UTF-16LE'), + 'midi' => array('midi' , 'ISO-8859-1'), + 'nsv' => array('nsv' , 'ISO-8859-1'), + 'ogg' => array('vorbiscomment' , 'UTF-8'), + 'png' => array('png' , 'UTF-8'), + 'tiff' => array('tiff' , 'ISO-8859-1'), + 'quicktime' => array('quicktime' , 'ISO-8859-1'), + 'real' => array('real' , 'ISO-8859-1'), + 'vqf' => array('vqf' , 'ISO-8859-1'), + 'zip' => array('zip' , 'ISO-8859-1'), + 'riff' => array('riff' , 'ISO-8859-1'), + 'lyrics3' => array('lyrics3' , 'ISO-8859-1'), + 'id3v1' => array('id3v1' , $this->encoding_id3v1), + 'id3v2' => array('id3v2' , 'UTF-8'), // not according to the specs (every frame can have a different encoding), but getID3() force-converts all encodings to UTF-8 + 'ape' => array('ape' , 'UTF-8') + ); + } + + // loop thru comments array + foreach ($tags as $comment_name => $tagname_encoding_array) { + list($tag_name, $encoding) = $tagname_encoding_array; + + // fill in default encoding type if not already present + if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) { + $this->info[$comment_name]['encoding'] = $encoding; + } + + // copy comments if key name set + if (!empty($this->info[$comment_name]['comments'])) { + + foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) { + foreach ($valuearray as $key => $value) { + if (strlen(trim($value)) > 0) { + $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; // do not trim!! Unicode characters will get mangled if trailing nulls are removed! + } + } + } + + if (!isset($this->info['tags'][$tag_name])) { + // comments are set but contain nothing but empty strings, so skip + continue; + } + + if ($this->option_tags_html) { + foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) { + foreach ($valuearray as $key => $value) { + if (is_string($value)) { + //$this->info['tags_html'][$tag_name][$tag_key][$key] = getid3_lib::MultiByteCharString2HTML($value, $encoding); + $this->info['tags_html'][$tag_name][$tag_key][$key] = str_replace('�', '', getid3_lib::MultiByteCharString2HTML($value, $encoding)); + } else { + $this->info['tags_html'][$tag_name][$tag_key][$key] = $value; + } + } + } + } + + $this->CharConvert($this->info['tags'][$tag_name], $encoding); // only copy gets converted! + } + + } + return true; + } + + + function getHashdata($algorithm) { + switch ($algorithm) { + case 'md5': + case 'sha1': + break; + + default: + return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()'); + break; + } + + if ((@$this->info['fileformat'] == 'ogg') && (@$this->info['audio']['dataformat'] == 'vorbis')) { + + // We cannot get an identical md5_data value for Ogg files where the comments + // span more than 1 Ogg page (compared to the same audio data with smaller + // comments) using the normal getID3() method of MD5'ing the data between the + // end of the comments and the end of the file (minus any trailing tags), + // because the page sequence numbers of the pages that the audio data is on + // do not match. Under normal circumstances, where comments are smaller than + // the nominal 4-8kB page size, then this is not a problem, but if there are + // very large comments, the only way around it is to strip off the comment + // tags with vorbiscomment and MD5 that file. + // This procedure must be applied to ALL Ogg files, not just the ones with + // comments larger than 1 page, because the below method simply MD5's the + // whole file with the comments stripped, not just the portion after the + // comments block (which is the standard getID3() method. + + // The above-mentioned problem of comments spanning multiple pages and changing + // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but + // currently vorbiscomment only works on OggVorbis files. + + if ((bool) ini_get('safe_mode')) { + + $this->info['warning'][] = 'Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)'; + $this->info[$algorithm.'_data'] = false; + + } else { + + // Prevent user from aborting script + $old_abort = ignore_user_abort(true); + + // Create empty file + $empty = tempnam('*', 'getID3'); + touch($empty); + + + // Use vorbiscomment to make temp file without comments + $temp = tempnam('*', 'getID3'); + $file = $this->info['filenamepath']; + + if (GETID3_OS_ISWINDOWS) { + + if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) { + + $commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"'; + $VorbisCommentError = `$commandline`; + + } else { + + $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR; + + } + + } else { + + $commandline = 'vorbiscomment -w -c "'.$empty.'" "'.$file.'" "'.$temp.'" 2>&1'; + $VorbisCommentError = `$commandline`; + + } + + if (!empty($VorbisCommentError)) { + + $this->info['warning'][] = 'Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError; + $this->info[$algorithm.'_data'] = false; + + } else { + + // Get hash of newly created file + switch ($algorithm) { + case 'md5': + $this->info[$algorithm.'_data'] = getid3_lib::md5_file($temp); + break; + + case 'sha1': + $this->info[$algorithm.'_data'] = getid3_lib::sha1_file($temp); + break; + } + } + + // Clean up + unlink($empty); + unlink($temp); + + // Reset abort setting + ignore_user_abort($old_abort); + + } + + } else { + + if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) { + + // get hash from part of file + $this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm); + + } else { + + // get hash from whole file + switch ($algorithm) { + case 'md5': + $this->info[$algorithm.'_data'] = getid3_lib::md5_file($this->info['filenamepath']); + break; + + case 'sha1': + $this->info[$algorithm.'_data'] = getid3_lib::sha1_file($this->info['filenamepath']); + break; + } + } + + } + return true; + } + + + function ChannelsBitratePlaytimeCalculations() { + + // set channelmode on audio + if (@$this->info['audio']['channels'] == '1') { + $this->info['audio']['channelmode'] = 'mono'; + } elseif (@$this->info['audio']['channels'] == '2') { + $this->info['audio']['channelmode'] = 'stereo'; + } + + // Calculate combined bitrate - audio + video + $CombinedBitrate = 0; + $CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0); + $CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0); + if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) { + $this->info['bitrate'] = $CombinedBitrate; + } + //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) { + // // for example, VBR MPEG video files cannot determine video bitrate: + // // should not set overall bitrate and playtime from audio bitrate only + // unset($this->info['bitrate']); + //} + + if (!isset($this->info['playtime_seconds']) && !empty($this->info['bitrate'])) { + $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate']; + } + + // Set playtime string + if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) { + $this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']); + } + } + + + function CalculateCompressionRatioVideo() { + if (empty($this->info['video'])) { + return false; + } + if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) { + return false; + } + if (empty($this->info['video']['bits_per_sample'])) { + return false; + } + + switch ($this->info['video']['dataformat']) { + case 'bmp': + case 'gif': + case 'jpeg': + case 'jpg': + case 'png': + case 'tiff': + $FrameRate = 1; + $PlaytimeSeconds = 1; + $BitrateCompressed = $this->info['filesize'] * 8; + break; + + default: + if (!empty($this->info['video']['frame_rate'])) { + $FrameRate = $this->info['video']['frame_rate']; + } else { + return false; + } + if (!empty($this->info['playtime_seconds'])) { + $PlaytimeSeconds = $this->info['playtime_seconds']; + } else { + return false; + } + if (!empty($this->info['video']['bitrate'])) { + $BitrateCompressed = $this->info['video']['bitrate']; + } else { + return false; + } + break; + } + $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate; + + $this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed; + return true; + } + + + function CalculateCompressionRatioAudio() { + if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate'])) { + return false; + } + $this->info['audio']['compression_ratio'] = $this->info['audio']['bitrate'] / ($this->info['audio']['channels'] * $this->info['audio']['sample_rate'] * (!empty($this->info['audio']['bits_per_sample']) ? $this->info['audio']['bits_per_sample'] : 16)); + + if (!empty($this->info['audio']['streams'])) { + foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) { + if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) { + $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16)); + } + } + } + return true; + } + + + function CalculateReplayGain() { + if (isset($this->info['replay_gain'])) { + $this->info['replay_gain']['reference_volume'] = 89; + if (isset($this->info['replay_gain']['track']['adjustment'])) { + $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment']; + } + if (isset($this->info['replay_gain']['album']['adjustment'])) { + $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment']; + } + + if (isset($this->info['replay_gain']['track']['peak'])) { + $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']); + } + if (isset($this->info['replay_gain']['album']['peak'])) { + $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']); + } + } + return true; + } + + function ProcessAudioStreams() { + if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) { + if (!isset($this->info['audio']['streams'])) { + foreach ($this->info['audio'] as $key => $value) { + if ($key != 'streams') { + $this->info['audio']['streams'][0][$key] = $value; + } + } + } + } + return true; + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.archive.gzip.php b/modules/id3/getid3/module.archive.gzip.php new file mode 100644 index 00000000..db18c329 --- /dev/null +++ b/modules/id3/getid3/module.archive.gzip.php @@ -0,0 +1,209 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.archive.gzip.php // +// written by Mike Mozolin <mmozolinØavk*ru> // +// module for analyzing GZIP files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + +class getid3_gzip { + + function getid3_gzip(&$fd, &$ThisFileInfo) { + $ThisFileInfo['fileformat'] = 'gzip'; + $this->read_gzip($fd, $ThisFileInfo); + return false; + } + + // Reads the gzip-file + function read_gzip($fd, &$ThisFileInfo) { + + $start_length = 10; + $unpack_header = 'a1id1/a1id2/a1cmethod/a1flags/a4mtime/a1xflags/a1os'; + //+---+---+---+---+---+---+---+---+---+---+ + //|ID1|ID2|CM |FLG| MTIME |XFL|OS | + //+---+---+---+---+---+---+---+---+---+---+ + @fseek($fd, 0); + $buffer = @fread($fd, $ThisFileInfo['filesize']); + + $arr_members = explode("\x1F\x8B\x08", $buffer); + $num_members = intval(count($arr_members)); + for ($i = 0; $i < $num_members; $i++) { + if (strlen($arr_members[$i]) == 0) { + continue; + } + $thisThisFileInfo = &$ThisFileInfo['gzip']['member_header'][$i]; + + $buf = "\x1F\x8B\x08".$arr_members[$i]; + + $attr = unpack($unpack_header, substr($buf, 0, $start_length)); + if (!$this->get_os_type(ord($attr['os']))) { + // Split member with previous if wrong OS type + $arr_members[$i - 1] .= $buf; + $arr_members[$i] = ''; + continue; + } + } + + $ThisFileInfo['gzip']['files'] = array(); + + $fpointer = 0; + for ($i = 0; $i < $num_members; $i++) { + + if (strlen($arr_members[$i]) == 0) { + continue; + } + $thisThisFileInfo = &$ThisFileInfo['gzip']['member_header'][$i]; + + $buff = "\x1F\x8B\x08".$arr_members[$i]; + + $attr = unpack($unpack_header, substr($buff, 0, $start_length)); + //$id1 = ord($attr['id1']); + //$id2 = ord($attr['id2']); + $cmethod = ord($attr['cmethod']); + $thisThisFileInfo['raw']['flags'] = ord($attr['flags']); + $thisThisFileInfo['flags']['crc16'] = (bool) ($thisThisFileInfo['raw']['flags'] & 0x02); + $thisThisFileInfo['flags']['extra'] = (bool) ($thisThisFileInfo['raw']['flags'] & 0x04); + $thisThisFileInfo['flags']['filename'] = (bool) ($thisThisFileInfo['raw']['flags'] & 0x08); + $thisThisFileInfo['flags']['comment'] = (bool) ($thisThisFileInfo['raw']['flags'] & 0x10); + + $thisThisFileInfo['raw']['xflag'] = ord($attr['xflags']); + $thisThisFileInfo['compression'] = $this->get_xflag_type($thisThisFileInfo['raw']['xflag']); + + $thisThisFileInfo['filemtime'] = getid3_lib::LittleEndian2Int($attr['mtime']); + + $thisThisFileInfo['raw']['os'] = ord($attr['os']); + $thisThisFileInfo['os'] = $this->get_os_type($thisThisFileInfo['raw']['os']); + if (!$thisThisFileInfo['os']) { + $ThisFileInfo['error'][] = 'Read error on gzip file'; + return false; + } + + $fpointer = 10; + $arr_xsubfield = array(); + // bit 2 - FLG.FEXTRA + //+---+---+=================================+ + //| XLEN |...XLEN bytes of "extra field"...| + //+---+---+=================================+ + if ($thisThisFileInfo['flags']['extra']) { + $w_xlen = substr($buff, $fpointer, 2); + $xlen = getid3_lib::LittleEndian2Int($w_xlen); + $fpointer += 2; + $thisThisFileInfo['raw']['xfield'] = substr($buff, $fpointer, $xlen); + // Extra SubFields + //+---+---+---+---+==================================+ + //|SI1|SI2| LEN |... LEN bytes of subfield data ...| + //+---+---+---+---+==================================+ + $idx = 0; + while (true) { + if ($idx >= $xlen) { + break; + } + $si1 = ord(substr($buff, $fpointer+$idx, 1)); + $idx++; + $si2 = ord(substr($buff, $fpointer+$idx, 1)); + $idx++; + if (($si1 == 0x41) && ($si2 == 0x70)) { + $w_xsublen = substr($buff, $fpointer+$idx, 2); + $xsublen = getid3_lib::LittleEndian2Int($w_xsublen); + $idx += 2; + $arr_xsubfield[] = substr($buff, $fpointer+$idx, $xsublen); + $idx += $xsublen; + } else { + break; + } + } + $fpointer += $xlen; + } + // bit 3 - FLG.FNAME + //+=========================================+ + //|...original file name, zero-terminated...| + //+=========================================+ + $thisThisFileInfo['filename'] = ''; + if ($thisThisFileInfo['flags']['filename']) { + while(true) { + if (ord($buff[$fpointer]) == 0) { + $fpointer++; + break; + } + $thisThisFileInfo['filename'] .= $buff[$fpointer]; + $fpointer++; + } + } + // bit 4 - FLG.FCOMMENT + //+===================================+ + //|...file comment, zero-terminated...| + //+===================================+ + if ($thisThisFileInfo['flags']['comment']) { + while(true) { + if (ord($buff[$fpointer]) == 0) { + $fpointer++; + break; + } + $thisThisFileInfo['comment'] .= $buff[$fpointer]; + $fpointer++; + } + } + // bit 1 - FLG.FHCRC + //+---+---+ + //| CRC16 | + //+---+---+ + if ($thisThisFileInfo['flags']['crc16']) { + $w_crc = substr($buff, $fpointer, 2); + $thisThisFileInfo['crc16'] = getid3_lib::LittleEndian2Int($w_crc); + $fpointer += 2; + } + // bit 0 - FLG.FTEXT + //if ($thisThisFileInfo['raw']['flags'] & 0x01) { + // echo 'FTEXT<br>'; + //} + // bits 5, 6, 7 - reserved + + $thisThisFileInfo['crc32'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 8, 4)); + $thisThisFileInfo['filesize'] = getid3_lib::LittleEndian2Int(substr($buff, strlen($buff) - 4)); + + $ThisFileInfo['gzip']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['gzip']['files'], getid3_lib::CreateDeepArray($thisThisFileInfo['filename'], '/', $thisThisFileInfo['filesize'])); + } + } + + // Converts the OS type + function get_os_type($key) { + static $os_type = array( + '0' => 'FAT filesystem (MS-DOS, OS/2, NT/Win32)', + '1' => 'Amiga', + '2' => 'VMS (or OpenVMS)', + '3' => 'Unix', + '4' => 'VM/CMS', + '5' => 'Atari TOS', + '6' => 'HPFS filesystem (OS/2, NT)', + '7' => 'Macintosh', + '8' => 'Z-System', + '9' => 'CP/M', + '10' => 'TOPS-20', + '11' => 'NTFS filesystem (NT)', + '12' => 'QDOS', + '13' => 'Acorn RISCOS', + '255' => 'unknown' + ); + return @$os_type[$key]; + } + + // Converts the eXtra FLags + function get_xflag_type($key) { + static $xflag_type = array( + '0' => 'unknown', + '2' => 'maximum compression', + '4' => 'fastest algorithm' + ); + return @$xflag_type[$key]; + } +} + +?> diff --git a/modules/id3/getid3/module.archive.rar.php b/modules/id3/getid3/module.archive.rar.php new file mode 100644 index 00000000..f006ce43 --- /dev/null +++ b/modules/id3/getid3/module.archive.rar.php @@ -0,0 +1,32 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.archive.rar.php // +// module for analyzing RAR files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_rar +{ + + function getid3_rar(&$fd, &$ThisFileInfo) { + + $ThisFileInfo['fileformat'] = 'rar'; + + $ThisFileInfo['error'][] = 'RAR parsing not enabled in this version of getID3()'; + return false; + + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.archive.szip.php b/modules/id3/getid3/module.archive.szip.php new file mode 100644 index 00000000..2513c85c --- /dev/null +++ b/modules/id3/getid3/module.archive.szip.php @@ -0,0 +1,97 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.archive.szip.php // +// module for analyzing SZIP compressed files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_szip +{ + + function getid3_szip(&$fd, &$ThisFileInfo) { + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $SZIPHeader = fread($fd, 6); + if (substr($SZIPHeader, 0, 4) != 'SZ'."\x0A\x04") { + $ThisFileInfo['error'][] = 'Expecting "SZ[x0A][x04]" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($SZIPHeader, 0, 4).'"'; + return false; + } + + $ThisFileInfo['fileformat'] = 'szip'; + + $ThisFileInfo['szip']['major_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 4, 1)); + $ThisFileInfo['szip']['minor_version'] = getid3_lib::BigEndian2Int(substr($SZIPHeader, 5, 1)); + + while (!feof($fd)) { + $NextBlockID = fread($fd, 2); + switch ($NextBlockID) { + case 'SZ': + // Note that szip files can be concatenated, this has the same effect as + // concatenating the files. this also means that global header blocks + // might be present between directory/data blocks. + fseek($fd, 4, SEEK_CUR); + break; + + case 'BH': + $BHheaderbytes = getid3_lib::BigEndian2Int(fread($fd, 3)); + $BHheaderdata = fread($fd, $BHheaderbytes); + $BHheaderoffset = 0; + while (strpos($BHheaderdata, "\x00", $BHheaderoffset) > 0) { + //filename as \0 terminated string (empty string indicates end) + //owner as \0 terminated string (empty is same as last file) + //group as \0 terminated string (empty is same as last file) + //3 byte filelength in this block + //2 byte access flags + //4 byte creation time (like in unix) + //4 byte modification time (like in unix) + //4 byte access time (like in unix) + + $BHdataArray['filename'] = substr($BHheaderdata, $BHheaderoffset, strcspn($BHheaderdata, "\x00")); + $BHheaderoffset += (strlen($BHdataArray['filename']) + 1); + + $BHdataArray['owner'] = substr($BHheaderdata, $BHheaderoffset, strcspn($BHheaderdata, "\x00")); + $BHheaderoffset += (strlen($BHdataArray['owner']) + 1); + + $BHdataArray['group'] = substr($BHheaderdata, $BHheaderoffset, strcspn($BHheaderdata, "\x00")); + $BHheaderoffset += (strlen($BHdataArray['group']) + 1); + + $BHdataArray['filelength'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 3)); + $BHheaderoffset += 3; + + $BHdataArray['access_flags'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 2)); + $BHheaderoffset += 2; + + $BHdataArray['creation_time'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4)); + $BHheaderoffset += 4; + + $BHdataArray['modification_time'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4)); + $BHheaderoffset += 4; + + $BHdataArray['access_time'] = getid3_lib::BigEndian2Int(substr($BHheaderdata, $BHheaderoffset, 4)); + $BHheaderoffset += 4; + + $ThisFileInfo['szip']['BH'][] = $BHdataArray; + } + break; + + default: + break 2; + } + } + + return true; + + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.archive.tar.php b/modules/id3/getid3/module.archive.tar.php new file mode 100644 index 00000000..8ba31ed1 --- /dev/null +++ b/modules/id3/getid3/module.archive.tar.php @@ -0,0 +1,179 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.archive.tar.php // +// written by Mike Mozolin <mmozolinØavk*ru> // +// module for analyzing TAR files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + +class getid3_tar { + + function getid3_tar(&$fd, &$ThisFileInfo) { + $ThisFileInfo['fileformat'] = 'tar'; + $this->read_tar($fd, $ThisFileInfo); + return false; + } + + // Reads the tar-file + function read_tar($fd, &$ThisFileInfo) { + + $header_length = 512; + $unpack_header = 'a100fname/a8mode/a8uid/a8gid/a12size/a12mtime/a8chksum/a1typflag/a100lnkname/a6magic/a2ver/a32uname/a32gname/a8devmaj/a8devmin/a155/prefix'; + + $null_512k = str_repeat("\0", 512); // end-of-file marker + + $ThisFileInfo['tar']['files'] = array(); + + @fseek($fd, 0); + while (!@feof($fd)) { + $buffer = @fread($fd, $header_length); + // check the block + $checksum = 0; + for ($i = 0; $i < 148; $i++) { + $checksum += ord(substr($buffer, $i, 1)); + } + for ($i = 148; $i < 156; $i++) { + $checksum += ord(' '); + } + for ($i = 156; $i < 512; $i++) { + $checksum += ord(substr($buffer, $i, 1)); + } + $attr = unpack($unpack_header, $buffer); + $name = trim($attr['fname']); + $mode = octdec(trim($attr['mode'])); + $uid = octdec(trim($attr['uid'])); + $gid = octdec(trim($attr['gid'])); + $size = octdec(trim($attr['size'])); + $mtime = octdec(trim($attr['mtime'])); + $chksum = octdec(trim($attr['chksum'])); + $typflag = trim($attr['typflag']); + $lnkname = trim($attr['lnkname']); + $magic = trim($attr['magic']); + $ver = trim($attr['ver']); + $uname = trim($attr['uname']); + $gname = trim($attr['gname']); + $devmaj = octdec(trim($attr['devmaj'])); + $devmin = octdec(trim($attr['devmin'])); + $prefix = trim(@$attr['prefix']); + // EOF Found + if (($checksum == 256) && ($chksum == 0)) { + break; + } + if ($prefix) { + $name = $prefix.'/'.$name; + } + if ((preg_match('#/$#', $name)) && !$name) { + $typeflag = 5; + } + // If it's the end of the tar-file... + if ($buffer == $null_512k) { + break; + } + // Read the next chunk + $data = @fread( $fd, $size ); + if (strlen($data) != $size) { + @fclose($fd); + $ThisFileInfo['error'][] = 'Read error on TAR file'; + return false; + } + $diff = $size % 512; + if ($diff != 0) { + // Padding, throw away + @fread($fd, (512 - $diff)); + } + // Protect against tar-files with garbage at the end + if ($name == '') { + break; + } + $ThisFileInfo['tar']['file_details'][$name] = array ( + 'name' => $name, + 'mode_raw' => $mode, + 'mode' => $this->display_perms($mode), + 'uid' => $uid, + 'gid' => $gid, + 'size' => $size, + 'mtime' => $mtime, + 'chksum' => $chksum, + 'typeflag' => $this->get_flag_type($typflag), + 'linkname' => $lnkname, + 'magic' => $magic, + 'version' => $ver, + 'uname' => $uname, + 'gname' => $gname, + 'devmajor' => $devmaj, + 'devminor' => $devmin + ); + $ThisFileInfo['tar']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['tar']['files'], getid3_lib::CreateDeepArray($ThisFileInfo['tar']['file_details'][$name]['name'], '/', $size)); + } + return true; + + } + + // Parses the file mode to file permissions + function display_perms($mode) { + // Determine Type + if ($mode & 0x1000) $type='p'; // FIFO pipe + elseif ($mode & 0x2000) $type='c'; // Character special + elseif ($mode & 0x4000) $type='d'; // Directory + elseif ($mode & 0x6000) $type='b'; // Block special + elseif ($mode & 0x8000) $type='-'; // Regular + elseif ($mode & 0xA000) $type='l'; // Symbolic Link + elseif ($mode & 0xC000) $type='s'; // Socket + else $type='u'; // UNKNOWN + + // Determine permissions + $owner['read'] = (($mode & 00400) ? 'r' : '-'); + $owner['write'] = (($mode & 00200) ? 'w' : '-'); + $owner['execute'] = (($mode & 00100) ? 'x' : '-'); + $group['read'] = (($mode & 00040) ? 'r' : '-'); + $group['write'] = (($mode & 00020) ? 'w' : '-'); + $group['execute'] = (($mode & 00010) ? 'x' : '-'); + $world['read'] = (($mode & 00004) ? 'r' : '-'); + $world['write'] = (($mode & 00002) ? 'w' : '-'); + $world['execute'] = (($mode & 00001) ? 'x' : '-'); + + // Adjust for SUID, SGID and sticky bit + if ($mode & 0x800) $owner['execute'] = ($owner['execute'] == 'x') ? 's' : 'S'; + if ($mode & 0x400) $group['execute'] = ($group['execute'] == 'x') ? 's' : 'S'; + if ($mode & 0x200) $world['execute'] = ($world['execute'] == 'x') ? 't' : 'T'; + + $s = sprintf("%1s", $type); + $s .= sprintf("%1s%1s%1s", $owner['read'], $owner['write'], $owner['execute']); + $s .= sprintf("%1s%1s%1s", $group['read'], $group['write'], $group['execute']); + $s .= sprintf("%1s%1s%1s\n", $world['read'], $world['write'], $world['execute']); + return $s; + } + + // Converts the file type + function get_flag_type($typflag) { + static $flag_types = array( + '0' => 'LF_NORMAL', + '1' => 'LF_LINK', + '2' => 'LF_SYNLINK', + '3' => 'LF_CHR', + '4' => 'LF_BLK', + '5' => 'LF_DIR', + '6' => 'LF_FIFO', + '7' => 'LF_CONFIG', + 'D' => 'LF_DUMPDIR', + 'K' => 'LF_LONGLINK', + 'L' => 'LF_LONGNAME', + 'M' => 'LF_MULTIVOL', + 'N' => 'LF_NAMES', + 'S' => 'LF_SPARSE', + 'V' => 'LF_VOLHDR' + ); + return @$flag_types[$typflag]; + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.archive.zip.php b/modules/id3/getid3/module.archive.zip.php new file mode 100644 index 00000000..285dfd9e --- /dev/null +++ b/modules/id3/getid3/module.archive.zip.php @@ -0,0 +1,415 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.archive.zip.php // +// module for analyzing pkZip files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_zip +{ + + function getid3_zip(&$fd, &$ThisFileInfo) { + + $ThisFileInfo['fileformat'] = 'zip'; + $ThisFileInfo['zip']['encoding'] = 'ISO-8859-1'; + $ThisFileInfo['zip']['files'] = array(); + + $ThisFileInfo['zip']['compressed_size'] = 0; + $ThisFileInfo['zip']['uncompressed_size'] = 0; + $ThisFileInfo['zip']['entries_count'] = 0; + + $EOCDsearchData = ''; + $EOCDsearchCounter = 0; + while ($EOCDsearchCounter++ < 512) { + + fseek($fd, -128 * $EOCDsearchCounter, SEEK_END); + $EOCDsearchData = fread($fd, 128).$EOCDsearchData; + + if (strstr($EOCDsearchData, 'PK'."\x05\x06")) { + + $EOCDposition = strpos($EOCDsearchData, 'PK'."\x05\x06"); + fseek($fd, (-128 * $EOCDsearchCounter) + $EOCDposition, SEEK_END); + $ThisFileInfo['zip']['end_central_directory'] = $this->ZIPparseEndOfCentralDirectory($fd); + + fseek($fd, $ThisFileInfo['zip']['end_central_directory']['directory_offset'], SEEK_SET); + $ThisFileInfo['zip']['entries_count'] = 0; + while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($fd)) { + $ThisFileInfo['zip']['central_directory'][] = $centraldirectoryentry; + $ThisFileInfo['zip']['entries_count']++; + $ThisFileInfo['zip']['compressed_size'] += $centraldirectoryentry['compressed_size']; + $ThisFileInfo['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size']; + + if ($centraldirectoryentry['uncompressed_size'] > 0) { + $ThisFileInfo['zip']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['zip']['files'], getid3_lib::CreateDeepArray($centraldirectoryentry['filename'], '/', $centraldirectoryentry['uncompressed_size'])); + } + } + + if ($ThisFileInfo['zip']['entries_count'] == 0) { + $ThisFileInfo['error'][] = 'No Central Directory entries found (truncated file?)'; + return false; + } + + if (!empty($ThisFileInfo['zip']['end_central_directory']['comment'])) { + $ThisFileInfo['zip']['comments']['comment'][] = $ThisFileInfo['zip']['end_central_directory']['comment']; + } + + if (isset($ThisFileInfo['zip']['central_directory'][0]['compression_method'])) { + $ThisFileInfo['zip']['compression_method'] = $ThisFileInfo['zip']['central_directory'][0]['compression_method']; + } + if (isset($ThisFileInfo['zip']['central_directory'][0]['flags']['compression_speed'])) { + $ThisFileInfo['zip']['compression_speed'] = $ThisFileInfo['zip']['central_directory'][0]['flags']['compression_speed']; + } + if (isset($ThisFileInfo['zip']['compression_method']) && ($ThisFileInfo['zip']['compression_method'] == 'store') && !isset($ThisFileInfo['zip']['compression_speed'])) { + $ThisFileInfo['zip']['compression_speed'] = 'store'; + } + + return true; + + } + + } + + if ($this->getZIPentriesFilepointer($fd, $ThisFileInfo)) { + + // central directory couldn't be found and/or parsed + // scan through actual file data entries, recover as much as possible from probable trucated file + if ($ThisFileInfo['zip']['compressed_size'] > ($ThisFileInfo['filesize'] - 46 - 22)) { + $ThisFileInfo['error'][] = 'Warning: Truncated file! - Total compressed file sizes ('.$ThisFileInfo['zip']['compressed_size'].' bytes) is greater than filesize minus Central Directory and End Of Central Directory structures ('.($ThisFileInfo['filesize'] - 46 - 22).' bytes)'; + } + $ThisFileInfo['error'][] = 'Cannot find End Of Central Directory - returned list of files in [zip][entries] array may not be complete'; + foreach ($ThisFileInfo['zip']['entries'] as $key => $valuearray) { + $ThisFileInfo['zip']['files'][$valuearray['filename']] = $valuearray['uncompressed_size']; + } + return true; + + } else { + + unset($ThisFileInfo['zip']); + $ThisFileInfo['fileformat'] = ''; + $ThisFileInfo['error'][] = 'Cannot find End Of Central Directory (truncated file?)'; + return false; + + } + } + + + function getZIPHeaderFilepointerTopDown(&$fd, &$ThisFileInfo) { + $ThisFileInfo['fileformat'] = 'zip'; + + $ThisFileInfo['zip']['compressed_size'] = 0; + $ThisFileInfo['zip']['uncompressed_size'] = 0; + $ThisFileInfo['zip']['entries_count'] = 0; + + rewind($fd); + while ($fileentry = $this->ZIPparseLocalFileHeader($fd)) { + $ThisFileInfo['zip']['entries'][] = $fileentry; + $ThisFileInfo['zip']['entries_count']++; + } + if ($ThisFileInfo['zip']['entries_count'] == 0) { + $ThisFileInfo['error'][] = 'No Local File Header entries found'; + return false; + } + + $ThisFileInfo['zip']['entries_count'] = 0; + while ($centraldirectoryentry = $this->ZIPparseCentralDirectory($fd)) { + $ThisFileInfo['zip']['central_directory'][] = $centraldirectoryentry; + $ThisFileInfo['zip']['entries_count']++; + $ThisFileInfo['zip']['compressed_size'] += $centraldirectoryentry['compressed_size']; + $ThisFileInfo['zip']['uncompressed_size'] += $centraldirectoryentry['uncompressed_size']; + } + if ($ThisFileInfo['zip']['entries_count'] == 0) { + $ThisFileInfo['error'][] = 'No Central Directory entries found (truncated file?)'; + return false; + } + + if ($EOCD = $this->ZIPparseEndOfCentralDirectory($fd)) { + $ThisFileInfo['zip']['end_central_directory'] = $EOCD; + } else { + $ThisFileInfo['error'][] = 'No End Of Central Directory entry found (truncated file?)'; + return false; + } + + if (!empty($ThisFileInfo['zip']['end_central_directory']['comment'])) { + $ThisFileInfo['zip']['comments']['comment'][] = $ThisFileInfo['zip']['end_central_directory']['comment']; + } + + return true; + } + + + function getZIPentriesFilepointer(&$fd, &$ThisFileInfo) { + $ThisFileInfo['zip']['compressed_size'] = 0; + $ThisFileInfo['zip']['uncompressed_size'] = 0; + $ThisFileInfo['zip']['entries_count'] = 0; + + rewind($fd); + while ($fileentry = $this->ZIPparseLocalFileHeader($fd)) { + $ThisFileInfo['zip']['entries'][] = $fileentry; + $ThisFileInfo['zip']['entries_count']++; + $ThisFileInfo['zip']['compressed_size'] += $fileentry['compressed_size']; + $ThisFileInfo['zip']['uncompressed_size'] += $fileentry['uncompressed_size']; + } + if ($ThisFileInfo['zip']['entries_count'] == 0) { + $ThisFileInfo['error'][] = 'No Local File Header entries found'; + return false; + } + + return true; + } + + + function ZIPparseLocalFileHeader(&$fd) { + $LocalFileHeader['offset'] = ftell($fd); + + $ZIPlocalFileHeader = fread($fd, 30); + + $LocalFileHeader['raw']['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 0, 4)); + if ($LocalFileHeader['raw']['signature'] != 0x04034B50) { + // invalid Local File Header Signature + fseek($fd, $LocalFileHeader['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly + return false; + } + $LocalFileHeader['raw']['extract_version'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 4, 2)); + $LocalFileHeader['raw']['general_flags'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 6, 2)); + $LocalFileHeader['raw']['compression_method'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 8, 2)); + $LocalFileHeader['raw']['last_mod_file_time'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 10, 2)); + $LocalFileHeader['raw']['last_mod_file_date'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 12, 2)); + $LocalFileHeader['raw']['crc_32'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 14, 4)); + $LocalFileHeader['raw']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 18, 4)); + $LocalFileHeader['raw']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 22, 4)); + $LocalFileHeader['raw']['filename_length'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 26, 2)); + $LocalFileHeader['raw']['extra_field_length'] = getid3_lib::LittleEndian2Int(substr($ZIPlocalFileHeader, 28, 2)); + + $LocalFileHeader['extract_version'] = sprintf('%1.1f', $LocalFileHeader['raw']['extract_version'] / 10); + $LocalFileHeader['host_os'] = $this->ZIPversionOSLookup(($LocalFileHeader['raw']['extract_version'] & 0xFF00) >> 8); + $LocalFileHeader['compression_method'] = $this->ZIPcompressionMethodLookup($LocalFileHeader['raw']['compression_method']); + $LocalFileHeader['compressed_size'] = $LocalFileHeader['raw']['compressed_size']; + $LocalFileHeader['uncompressed_size'] = $LocalFileHeader['raw']['uncompressed_size']; + $LocalFileHeader['flags'] = $this->ZIPparseGeneralPurposeFlags($LocalFileHeader['raw']['general_flags'], $LocalFileHeader['raw']['compression_method']); + $LocalFileHeader['last_modified_timestamp'] = $this->DOStime2UNIXtime($LocalFileHeader['raw']['last_mod_file_date'], $LocalFileHeader['raw']['last_mod_file_time']); + + $FilenameExtrafieldLength = $LocalFileHeader['raw']['filename_length'] + $LocalFileHeader['raw']['extra_field_length']; + if ($FilenameExtrafieldLength > 0) { + $ZIPlocalFileHeader .= fread($fd, $FilenameExtrafieldLength); + + if ($LocalFileHeader['raw']['filename_length'] > 0) { + $LocalFileHeader['filename'] = substr($ZIPlocalFileHeader, 30, $LocalFileHeader['raw']['filename_length']); + } + if ($LocalFileHeader['raw']['extra_field_length'] > 0) { + $LocalFileHeader['raw']['extra_field_data'] = substr($ZIPlocalFileHeader, 30 + $LocalFileHeader['raw']['filename_length'], $LocalFileHeader['raw']['extra_field_length']); + } + } + + $LocalFileHeader['data_offset'] = ftell($fd); + //$LocalFileHeader['compressed_data'] = fread($fd, $LocalFileHeader['raw']['compressed_size']); + fseek($fd, $LocalFileHeader['raw']['compressed_size'], SEEK_CUR); + + if ($LocalFileHeader['flags']['data_descriptor_used']) { + $DataDescriptor = fread($fd, 12); + $LocalFileHeader['data_descriptor']['crc_32'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 0, 4)); + $LocalFileHeader['data_descriptor']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 4, 4)); + $LocalFileHeader['data_descriptor']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($DataDescriptor, 8, 4)); + } + + return $LocalFileHeader; + } + + + function ZIPparseCentralDirectory(&$fd) { + $CentralDirectory['offset'] = ftell($fd); + + $ZIPcentralDirectory = fread($fd, 46); + + $CentralDirectory['raw']['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 0, 4)); + if ($CentralDirectory['raw']['signature'] != 0x02014B50) { + // invalid Central Directory Signature + fseek($fd, $CentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly + return false; + } + $CentralDirectory['raw']['create_version'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 4, 2)); + $CentralDirectory['raw']['extract_version'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 6, 2)); + $CentralDirectory['raw']['general_flags'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 8, 2)); + $CentralDirectory['raw']['compression_method'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 10, 2)); + $CentralDirectory['raw']['last_mod_file_time'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 12, 2)); + $CentralDirectory['raw']['last_mod_file_date'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 14, 2)); + $CentralDirectory['raw']['crc_32'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 16, 4)); + $CentralDirectory['raw']['compressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 20, 4)); + $CentralDirectory['raw']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 24, 4)); + $CentralDirectory['raw']['filename_length'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 28, 2)); + $CentralDirectory['raw']['extra_field_length'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 30, 2)); + $CentralDirectory['raw']['file_comment_length'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 32, 2)); + $CentralDirectory['raw']['disk_number_start'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 34, 2)); + $CentralDirectory['raw']['internal_file_attrib'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 36, 2)); + $CentralDirectory['raw']['external_file_attrib'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 38, 4)); + $CentralDirectory['raw']['local_header_offset'] = getid3_lib::LittleEndian2Int(substr($ZIPcentralDirectory, 42, 4)); + + $CentralDirectory['entry_offset'] = $CentralDirectory['raw']['local_header_offset']; + $CentralDirectory['create_version'] = sprintf('%1.1f', $CentralDirectory['raw']['create_version'] / 10); + $CentralDirectory['extract_version'] = sprintf('%1.1f', $CentralDirectory['raw']['extract_version'] / 10); + $CentralDirectory['host_os'] = $this->ZIPversionOSLookup(($CentralDirectory['raw']['extract_version'] & 0xFF00) >> 8); + $CentralDirectory['compression_method'] = $this->ZIPcompressionMethodLookup($CentralDirectory['raw']['compression_method']); + $CentralDirectory['compressed_size'] = $CentralDirectory['raw']['compressed_size']; + $CentralDirectory['uncompressed_size'] = $CentralDirectory['raw']['uncompressed_size']; + $CentralDirectory['flags'] = $this->ZIPparseGeneralPurposeFlags($CentralDirectory['raw']['general_flags'], $CentralDirectory['raw']['compression_method']); + $CentralDirectory['last_modified_timestamp'] = $this->DOStime2UNIXtime($CentralDirectory['raw']['last_mod_file_date'], $CentralDirectory['raw']['last_mod_file_time']); + + $FilenameExtrafieldCommentLength = $CentralDirectory['raw']['filename_length'] + $CentralDirectory['raw']['extra_field_length'] + $CentralDirectory['raw']['file_comment_length']; + if ($FilenameExtrafieldCommentLength > 0) { + $FilenameExtrafieldComment = fread($fd, $FilenameExtrafieldCommentLength); + + if ($CentralDirectory['raw']['filename_length'] > 0) { + $CentralDirectory['filename'] = substr($FilenameExtrafieldComment, 0, $CentralDirectory['raw']['filename_length']); + } + if ($CentralDirectory['raw']['extra_field_length'] > 0) { + $CentralDirectory['raw']['extra_field_data'] = substr($FilenameExtrafieldComment, $CentralDirectory['raw']['filename_length'], $CentralDirectory['raw']['extra_field_length']); + } + if ($CentralDirectory['raw']['file_comment_length'] > 0) { + $CentralDirectory['file_comment'] = substr($FilenameExtrafieldComment, $CentralDirectory['raw']['filename_length'] + $CentralDirectory['raw']['extra_field_length'], $CentralDirectory['raw']['file_comment_length']); + } + } + + return $CentralDirectory; + } + + function ZIPparseEndOfCentralDirectory(&$fd) { + $EndOfCentralDirectory['offset'] = ftell($fd); + + $ZIPendOfCentralDirectory = fread($fd, 22); + + $EndOfCentralDirectory['signature'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 0, 4)); + if ($EndOfCentralDirectory['signature'] != 0x06054B50) { + // invalid End Of Central Directory Signature + fseek($fd, $EndOfCentralDirectory['offset'], SEEK_SET); // seek back to where filepointer originally was so it can be handled properly + return false; + } + $EndOfCentralDirectory['disk_number_current'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 4, 2)); + $EndOfCentralDirectory['disk_number_start_directory'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 6, 2)); + $EndOfCentralDirectory['directory_entries_this_disk'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 8, 2)); + $EndOfCentralDirectory['directory_entries_total'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 10, 2)); + $EndOfCentralDirectory['directory_size'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 12, 4)); + $EndOfCentralDirectory['directory_offset'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 16, 4)); + $EndOfCentralDirectory['comment_length'] = getid3_lib::LittleEndian2Int(substr($ZIPendOfCentralDirectory, 20, 2)); + + if ($EndOfCentralDirectory['comment_length'] > 0) { + $EndOfCentralDirectory['comment'] = fread($fd, $EndOfCentralDirectory['comment_length']); + } + + return $EndOfCentralDirectory; + } + + + function ZIPparseGeneralPurposeFlags($flagbytes, $compressionmethod) { + $ParsedFlags['encrypted'] = (bool) ($flagbytes & 0x0001); + + switch ($compressionmethod) { + case 6: + $ParsedFlags['dictionary_size'] = (($flagbytes & 0x0002) ? 8192 : 4096); + $ParsedFlags['shannon_fano_trees'] = (($flagbytes & 0x0004) ? 3 : 2); + break; + + case 8: + case 9: + switch (($flagbytes & 0x0006) >> 1) { + case 0: + $ParsedFlags['compression_speed'] = 'normal'; + break; + case 1: + $ParsedFlags['compression_speed'] = 'maximum'; + break; + case 2: + $ParsedFlags['compression_speed'] = 'fast'; + break; + case 3: + $ParsedFlags['compression_speed'] = 'superfast'; + break; + } + break; + } + $ParsedFlags['data_descriptor_used'] = (bool) ($flagbytes & 0x0008); + + return $ParsedFlags; + } + + + function ZIPversionOSLookup($index) { + static $ZIPversionOSLookup = array( + 0 => 'MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)', + 1 => 'Amiga', + 2 => 'OpenVMS', + 3 => 'Unix', + 4 => 'VM/CMS', + 5 => 'Atari ST', + 6 => 'OS/2 H.P.F.S.', + 7 => 'Macintosh', + 8 => 'Z-System', + 9 => 'CP/M', + 10 => 'Windows NTFS', + 11 => 'MVS', + 12 => 'VSE', + 13 => 'Acorn Risc', + 14 => 'VFAT', + 15 => 'Alternate MVS', + 16 => 'BeOS', + 17 => 'Tandem' + ); + + return (isset($ZIPversionOSLookup[$index]) ? $ZIPversionOSLookup[$index] : '[unknown]'); + } + + function ZIPcompressionMethodLookup($index) { + static $ZIPcompressionMethodLookup = array( + 0 => 'store', + 1 => 'shrink', + 2 => 'reduce-1', + 3 => 'reduce-2', + 4 => 'reduce-3', + 5 => 'reduce-4', + 6 => 'implode', + 7 => 'tokenize', + 8 => 'deflate', + 9 => 'deflate64', + 10 => 'PKWARE Date Compression Library Imploding' + ); + + return (isset($ZIPcompressionMethodLookup[$index]) ? $ZIPcompressionMethodLookup[$index] : '[unknown]'); + } + + function DOStime2UNIXtime($DOSdate, $DOStime) { + // wFatDate + // Specifies the MS-DOS date. The date is a packed 16-bit value with the following format: + // Bits Contents + // 0-4 Day of the month (1-31) + // 5-8 Month (1 = January, 2 = February, and so on) + // 9-15 Year offset from 1980 (add 1980 to get actual year) + + $UNIXday = ($DOSdate & 0x001F); + $UNIXmonth = (($DOSdate & 0x01E0) >> 5); + $UNIXyear = (($DOSdate & 0xFE00) >> 9) + 1980; + + // wFatTime + // Specifies the MS-DOS time. The time is a packed 16-bit value with the following format: + // Bits Contents + // 0-4 Second divided by 2 + // 5-10 Minute (0-59) + // 11-15 Hour (0-23 on a 24-hour clock) + + $UNIXsecond = ($DOStime & 0x001F) * 2; + $UNIXminute = (($DOStime & 0x07E0) >> 5); + $UNIXhour = (($DOStime & 0xF800) >> 11); + + return mktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear); + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio-video.asf.php b/modules/id3/getid3/module.audio-video.asf.php new file mode 100644 index 00000000..c11a04b2 --- /dev/null +++ b/modules/id3/getid3/module.audio-video.asf.php @@ -0,0 +1,1593 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.asf.php // +// module for analyzing ASF, WMA and WMV files // +// dependencies: module.audio-video.riff.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + +$GUIDarray = getid3_asf::KnownGUIDs(); +foreach ($GUIDarray as $GUIDname => $hexstringvalue) { + // initialize all GUID constants + define($GUIDname, getid3_asf::GUIDtoBytestring($hexstringvalue)); +} + + + +class getid3_asf +{ + + function getid3_asf(&$fd, &$ThisFileInfo) { + + // Shortcuts + $thisfile_audio = &$ThisFileInfo['audio']; + $thisfile_video = &$ThisFileInfo['video']; + $ThisFileInfo['asf'] = array(); + $thisfile_asf = &$ThisFileInfo['asf']; + $thisfile_asf['comments'] = array(); + $thisfile_asf_comments = &$thisfile_asf['comments']; + $thisfile_asf['header_object'] = array(); + $thisfile_asf_headerobject = &$thisfile_asf['header_object']; + + + // ASF structure: + // * Header Object [required] + // * File Properties Object [required] (global file attributes) + // * Stream Properties Object [required] (defines media stream & characteristics) + // * Header Extension Object [required] (additional functionality) + // * Content Description Object (bibliographic information) + // * Script Command Object (commands for during playback) + // * Marker Object (named jumped points within the file) + // * Data Object [required] + // * Data Packets + // * Index Object + + // Header Object: (mandatory, one only) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for header object - GETID3_ASF_Header_Object + // Object Size QWORD 64 // size of header object, including 30 bytes of Header Object header + // Number of Header Objects DWORD 32 // number of objects in header object + // Reserved1 BYTE 8 // hardcoded: 0x01 + // Reserved2 BYTE 8 // hardcoded: 0x02 + + $ThisFileInfo['fileformat'] = 'asf'; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $HeaderObjectData = fread($fd, 30); + + $thisfile_asf_headerobject['objectid'] = substr($HeaderObjectData, 0, 16); + $thisfile_asf_headerobject['objectid_guid'] = $this->BytestringToGUID($thisfile_asf_headerobject['objectid']); + if ($thisfile_asf_headerobject['objectid'] != GETID3_ASF_Header_Object) { + $ThisFileInfo['warning'][] = 'ASF header GUID {'.$this->BytestringToGUID($thisfile_asf_headerobject['objectid']).'} does not match expected "GETID3_ASF_Header_Object" GUID {'.$this->BytestringToGUID(GETID3_ASF_Header_Object).'}'; + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['asf']); + return false; + break; + } + $thisfile_asf_headerobject['objectsize'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 16, 8)); + $thisfile_asf_headerobject['headerobjects'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 24, 4)); + $thisfile_asf_headerobject['reserved1'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 28, 1)); + $thisfile_asf_headerobject['reserved2'] = getid3_lib::LittleEndian2Int(substr($HeaderObjectData, 29, 1)); + + //$ASFHeaderData = $HeaderObjectData; + $ASFHeaderData = fread($fd, $thisfile_asf_headerobject['objectsize'] - 30); + //$offset = 30; + $offset = 0; + + for ($HeaderObjectsCounter = 0; $HeaderObjectsCounter < $thisfile_asf_headerobject['headerobjects']; $HeaderObjectsCounter++) { + $NextObjectGUID = substr($ASFHeaderData, $offset, 16); + $offset += 16; + $NextObjectGUIDtext = $this->BytestringToGUID($NextObjectGUID); + $NextObjectSize = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); + $offset += 8; + switch ($NextObjectGUID) { + + case GETID3_ASF_File_Properties_Object: + // File Properties Object: (mandatory, one only) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for file properties object - GETID3_ASF_File_Properties_Object + // Object Size QWORD 64 // size of file properties object, including 104 bytes of File Properties Object header + // File ID GUID 128 // unique ID - identical to File ID in Data Object + // File Size QWORD 64 // entire file in bytes. Invalid if Broadcast Flag == 1 + // Creation Date QWORD 64 // date & time of file creation. Maybe invalid if Broadcast Flag == 1 + // Data Packets Count QWORD 64 // number of data packets in Data Object. Invalid if Broadcast Flag == 1 + // Play Duration QWORD 64 // playtime, in 100-nanosecond units. Invalid if Broadcast Flag == 1 + // Send Duration QWORD 64 // time needed to send file, in 100-nanosecond units. Players can ignore this value. Invalid if Broadcast Flag == 1 + // Preroll QWORD 64 // time to buffer data before starting to play file, in 1-millisecond units. If <> 0, PlayDuration and PresentationTime have been offset by this amount + // Flags DWORD 32 // + // * Broadcast Flag bits 1 (0x01) // file is currently being written, some header values are invalid + // * Seekable Flag bits 1 (0x02) // is file seekable + // * Reserved bits 30 (0xFFFFFFFC) // reserved - set to zero + // Minimum Data Packet Size DWORD 32 // in bytes. should be same as Maximum Data Packet Size. Invalid if Broadcast Flag == 1 + // Maximum Data Packet Size DWORD 32 // in bytes. should be same as Minimum Data Packet Size. Invalid if Broadcast Flag == 1 + // Maximum Bitrate DWORD 32 // maximum instantaneous bitrate in bits per second for entire file, including all data streams and ASF overhead + + // shortcut + $thisfile_asf['file_properties_object'] = array(); + $thisfile_asf_filepropertiesobject = &$thisfile_asf['file_properties_object']; + + $thisfile_asf_filepropertiesobject['objectid'] = $NextObjectGUID; + $thisfile_asf_filepropertiesobject['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_filepropertiesobject['objectsize'] = $NextObjectSize; + $thisfile_asf_filepropertiesobject['fileid'] = substr($ASFHeaderData, $offset, 16); + $offset += 16; + $thisfile_asf_filepropertiesobject['fileid_guid'] = $this->BytestringToGUID($thisfile_asf_filepropertiesobject['fileid']); + $thisfile_asf_filepropertiesobject['filesize'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); + $offset += 8; + $thisfile_asf_filepropertiesobject['creation_date'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); + $thisfile_asf_filepropertiesobject['creation_date_unix'] = $this->FILETIMEtoUNIXtime($thisfile_asf_filepropertiesobject['creation_date']); + $offset += 8; + $thisfile_asf_filepropertiesobject['data_packets'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); + $offset += 8; + $thisfile_asf_filepropertiesobject['play_duration'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); + $offset += 8; + $thisfile_asf_filepropertiesobject['send_duration'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); + $offset += 8; + $thisfile_asf_filepropertiesobject['preroll'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); + $offset += 8; + $ThisFileInfo['playtime_seconds'] = ($thisfile_asf_filepropertiesobject['play_duration'] / 10000000) - ($thisfile_asf_filepropertiesobject['preroll'] / 1000); + $thisfile_asf_filepropertiesobject['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + $thisfile_asf_filepropertiesobject['flags']['broadcast'] = (bool) ($thisfile_asf_filepropertiesobject['flags_raw'] & 0x0001); + $thisfile_asf_filepropertiesobject['flags']['seekable'] = (bool) ($thisfile_asf_filepropertiesobject['flags_raw'] & 0x0002); + + $thisfile_asf_filepropertiesobject['min_packet_size'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + $thisfile_asf_filepropertiesobject['max_packet_size'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + $thisfile_asf_filepropertiesobject['max_bitrate'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + $ThisFileInfo['bitrate'] = $thisfile_asf_filepropertiesobject['max_bitrate']; + break; + + case GETID3_ASF_Stream_Properties_Object: + // Stream Properties Object: (mandatory, one per media stream) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for stream properties object - GETID3_ASF_Stream_Properties_Object + // Object Size QWORD 64 // size of stream properties object, including 78 bytes of Stream Properties Object header + // Stream Type GUID 128 // GETID3_ASF_Audio_Media, GETID3_ASF_Video_Media or GETID3_ASF_Command_Media + // Error Correction Type GUID 128 // GETID3_ASF_Audio_Spread for audio-only streams, GETID3_ASF_No_Error_Correction for other stream types + // Time Offset QWORD 64 // 100-nanosecond units. typically zero. added to all timestamps of samples in the stream + // Type-Specific Data Length DWORD 32 // number of bytes for Type-Specific Data field + // Error Correction Data Length DWORD 32 // number of bytes for Error Correction Data field + // Flags WORD 16 // + // * Stream Number bits 7 (0x007F) // number of this stream. 1 <= valid <= 127 + // * Reserved bits 8 (0x7F80) // reserved - set to zero + // * Encrypted Content Flag bits 1 (0x8000) // stream contents encrypted if set + // Reserved DWORD 32 // reserved - set to zero + // Type-Specific Data BYTESTREAM variable // type-specific format data, depending on value of Stream Type + // Error Correction Data BYTESTREAM variable // error-correction-specific format data, depending on value of Error Correct Type + + // There is one GETID3_ASF_Stream_Properties_Object for each stream (audio, video) but the + // stream number isn't known until halfway through decoding the structure, hence it + // it is decoded to a temporary variable and then stuck in the appropriate index later + + $StreamPropertiesObjectData['objectid'] = $NextObjectGUID; + $StreamPropertiesObjectData['objectid_guid'] = $NextObjectGUIDtext; + $StreamPropertiesObjectData['objectsize'] = $NextObjectSize; + $StreamPropertiesObjectData['stream_type'] = substr($ASFHeaderData, $offset, 16); + $offset += 16; + $StreamPropertiesObjectData['stream_type_guid'] = $this->BytestringToGUID($StreamPropertiesObjectData['stream_type']); + $StreamPropertiesObjectData['error_correct_type'] = substr($ASFHeaderData, $offset, 16); + $offset += 16; + $StreamPropertiesObjectData['error_correct_guid'] = $this->BytestringToGUID($StreamPropertiesObjectData['error_correct_type']); + $StreamPropertiesObjectData['time_offset'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); + $offset += 8; + $StreamPropertiesObjectData['type_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + $StreamPropertiesObjectData['error_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + $StreamPropertiesObjectData['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $StreamPropertiesObjectStreamNumber = $StreamPropertiesObjectData['flags_raw'] & 0x007F; + $StreamPropertiesObjectData['flags']['encrypted'] = (bool) ($StreamPropertiesObjectData['flags_raw'] & 0x8000); + + $offset += 4; // reserved - DWORD + $StreamPropertiesObjectData['type_specific_data'] = substr($ASFHeaderData, $offset, $StreamPropertiesObjectData['type_data_length']); + $offset += $StreamPropertiesObjectData['type_data_length']; + $StreamPropertiesObjectData['error_correct_data'] = substr($ASFHeaderData, $offset, $StreamPropertiesObjectData['error_data_length']); + $offset += $StreamPropertiesObjectData['error_data_length']; + + switch ($StreamPropertiesObjectData['stream_type']) { + + case GETID3_ASF_Audio_Media: + $thisfile_audio['dataformat'] = (!empty($thisfile_audio['dataformat']) ? $thisfile_audio['dataformat'] : 'asf'); + $thisfile_audio['bitrate_mode'] = (!empty($thisfile_audio['bitrate_mode']) ? $thisfile_audio['bitrate_mode'] : 'cbr'); + + $audiodata = getid3_riff::RIFFparseWAVEFORMATex(substr($StreamPropertiesObjectData['type_specific_data'], 0, 16)); + unset($audiodata['raw']); + $thisfile_audio = getid3_lib::array_merge_noclobber($audiodata, $thisfile_audio); + break; + + case GETID3_ASF_Video_Media: + $thisfile_video['dataformat'] = (!empty($thisfile_video['dataformat']) ? $thisfile_video['dataformat'] : 'asf'); + $thisfile_video['bitrate_mode'] = (!empty($thisfile_video['bitrate_mode']) ? $thisfile_video['bitrate_mode'] : 'cbr'); + break; + + case GETID3_ASF_Command_Media: + default: + // do nothing + break; + + } + + $thisfile_asf['stream_properties_object'][$StreamPropertiesObjectStreamNumber] = $StreamPropertiesObjectData; + unset($StreamPropertiesObjectData); // clear for next stream, if any + break; + + case GETID3_ASF_Header_Extension_Object: + // Header Extension Object: (mandatory, one only) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for Header Extension object - GETID3_ASF_Header_Extension_Object + // Object Size QWORD 64 // size of Header Extension object, including 46 bytes of Header Extension Object header + // Reserved Field 1 GUID 128 // hardcoded: GETID3_ASF_Reserved_1 + // Reserved Field 2 WORD 16 // hardcoded: 0x00000006 + // Header Extension Data Size DWORD 32 // in bytes. valid: 0, or > 24. equals object size minus 46 + // Header Extension Data BYTESTREAM variable // array of zero or more extended header objects + + // shortcut + $thisfile_asf['header_extension_object'] = array(); + $thisfile_asf_headerextensionobject = &$thisfile_asf['header_extension_object']; + + $thisfile_asf_headerextensionobject['objectid'] = $NextObjectGUID; + $thisfile_asf_headerextensionobject['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_headerextensionobject['objectsize'] = $NextObjectSize; + $thisfile_asf_headerextensionobject['reserved_1'] = substr($ASFHeaderData, $offset, 16); + $offset += 16; + $thisfile_asf_headerextensionobject['reserved_1_guid'] = $this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']); + if ($thisfile_asf_headerextensionobject['reserved_1'] != GETID3_ASF_Reserved_1) { + $ThisFileInfo['warning'][] = 'header_extension_object.reserved_1 GUID ('.$this->BytestringToGUID($thisfile_asf_headerextensionobject['reserved_1']).') does not match expected "GETID3_ASF_Reserved_1" GUID ('.$this->BytestringToGUID(GETID3_ASF_Reserved_1).')'; + //return false; + break; + } + $thisfile_asf_headerextensionobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + if ($thisfile_asf_headerextensionobject['reserved_2'] != 6) { + $ThisFileInfo['warning'][] = 'header_extension_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_headerextensionobject['reserved_2']).') does not match expected value of "6"'; + //return false; + break; + } + $thisfile_asf_headerextensionobject['extension_data_size'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + $thisfile_asf_headerextensionobject['extension_data'] = substr($ASFHeaderData, $offset, $thisfile_asf_headerextensionobject['extension_data_size']); + $offset += $thisfile_asf_headerextensionobject['extension_data_size']; + break; + + case GETID3_ASF_Codec_List_Object: + // Codec List Object: (optional, one only) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for Codec List object - GETID3_ASF_Codec_List_Object + // Object Size QWORD 64 // size of Codec List object, including 44 bytes of Codec List Object header + // Reserved GUID 128 // hardcoded: 86D15241-311D-11D0-A3A4-00A0C90348F6 + // Codec Entries Count DWORD 32 // number of entries in Codec Entries array + // Codec Entries array of: variable // + // * Type WORD 16 // 0x0001 = Video Codec, 0x0002 = Audio Codec, 0xFFFF = Unknown Codec + // * Codec Name Length WORD 16 // number of Unicode characters stored in the Codec Name field + // * Codec Name WCHAR variable // array of Unicode characters - name of codec used to create the content + // * Codec Description Length WORD 16 // number of Unicode characters stored in the Codec Description field + // * Codec Description WCHAR variable // array of Unicode characters - description of format used to create the content + // * Codec Information Length WORD 16 // number of Unicode characters stored in the Codec Information field + // * Codec Information BYTESTREAM variable // opaque array of information bytes about the codec used to create the content + + // shortcut + $thisfile_asf['codec_list_object'] = array(); + $thisfile_asf_codeclistobject = &$thisfile_asf['codec_list_object']; + + $thisfile_asf_codeclistobject['objectid'] = $NextObjectGUID; + $thisfile_asf_codeclistobject['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_codeclistobject['objectsize'] = $NextObjectSize; + $thisfile_asf_codeclistobject['reserved'] = substr($ASFHeaderData, $offset, 16); + $offset += 16; + $thisfile_asf_codeclistobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']); + if ($thisfile_asf_codeclistobject['reserved'] != $this->GUIDtoBytestring('86D15241-311D-11D0-A3A4-00A0C90348F6')) { + $ThisFileInfo['warning'][] = 'codec_list_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_codeclistobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {86D15241-311D-11D0-A3A4-00A0C90348F6}'; + //return false; + break; + } + $thisfile_asf_codeclistobject['codec_entries_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + for ($CodecEntryCounter = 0; $CodecEntryCounter < $thisfile_asf_codeclistobject['codec_entries_count']; $CodecEntryCounter++) { + // shortcut + $thisfile_asf_codeclistobject['codec_entries'][$CodecEntryCounter] = array(); + $thisfile_asf_codeclistobject_codecentries_current = &$thisfile_asf_codeclistobject['codec_entries'][$CodecEntryCounter]; + + $thisfile_asf_codeclistobject_codecentries_current['type_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_codeclistobject_codecentries_current['type'] = $this->ASFCodecListObjectTypeLookup($thisfile_asf_codeclistobject_codecentries_current['type_raw']); + + $CodecNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character + $offset += 2; + $thisfile_asf_codeclistobject_codecentries_current['name'] = substr($ASFHeaderData, $offset, $CodecNameLength); + $offset += $CodecNameLength; + + $CodecDescriptionLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character + $offset += 2; + $thisfile_asf_codeclistobject_codecentries_current['description'] = substr($ASFHeaderData, $offset, $CodecDescriptionLength); + $offset += $CodecDescriptionLength; + + $CodecInformationLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_codeclistobject_codecentries_current['information'] = substr($ASFHeaderData, $offset, $CodecInformationLength); + $offset += $CodecInformationLength; + + if ($thisfile_asf_codeclistobject_codecentries_current['type_raw'] == 2) { + // audio codec + if (strpos($thisfile_asf_codeclistobject_codecentries_current['description'], ',') === false) { + $ThisFileInfo['error'][] = '[asf][codec_list_object][codec_entries]['.$CodecEntryCounter.'][description] expected to contain comma-seperated list of parameters: "'.$thisfile_asf_codeclistobject_codecentries_current['description'].'"'; + return false; + } + list($AudioCodecBitrate, $AudioCodecFrequency, $AudioCodecChannels) = explode(',', $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description'])); + $thisfile_audio['codec'] = $this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['name']); + + if (!isset($thisfile_audio['bitrate']) && strstr($AudioCodecBitrate, 'kbps')) { + $thisfile_audio['bitrate'] = (int) (trim(str_replace('kbps', '', $AudioCodecBitrate)) * 1000); + } + if (!isset($thisfile_video['bitrate']) && isset($thisfile_audio['bitrate']) && isset($thisfile_asf['file_properties_object']['max_bitrate']) && ($thisfile_asf_codeclistobject['codec_entries_count'] > 1)) { + $thisfile_video['bitrate'] = $thisfile_asf['file_properties_object']['max_bitrate'] - $thisfile_audio['bitrate']; + } + + $AudioCodecFrequency = (int) trim(str_replace('kHz', '', $AudioCodecFrequency)); + switch ($AudioCodecFrequency) { + case 8: + $thisfile_audio['sample_rate'] = 8000; + break; + + case 11: + $thisfile_audio['sample_rate'] = 11025; + break; + + case 12: + $thisfile_audio['sample_rate'] = 12000; + break; + + case 16: + $thisfile_audio['sample_rate'] = 16000; + break; + + case 22: + $thisfile_audio['sample_rate'] = 22050; + break; + + case 24: + $thisfile_audio['sample_rate'] = 24000; + break; + + case 32: + $thisfile_audio['sample_rate'] = 32000; + break; + + case 44: + $thisfile_audio['sample_rate'] = 44100; + break; + + case 48: + $thisfile_audio['sample_rate'] = 48000; + break; + + default: + $ThisFileInfo['warning'][] = 'unknown frequency: "'.$AudioCodecFrequency.'" ('.$this->TrimConvert($thisfile_asf_codeclistobject_codecentries_current['description']).')'; + // return false; + break; + } + + if (!isset($thisfile_audio['channels'])) { + if (strstr($AudioCodecChannels, 'stereo')) { + $thisfile_audio['channels'] = 2; + } elseif (strstr($AudioCodecChannels, 'mono')) { + $thisfile_audio['channels'] = 1; + } + } + } + } + break; + + case GETID3_ASF_Script_Command_Object: + // Script Command Object: (optional, one only) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for Script Command object - GETID3_ASF_Script_Command_Object + // Object Size QWORD 64 // size of Script Command object, including 44 bytes of Script Command Object header + // Reserved GUID 128 // hardcoded: 4B1ACBE3-100B-11D0-A39B-00A0C90348F6 + // Commands Count WORD 16 // number of Commands structures in the Script Commands Objects + // Command Types Count WORD 16 // number of Command Types structures in the Script Commands Objects + // Command Types array of: variable // + // * Command Type Name Length WORD 16 // number of Unicode characters for Command Type Name + // * Command Type Name WCHAR variable // array of Unicode characters - name of a type of command + // Commands array of: variable // + // * Presentation Time DWORD 32 // presentation time of that command, in milliseconds + // * Type Index WORD 16 // type of this command, as a zero-based index into the array of Command Types of this object + // * Command Name Length WORD 16 // number of Unicode characters for Command Name + // * Command Name WCHAR variable // array of Unicode characters - name of this command + + // shortcut + $thisfile_asf['script_command_object'] = array(); + $thisfile_asf_scriptcommandobject = &$thisfile_asf['script_command_object']; + + $thisfile_asf_scriptcommandobject['objectid'] = $NextObjectGUID; + $thisfile_asf_scriptcommandobject['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_scriptcommandobject['objectsize'] = $NextObjectSize; + $thisfile_asf_scriptcommandobject['reserved'] = substr($ASFHeaderData, $offset, 16); + $offset += 16; + $thisfile_asf_scriptcommandobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']); + if ($thisfile_asf_scriptcommandobject['reserved'] != $this->GUIDtoBytestring('4B1ACBE3-100B-11D0-A39B-00A0C90348F6')) { + $ThisFileInfo['warning'][] = 'script_command_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_scriptcommandobject['reserved']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4B1ACBE3-100B-11D0-A39B-00A0C90348F6}'; + //return false; + break; + } + $thisfile_asf_scriptcommandobject['commands_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_scriptcommandobject['command_types_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + for ($CommandTypesCounter = 0; $CommandTypesCounter < $thisfile_asf_scriptcommandobject['command_types_count']; $CommandTypesCounter++) { + $CommandTypeNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character + $offset += 2; + $thisfile_asf_scriptcommandobject['command_types'][$CommandTypesCounter]['name'] = substr($ASFHeaderData, $offset, $CommandTypeNameLength); + $offset += $CommandTypeNameLength; + } + for ($CommandsCounter = 0; $CommandsCounter < $thisfile_asf_scriptcommandobject['commands_count']; $CommandsCounter++) { + $thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['presentation_time'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + $thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['type_index'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + + $CommandTypeNameLength = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)) * 2; // 2 bytes per character + $offset += 2; + $thisfile_asf_scriptcommandobject['commands'][$CommandsCounter]['name'] = substr($ASFHeaderData, $offset, $CommandTypeNameLength); + $offset += $CommandTypeNameLength; + } + break; + + case GETID3_ASF_Marker_Object: + // Marker Object: (optional, one only) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for Marker object - GETID3_ASF_Marker_Object + // Object Size QWORD 64 // size of Marker object, including 48 bytes of Marker Object header + // Reserved GUID 128 // hardcoded: 4CFEDB20-75F6-11CF-9C0F-00A0C90349CB + // Markers Count DWORD 32 // number of Marker structures in Marker Object + // Reserved WORD 16 // hardcoded: 0x0000 + // Name Length WORD 16 // number of bytes in the Name field + // Name WCHAR variable // name of the Marker Object + // Markers array of: variable // + // * Offset QWORD 64 // byte offset into Data Object + // * Presentation Time QWORD 64 // in 100-nanosecond units + // * Entry Length WORD 16 // length in bytes of (Send Time + Flags + Marker Description Length + Marker Description + Padding) + // * Send Time DWORD 32 // in milliseconds + // * Flags DWORD 32 // hardcoded: 0x00000000 + // * Marker Description Length DWORD 32 // number of bytes in Marker Description field + // * Marker Description WCHAR variable // array of Unicode characters - description of marker entry + // * Padding BYTESTREAM variable // optional padding bytes + + // shortcut + $thisfile_asf['marker_object'] = array(); + $thisfile_asf_markerobject = &$thisfile_asf['marker_object']; + + $thisfile_asf_markerobject['objectid'] = $NextObjectGUID; + $thisfile_asf_markerobject['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_markerobject['objectsize'] = $NextObjectSize; + $thisfile_asf_markerobject['reserved'] = substr($ASFHeaderData, $offset, 16); + $offset += 16; + $thisfile_asf_markerobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_markerobject['reserved']); + if ($thisfile_asf_markerobject['reserved'] != $this->GUIDtoBytestring('4CFEDB20-75F6-11CF-9C0F-00A0C90349CB')) { + $ThisFileInfo['warning'][] = 'marker_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_markerobject['reserved_1']).'} does not match expected "GETID3_ASF_Reserved_1" GUID {4CFEDB20-75F6-11CF-9C0F-00A0C90349CB}'; + break; + } + $thisfile_asf_markerobject['markers_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + $thisfile_asf_markerobject['reserved_2'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + if ($thisfile_asf_markerobject['reserved_2'] != 0) { + $ThisFileInfo['warning'][] = 'marker_object.reserved_2 ('.getid3_lib::PrintHexBytes($thisfile_asf_markerobject['reserved_2']).') does not match expected value of "0"'; + break; + } + $thisfile_asf_markerobject['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_markerobject['name'] = substr($ASFHeaderData, $offset, $thisfile_asf_markerobject['name_length']); + $offset += $thisfile_asf_markerobject['name_length']; + for ($MarkersCounter = 0; $MarkersCounter < $thisfile_asf_markerobject['markers_count']; $MarkersCounter++) { + $thisfile_asf_markerobject['markers'][$MarkersCounter]['offset'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); + $offset += 8; + $thisfile_asf_markerobject['markers'][$MarkersCounter]['presentation_time'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 8)); + $offset += 8; + $thisfile_asf_markerobject['markers'][$MarkersCounter]['entry_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_markerobject['markers'][$MarkersCounter]['send_time'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + $thisfile_asf_markerobject['markers'][$MarkersCounter]['flags'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description'] = substr($ASFHeaderData, $offset, $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length']); + $offset += $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length']; + $PaddingLength = $thisfile_asf_markerobject['markers'][$MarkersCounter]['entry_length'] - 4 - 4 - 4 - $thisfile_asf_markerobject['markers'][$MarkersCounter]['marker_description_length']; + if ($PaddingLength > 0) { + $thisfile_asf_markerobject['markers'][$MarkersCounter]['padding'] = substr($ASFHeaderData, $offset, $PaddingLength); + $offset += $PaddingLength; + } + } + break; + + case GETID3_ASF_Bitrate_Mutual_Exclusion_Object: + // Bitrate Mutual Exclusion Object: (optional) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for Bitrate Mutual Exclusion object - GETID3_ASF_Bitrate_Mutual_Exclusion_Object + // Object Size QWORD 64 // size of Bitrate Mutual Exclusion object, including 42 bytes of Bitrate Mutual Exclusion Object header + // Exlusion Type GUID 128 // nature of mutual exclusion relationship. one of: (GETID3_ASF_Mutex_Bitrate, GETID3_ASF_Mutex_Unknown) + // Stream Numbers Count WORD 16 // number of video streams + // Stream Numbers WORD variable // array of mutually exclusive video stream numbers. 1 <= valid <= 127 + + // shortcut + $thisfile_asf['bitrate_mutual_exclusion_object'] = array(); + $thisfile_asf_bitratemutualexclusionobject = &$thisfile_asf['bitrate_mutual_exclusion_object']; + + $thisfile_asf_bitratemutualexclusionobject['objectid'] = $NextObjectGUID; + $thisfile_asf_bitratemutualexclusionobject['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_bitratemutualexclusionobject['objectsize'] = $NextObjectSize; + $thisfile_asf_bitratemutualexclusionobject['reserved'] = substr($ASFHeaderData, $offset, 16); + $thisfile_asf_bitratemutualexclusionobject['reserved_guid'] = $this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']); + $offset += 16; + if (($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Bitrate) && ($thisfile_asf_bitratemutualexclusionobject['reserved'] != GETID3_ASF_Mutex_Unknown)) { + $ThisFileInfo['warning'][] = 'bitrate_mutual_exclusion_object.reserved GUID {'.$this->BytestringToGUID($thisfile_asf_bitratemutualexclusionobject['reserved']).'} does not match expected "GETID3_ASF_Mutex_Bitrate" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Bitrate).'} or "GETID3_ASF_Mutex_Unknown" GUID {'.$this->BytestringToGUID(GETID3_ASF_Mutex_Unknown).'}'; + //return false; + break; + } + $thisfile_asf_bitratemutualexclusionobject['stream_numbers_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + for ($StreamNumberCounter = 0; $StreamNumberCounter < $thisfile_asf_bitratemutualexclusionobject['stream_numbers_count']; $StreamNumberCounter++) { + $thisfile_asf_bitratemutualexclusionobject['stream_numbers'][$StreamNumberCounter] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + } + break; + + case GETID3_ASF_Error_Correction_Object: + // Error Correction Object: (optional, one only) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for Error Correction object - GETID3_ASF_Error_Correction_Object + // Object Size QWORD 64 // size of Error Correction object, including 44 bytes of Error Correction Object header + // Error Correction Type GUID 128 // type of error correction. one of: (GETID3_ASF_No_Error_Correction, GETID3_ASF_Audio_Spread) + // Error Correction Data Length DWORD 32 // number of bytes in Error Correction Data field + // Error Correction Data BYTESTREAM variable // structure depends on value of Error Correction Type field + + // shortcut + $thisfile_asf['error_correction_object'] = array(); + $thisfile_asf_errorcorrectionobject = &$thisfile_asf['error_correction_object']; + + $thisfile_asf_errorcorrectionobject['objectid'] = $NextObjectGUID; + $thisfile_asf_errorcorrectionobject['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_errorcorrectionobject['objectsize'] = $NextObjectSize; + $thisfile_asf_errorcorrectionobject['error_correction_type'] = substr($ASFHeaderData, $offset, 16); + $offset += 16; + $thisfile_asf_errorcorrectionobject['error_correction_guid'] = $this->BytestringToGUID($thisfile_asf_errorcorrectionobject['error_correction_type']); + $thisfile_asf_errorcorrectionobject['error_correction_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + switch ($thisfile_asf_errorcorrectionobject['error_correction_type']) { + case GETID3_ASF_No_Error_Correction: + // should be no data, but just in case there is, skip to the end of the field + $offset += $thisfile_asf_errorcorrectionobject['error_correction_data_length']; + break; + + case GETID3_ASF_Audio_Spread: + // Field Name Field Type Size (bits) + // Span BYTE 8 // number of packets over which audio will be spread. + // Virtual Packet Length WORD 16 // size of largest audio payload found in audio stream + // Virtual Chunk Length WORD 16 // size of largest audio payload found in audio stream + // Silence Data Length WORD 16 // number of bytes in Silence Data field + // Silence Data BYTESTREAM variable // hardcoded: 0x00 * (Silence Data Length) bytes + + $thisfile_asf_errorcorrectionobject['span'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 1)); + $offset += 1; + $thisfile_asf_errorcorrectionobject['virtual_packet_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_errorcorrectionobject['virtual_chunk_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_errorcorrectionobject['silence_data_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_errorcorrectionobject['silence_data'] = substr($ASFHeaderData, $offset, $thisfile_asf_errorcorrectionobject['silence_data_length']); + $offset += $thisfile_asf_errorcorrectionobject['silence_data_length']; + break; + + default: + $ThisFileInfo['warning'][] = 'error_correction_object.error_correction_type GUID {'.$this->BytestringToGUID($thisfile_asf_errorcorrectionobject['reserved']).'} does not match expected "GETID3_ASF_No_Error_Correction" GUID {'.$this->BytestringToGUID(GETID3_ASF_No_Error_Correction).'} or "GETID3_ASF_Audio_Spread" GUID {'.$this->BytestringToGUID(GETID3_ASF_Audio_Spread).'}'; + //return false; + break; + } + + break; + + case GETID3_ASF_Content_Description_Object: + // Content Description Object: (optional, one only) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for Content Description object - GETID3_ASF_Content_Description_Object + // Object Size QWORD 64 // size of Content Description object, including 34 bytes of Content Description Object header + // Title Length WORD 16 // number of bytes in Title field + // Author Length WORD 16 // number of bytes in Author field + // Copyright Length WORD 16 // number of bytes in Copyright field + // Description Length WORD 16 // number of bytes in Description field + // Rating Length WORD 16 // number of bytes in Rating field + // Title WCHAR 16 // array of Unicode characters - Title + // Author WCHAR 16 // array of Unicode characters - Author + // Copyright WCHAR 16 // array of Unicode characters - Copyright + // Description WCHAR 16 // array of Unicode characters - Description + // Rating WCHAR 16 // array of Unicode characters - Rating + + // shortcut + $thisfile_asf['content_description_object'] = array(); + $thisfile_asf_contentdescriptionobject = &$thisfile_asf['content_description_object']; + + $thisfile_asf_contentdescriptionobject['objectid'] = $NextObjectGUID; + $thisfile_asf_contentdescriptionobject['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_contentdescriptionobject['objectsize'] = $NextObjectSize; + $thisfile_asf_contentdescriptionobject['title_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_contentdescriptionobject['author_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_contentdescriptionobject['copyright_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_contentdescriptionobject['description_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_contentdescriptionobject['rating_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_contentdescriptionobject['title'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['title_length']); + $offset += $thisfile_asf_contentdescriptionobject['title_length']; + $thisfile_asf_contentdescriptionobject['author'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['author_length']); + $offset += $thisfile_asf_contentdescriptionobject['author_length']; + $thisfile_asf_contentdescriptionobject['copyright'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['copyright_length']); + $offset += $thisfile_asf_contentdescriptionobject['copyright_length']; + $thisfile_asf_contentdescriptionobject['description'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['description_length']); + $offset += $thisfile_asf_contentdescriptionobject['description_length']; + $thisfile_asf_contentdescriptionobject['rating'] = substr($ASFHeaderData, $offset, $thisfile_asf_contentdescriptionobject['rating_length']); + $offset += $thisfile_asf_contentdescriptionobject['rating_length']; + + $ASFcommentKeysToCopy = array('title'=>'title', 'author'=>'artist', 'copyright'=>'copyright', 'description'=>'comment', 'rating'=>'rating'); + foreach ($ASFcommentKeysToCopy as $keytocopyfrom => $keytocopyto) { + if (!empty($thisfile_asf_contentdescriptionobject[$keytocopyfrom])) { + $thisfile_asf_comments[$keytocopyto][] = $this->TrimTerm($thisfile_asf_contentdescriptionobject[$keytocopyfrom]); + } + } + break; + + case GETID3_ASF_Extended_Content_Description_Object: + // Extended Content Description Object: (optional, one only) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for Extended Content Description object - GETID3_ASF_Extended_Content_Description_Object + // Object Size QWORD 64 // size of ExtendedContent Description object, including 26 bytes of Extended Content Description Object header + // Content Descriptors Count WORD 16 // number of entries in Content Descriptors list + // Content Descriptors array of: variable // + // * Descriptor Name Length WORD 16 // size in bytes of Descriptor Name field + // * Descriptor Name WCHAR variable // array of Unicode characters - Descriptor Name + // * Descriptor Value Data Type WORD 16 // Lookup array: + // 0x0000 = Unicode String (variable length) + // 0x0001 = BYTE array (variable length) + // 0x0002 = BOOL (DWORD, 32 bits) + // 0x0003 = DWORD (DWORD, 32 bits) + // 0x0004 = QWORD (QWORD, 64 bits) + // 0x0005 = WORD (WORD, 16 bits) + // * Descriptor Value Length WORD 16 // number of bytes stored in Descriptor Value field + // * Descriptor Value variable variable // value for Content Descriptor + + // shortcut + $thisfile_asf['extended_content_description_object'] = array(); + $thisfile_asf_extendedcontentdescriptionobject = &$thisfile_asf['extended_content_description_object']; + + $thisfile_asf_extendedcontentdescriptionobject['objectid'] = $NextObjectGUID; + $thisfile_asf_extendedcontentdescriptionobject['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_extendedcontentdescriptionobject['objectsize'] = $NextObjectSize; + $thisfile_asf_extendedcontentdescriptionobject['content_descriptors_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + for ($ExtendedContentDescriptorsCounter = 0; $ExtendedContentDescriptorsCounter < $thisfile_asf_extendedcontentdescriptionobject['content_descriptors_count']; $ExtendedContentDescriptorsCounter++) { + // shortcut + $thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter] = array(); + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current = &$thisfile_asf_extendedcontentdescriptionobject['content_descriptors'][$ExtendedContentDescriptorsCounter]; + + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['base_offset'] = $offset + 30; + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name'] = substr($ASFHeaderData, $offset, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length']); + $offset += $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name_length']; + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = substr($ASFHeaderData, $offset, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length']); + $offset += $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_length']; + switch ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type']) { + case 0x0000: // Unicode string + break; + + case 0x0001: // BYTE array + // do nothing + break; + + case 0x0002: // BOOL + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = (bool) getid3_lib::LittleEndian2Int($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); + break; + + case 0x0003: // DWORD + case 0x0004: // QWORD + case 0x0005: // WORD + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'] = getid3_lib::LittleEndian2Int($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); + break; + + default: + $ThisFileInfo['warning'][] = 'extended_content_description.content_descriptors.'.$ExtendedContentDescriptorsCounter.'.value_type is invalid ('.$thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type'].')'; + //return false; + break; + } + switch ($this->TrimConvert(strtolower($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name']))) { + + case 'wm/albumartist': + case 'artist': + $thisfile_asf_comments['artist'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + break; + + case 'wm/albumtitle': + case 'album': + $thisfile_asf_comments['album'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + break; + + case 'wm/genre': + case 'genre': + $thisfile_asf_comments['genre'] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + break; + + case 'wm/tracknumber': + case 'tracknumber': + $thisfile_asf_comments['track'] = array(intval($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']))); + break; + + case 'wm/track': + if (empty($thisfile_asf_comments['track'])) { + $thisfile_asf_comments['track'] = array(1 + $this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + } + break; + + case 'wm/year': + case 'year': + case 'date': + $thisfile_asf_comments['year'] = array( $this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + break; + + case 'isvbr': + if ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']) { + $thisfile_audio['bitrate_mode'] = 'vbr'; + $thisfile_video['bitrate_mode'] = 'vbr'; + } + break; + + case 'id3': + // id3v2 module might not be loaded + if (class_exists('getid3_id3v2')) { + $tempfile = tempnam('*', 'getID3'); + $tempfilehandle = fopen($tempfile, "wb"); + $tempThisfileInfo = array('encoding'=>$ThisFileInfo['encoding']); + fwrite($tempfilehandle, $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); + fclose($tempfilehandle); + + $tempfilehandle = fopen($tempfile, "rb"); + $id3 = new getid3_id3v2($tempfilehandle, $tempThisfileInfo); + fclose($tempfilehandle); + unlink($tempfile); + + $ThisFileInfo['id3v2'] = $tempThisfileInfo['id3v2']; + } + break; + + case 'wm/encodingtime': + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['encoding_time_unix'] = $this->FILETIMEtoUNIXtime($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); + $thisfile_asf_comments['encoding_time_unix'] = array($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['encoding_time_unix']); + break; + + case 'wm/picture': + //typedef struct _WMPicture{ + // LPWSTR pwszMIMEType; + // BYTE bPictureType; + // LPWSTR pwszDescription; + // DWORD dwDataLen; + // BYTE* pbData; + //} WM_PICTURE; + + $wm_picture_offset = 0; + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id'] = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 1)); + $wm_picture_offset += 1; + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type'] = $this->WMpictureTypeLookup($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_type_id']); + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_size'] = getid3_lib::LittleEndian2Int(substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 4)); + $wm_picture_offset += 4; + + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] = ''; + do { + $next_byte_pair = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 2); + $wm_picture_offset += 2; + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_mime'] .= $next_byte_pair; + } while ($next_byte_pair !== "\x00\x00"); + + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_description'] = ''; + do { + $next_byte_pair = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset, 2); + $wm_picture_offset += 2; + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['image_description'] .= $next_byte_pair; + } while ($next_byte_pair !== "\x00\x00"); + + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['dataoffset'] = $wm_picture_offset; + $thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['data'] = substr($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'], $wm_picture_offset); + unset($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value']); + + break; + + default: + switch ($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value_type']) { + case 0: // Unicode string + if (substr($this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name']), 0, 3) == 'WM/') { + $thisfile_asf_comments[str_replace('wm/', '', strtolower($this->TrimConvert($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['name'])))] = array($this->TrimTerm($thisfile_asf_extendedcontentdescriptionobject_contentdescriptor_current['value'])); + } + break; + + case 1: + break; + } + break; + } + + } + break; + + case GETID3_ASF_Stream_Bitrate_Properties_Object: + // Stream Bitrate Properties Object: (optional, one only) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for Stream Bitrate Properties object - GETID3_ASF_Stream_Bitrate_Properties_Object + // Object Size QWORD 64 // size of Extended Content Description object, including 26 bytes of Stream Bitrate Properties Object header + // Bitrate Records Count WORD 16 // number of records in Bitrate Records + // Bitrate Records array of: variable // + // * Flags WORD 16 // + // * * Stream Number bits 7 (0x007F) // number of this stream + // * * Reserved bits 9 (0xFF80) // hardcoded: 0 + // * Average Bitrate DWORD 32 // in bits per second + + // shortcut + $thisfile_asf['stream_bitrate_properties_object'] = array(); + $thisfile_asf_streambitratepropertiesobject = &$thisfile_asf['stream_bitrate_properties_object']; + + $thisfile_asf_streambitrateproperties['objectid'] = $NextObjectGUID; + $thisfile_asf_streambitrateproperties['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_streambitrateproperties['objectsize'] = $NextObjectSize; + $thisfile_asf_streambitrateproperties['bitrate_records_count'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + for ($BitrateRecordsCounter = 0; $BitrateRecordsCounter < $thisfile_asf_streambitrateproperties['bitrate_records_count']; $BitrateRecordsCounter++) { + $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 2)); + $offset += 2; + $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['flags']['stream_number'] = $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['flags_raw'] & 0x007F; + $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['bitrate'] = getid3_lib::LittleEndian2Int(substr($ASFHeaderData, $offset, 4)); + $offset += 4; + } + break; + + case GETID3_ASF_Padding_Object: + // Padding Object: (optional) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for Padding object - GETID3_ASF_Padding_Object + // Object Size QWORD 64 // size of Padding object, including 24 bytes of ASF Padding Object header + // Padding Data BYTESTREAM variable // ignore + + // shortcut + $thisfile_asf['padding_object'] = array(); + $thisfile_asf_paddingobject = &$thisfile_asf['padding_object']; + + $thisfile_asf_paddingobject['objectid'] = $NextObjectGUID; + $thisfile_asf_paddingobject['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_paddingobject['objectsize'] = $NextObjectSize; + $thisfile_asf_paddingobject['padding_length'] = $thisfile_asf_paddingobject['objectsize'] - 16 - 8; + $thisfile_asf_paddingobject['padding'] = substr($ASFHeaderData, $offset, $thisfile_asf_paddingobject['padding_length']); + break; + + case GETID3_ASF_Extended_Content_Encryption_Object: + case GETID3_ASF_Content_Encryption_Object: + // WMA DRM - just ignore + $offset += ($NextObjectSize - 16 - 8); + break; + + default: + // Implementations shall ignore any standard or non-standard object that they do not know how to handle. + if ($this->GUIDname($NextObjectGUIDtext)) { + $ThisFileInfo['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8); + } else { + $ThisFileInfo['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF header at offset '.($offset - 16 - 8); + } + $offset += ($NextObjectSize - 16 - 8); + break; + } + } + if (isset($thisfile_asf_streambitrateproperties['bitrate_records_count'])) { + $ASFbitrateAudio = 0; + $ASFbitrateVideo = 0; + for ($BitrateRecordsCounter = 0; $BitrateRecordsCounter < $thisfile_asf_streambitrateproperties['bitrate_records_count']; $BitrateRecordsCounter++) { + if (isset($thisfile_asf_codeclistobject['codec_entries'][$BitrateRecordsCounter])) { + switch ($thisfile_asf_codeclistobject['codec_entries'][$BitrateRecordsCounter]['type_raw']) { + case 1: + $ASFbitrateVideo += $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['bitrate']; + break; + + case 2: + $ASFbitrateAudio += $thisfile_asf_streambitrateproperties['bitrate_records'][$BitrateRecordsCounter]['bitrate']; + break; + + default: + // do nothing + break; + } + } + } + if ($ASFbitrateAudio > 0) { + $thisfile_audio['bitrate'] = $ASFbitrateAudio; + } + if ($ASFbitrateVideo > 0) { + $thisfile_video['bitrate'] = $ASFbitrateVideo; + } + } + if (isset($thisfile_asf['stream_properties_object']) && is_array($thisfile_asf['stream_properties_object'])) { + + foreach ($thisfile_asf['stream_properties_object'] as $streamnumber => $streamdata) { + + switch ($streamdata['stream_type']) { + case GETID3_ASF_Audio_Media: + // Field Name Field Type Size (bits) + // Codec ID / Format Tag WORD 16 // unique ID of audio codec - defined as wFormatTag field of WAVEFORMATEX structure + // Number of Channels WORD 16 // number of channels of audio - defined as nChannels field of WAVEFORMATEX structure + // Samples Per Second DWORD 32 // in Hertz - defined as nSamplesPerSec field of WAVEFORMATEX structure + // Average number of Bytes/sec DWORD 32 // bytes/sec of audio stream - defined as nAvgBytesPerSec field of WAVEFORMATEX structure + // Block Alignment WORD 16 // block size in bytes of audio codec - defined as nBlockAlign field of WAVEFORMATEX structure + // Bits per sample WORD 16 // bits per sample of mono data. set to zero for variable bitrate codecs. defined as wBitsPerSample field of WAVEFORMATEX structure + // Codec Specific Data Size WORD 16 // size in bytes of Codec Specific Data buffer - defined as cbSize field of WAVEFORMATEX structure + // Codec Specific Data BYTESTREAM variable // array of codec-specific data bytes + + // shortcut + $thisfile_asf['audio_media'][$streamnumber] = array(); + $thisfile_asf_audiomedia_currentstream = &$thisfile_asf['audio_media'][$streamnumber]; + + $audiomediaoffset = 0; + + $thisfile_asf_audiomedia_currentstream = getid3_riff::RIFFparseWAVEFORMATex(substr($streamdata['type_specific_data'], $audiomediaoffset, 16)); + $audiomediaoffset += 16; + + $thisfile_audio['lossless'] = false; + switch ($thisfile_asf_audiomedia_currentstream['raw']['wFormatTag']) { + case 0x0001: // PCM + case 0x0163: // WMA9 Lossless + $thisfile_audio['lossless'] = true; + break; + } + + if (!isset($thisfile_audio['bitrate'])) { + $thisfile_audio['bitrate'] = $thisfile_asf_audiomedia_currentstream['bytes_sec'] * 8; + } + $thisfile_audio['streams'][$streamnumber] = $thisfile_asf_audiomedia_currentstream; + $thisfile_audio['streams'][$streamnumber]['wformattag'] = $thisfile_asf_audiomedia_currentstream['raw']['wFormatTag']; + $thisfile_audio['streams'][$streamnumber]['lossless'] = $thisfile_audio['lossless']; + $thisfile_audio['streams'][$streamnumber]['bitrate'] = $thisfile_audio['bitrate']; + unset($thisfile_audio['streams'][$streamnumber]['raw']); + + $thisfile_asf_audiomedia_currentstream['codec_data_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $audiomediaoffset, 2)); + $audiomediaoffset += 2; + $thisfile_asf_audiomedia_currentstream['codec_data'] = substr($streamdata['type_specific_data'], $audiomediaoffset, $thisfile_asf_audiomedia_currentstream['codec_data_size']); + $audiomediaoffset += $thisfile_asf_audiomedia_currentstream['codec_data_size']; + + break; + + case GETID3_ASF_Video_Media: + // Field Name Field Type Size (bits) + // Encoded Image Width DWORD 32 // width of image in pixels + // Encoded Image Height DWORD 32 // height of image in pixels + // Reserved Flags BYTE 8 // hardcoded: 0x02 + // Format Data Size WORD 16 // size of Format Data field in bytes + // Format Data array of: variable // + // * Format Data Size DWORD 32 // number of bytes in Format Data field, in bytes - defined as biSize field of BITMAPINFOHEADER structure + // * Image Width LONG 32 // width of encoded image in pixels - defined as biWidth field of BITMAPINFOHEADER structure + // * Image Height LONG 32 // height of encoded image in pixels - defined as biHeight field of BITMAPINFOHEADER structure + // * Reserved WORD 16 // hardcoded: 0x0001 - defined as biPlanes field of BITMAPINFOHEADER structure + // * Bits Per Pixel Count WORD 16 // bits per pixel - defined as biBitCount field of BITMAPINFOHEADER structure + // * Compression ID FOURCC 32 // fourcc of video codec - defined as biCompression field of BITMAPINFOHEADER structure + // * Image Size DWORD 32 // image size in bytes - defined as biSizeImage field of BITMAPINFOHEADER structure + // * Horizontal Pixels / Meter DWORD 32 // horizontal resolution of target device in pixels per meter - defined as biXPelsPerMeter field of BITMAPINFOHEADER structure + // * Vertical Pixels / Meter DWORD 32 // vertical resolution of target device in pixels per meter - defined as biYPelsPerMeter field of BITMAPINFOHEADER structure + // * Colors Used Count DWORD 32 // number of color indexes in the color table that are actually used - defined as biClrUsed field of BITMAPINFOHEADER structure + // * Important Colors Count DWORD 32 // number of color index required for displaying bitmap. if zero, all colors are required. defined as biClrImportant field of BITMAPINFOHEADER structure + // * Codec Specific Data BYTESTREAM variable // array of codec-specific data bytes + + // shortcut + $thisfile_asf['video_media'][$streamnumber] = array(); + $thisfile_asf_videomedia_currentstream = &$thisfile_asf['video_media'][$streamnumber]; + + $videomediaoffset = 0; + $thisfile_asf_videomedia_currentstream['image_width'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); + $videomediaoffset += 4; + $thisfile_asf_videomedia_currentstream['image_height'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); + $videomediaoffset += 4; + $thisfile_asf_videomedia_currentstream['flags'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 1)); + $videomediaoffset += 1; + $thisfile_asf_videomedia_currentstream['format_data_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2)); + $videomediaoffset += 2; + $thisfile_asf_videomedia_currentstream['format_data']['format_data_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); + $videomediaoffset += 4; + $thisfile_asf_videomedia_currentstream['format_data']['image_width'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); + $videomediaoffset += 4; + $thisfile_asf_videomedia_currentstream['format_data']['image_height'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); + $videomediaoffset += 4; + $thisfile_asf_videomedia_currentstream['format_data']['reserved'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2)); + $videomediaoffset += 2; + $thisfile_asf_videomedia_currentstream['format_data']['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 2)); + $videomediaoffset += 2; + $thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc'] = substr($streamdata['type_specific_data'], $videomediaoffset, 4); + $videomediaoffset += 4; + $thisfile_asf_videomedia_currentstream['format_data']['image_size'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); + $videomediaoffset += 4; + $thisfile_asf_videomedia_currentstream['format_data']['horizontal_pels'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); + $videomediaoffset += 4; + $thisfile_asf_videomedia_currentstream['format_data']['vertical_pels'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); + $videomediaoffset += 4; + $thisfile_asf_videomedia_currentstream['format_data']['colors_used'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); + $videomediaoffset += 4; + $thisfile_asf_videomedia_currentstream['format_data']['colors_important'] = getid3_lib::LittleEndian2Int(substr($streamdata['type_specific_data'], $videomediaoffset, 4)); + $videomediaoffset += 4; + $thisfile_asf_videomedia_currentstream['format_data']['codec_data'] = substr($streamdata['type_specific_data'], $videomediaoffset); + + + $thisfile_asf_videomedia_currentstream['format_data']['codec'] = getid3_riff::RIFFfourccLookup($thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc']); + + $thisfile_video['fourcc'] = $thisfile_asf_videomedia_currentstream['format_data']['codec_fourcc']; + $thisfile_video['codec'] = $thisfile_asf_videomedia_currentstream['format_data']['codec']; + $thisfile_video['resolution_x'] = $thisfile_asf_videomedia_currentstream['image_width']; + $thisfile_video['resolution_y'] = $thisfile_asf_videomedia_currentstream['image_height']; + $thisfile_video['bits_per_sample'] = $thisfile_asf_videomedia_currentstream['format_data']['bits_per_pixel']; + break; + + default: + break; + } + } + } + + while (ftell($fd) < $ThisFileInfo['avdataend']) { + $NextObjectDataHeader = fread($fd, 24); + $offset = 0; + $NextObjectGUID = substr($NextObjectDataHeader, 0, 16); + $offset += 16; + $NextObjectGUIDtext = $this->BytestringToGUID($NextObjectGUID); + $NextObjectSize = getid3_lib::LittleEndian2Int(substr($NextObjectDataHeader, $offset, 8)); + $offset += 8; + + switch ($NextObjectGUID) { + case GETID3_ASF_Data_Object: + // Data Object: (mandatory, one only) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for Data object - GETID3_ASF_Data_Object + // Object Size QWORD 64 // size of Data object, including 50 bytes of Data Object header. may be 0 if FilePropertiesObject.BroadcastFlag == 1 + // File ID GUID 128 // unique identifier. identical to File ID field in Header Object + // Total Data Packets QWORD 64 // number of Data Packet entries in Data Object. invalid if FilePropertiesObject.BroadcastFlag == 1 + // Reserved WORD 16 // hardcoded: 0x0101 + + // shortcut + $thisfile_asf['data_object'] = array(); + $thisfile_asf_dataobject = &$thisfile_asf['data_object']; + + $DataObjectData = $NextObjectDataHeader.fread($fd, 50 - 24); + $offset = 24; + + $thisfile_asf_dataobject['objectid'] = $NextObjectGUID; + $thisfile_asf_dataobject['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_dataobject['objectsize'] = $NextObjectSize; + + $thisfile_asf_dataobject['fileid'] = substr($DataObjectData, $offset, 16); + $offset += 16; + $thisfile_asf_dataobject['fileid_guid'] = $this->BytestringToGUID($thisfile_asf_dataobject['fileid']); + $thisfile_asf_dataobject['total_data_packets'] = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 8)); + $offset += 8; + $thisfile_asf_dataobject['reserved'] = getid3_lib::LittleEndian2Int(substr($DataObjectData, $offset, 2)); + $offset += 2; + if ($thisfile_asf_dataobject['reserved'] != 0x0101) { + $ThisFileInfo['warning'][] = 'data_object.reserved ('.getid3_lib::PrintHexBytes($thisfile_asf_dataobject['reserved']).') does not match expected value of "0x0101"'; + //return false; + break; + } + + // Data Packets array of: variable // + // * Error Correction Flags BYTE 8 // + // * * Error Correction Data Length bits 4 // if Error Correction Length Type == 00, size of Error Correction Data in bytes, else hardcoded: 0000 + // * * Opaque Data Present bits 1 // + // * * Error Correction Length Type bits 2 // number of bits for size of the error correction data. hardcoded: 00 + // * * Error Correction Present bits 1 // If set, use Opaque Data Packet structure, else use Payload structure + // * Error Correction Data + + $ThisFileInfo['avdataoffset'] = ftell($fd); + fseek($fd, ($thisfile_asf_dataobject['objectsize'] - 50), SEEK_CUR); // skip actual audio/video data + $ThisFileInfo['avdataend'] = ftell($fd); + break; + + case GETID3_ASF_Simple_Index_Object: + // Simple Index Object: (optional, recommended, one per video stream) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for Simple Index object - GETID3_ASF_Data_Object + // Object Size QWORD 64 // size of Simple Index object, including 56 bytes of Simple Index Object header + // File ID GUID 128 // unique identifier. may be zero or identical to File ID field in Data Object and Header Object + // Index Entry Time Interval QWORD 64 // interval between index entries in 100-nanosecond units + // Maximum Packet Count DWORD 32 // maximum packet count for all index entries + // Index Entries Count DWORD 32 // number of Index Entries structures + // Index Entries array of: variable // + // * Packet Number DWORD 32 // number of the Data Packet associated with this index entry + // * Packet Count WORD 16 // number of Data Packets to sent at this index entry + + // shortcut + $thisfile_asf['simple_index_object'] = array(); + $thisfile_asf_simpleindexobject = &$thisfile_asf['simple_index_object']; + + $SimpleIndexObjectData = $NextObjectDataHeader.fread($fd, 56 - 24); + $offset = 24; + + $thisfile_asf_simpleindexobject['objectid'] = $NextObjectGUID; + $thisfile_asf_simpleindexobject['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_simpleindexobject['objectsize'] = $NextObjectSize; + + $thisfile_asf_simpleindexobject['fileid'] = substr($SimpleIndexObjectData, $offset, 16); + $offset += 16; + $thisfile_asf_simpleindexobject['fileid_guid'] = $this->BytestringToGUID($thisfile_asf_simpleindexobject['fileid']); + $thisfile_asf_simpleindexobject['index_entry_time_interval'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 8)); + $offset += 8; + $thisfile_asf_simpleindexobject['maximum_packet_count'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 4)); + $offset += 4; + $thisfile_asf_simpleindexobject['index_entries_count'] = getid3_lib::LittleEndian2Int(substr($SimpleIndexObjectData, $offset, 4)); + $offset += 4; + + $IndexEntriesData = $SimpleIndexObjectData.fread($fd, 6 * $thisfile_asf_simpleindexobject['index_entries_count']); + for ($IndexEntriesCounter = 0; $IndexEntriesCounter < $thisfile_asf_simpleindexobject['index_entries_count']; $IndexEntriesCounter++) { + $thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]['packet_number'] = getid3_lib::LittleEndian2Int(substr($IndexEntriesData, $offset, 4)); + $offset += 4; + $thisfile_asf_simpleindexobject['index_entries'][$IndexEntriesCounter]['packet_count'] = getid3_lib::LittleEndian2Int(substr($IndexEntriesData, $offset, 4)); + $offset += 2; + } + + break; + + case GETID3_ASF_Index_Object: + // 6.2 ASF top-level Index Object (optional but recommended when appropriate, 0 or 1) + // Field Name Field Type Size (bits) + // Object ID GUID 128 // GUID for the Index Object - GETID3_ASF_Index_Object + // Object Size QWORD 64 // Specifies the size, in bytes, of the Index Object, including at least 34 bytes of Index Object header + // Index Entry Time Interval DWORD 32 // Specifies the time interval between each index entry in ms. + // Index Specifiers Count WORD 16 // Specifies the number of Index Specifiers structures in this Index Object. + // Index Blocks Count DWORD 32 // Specifies the number of Index Blocks structures in this Index Object. + + // Index Entry Time Interval DWORD 32 // Specifies the time interval between index entries in milliseconds. This value cannot be 0. + // Index Specifiers Count WORD 16 // Specifies the number of entries in the Index Specifiers list. Valid values are 1 and greater. + // Index Specifiers array of: varies // + // * Stream Number WORD 16 // Specifies the stream number that the Index Specifiers refer to. Valid values are between 1 and 127. + // * Index Type WORD 16 // Specifies Index Type values as follows: + // 1 = Nearest Past Data Packet - indexes point to the data packet whose presentation time is closest to the index entry time. + // 2 = Nearest Past Media Object - indexes point to the closest data packet containing an entire object or first fragment of an object. + // 3 = Nearest Past Cleanpoint. - indexes point to the closest data packet containing an entire object (or first fragment of an object) that has the Cleanpoint Flag set. + // Nearest Past Cleanpoint is the most common type of index. + // Index Entry Count DWORD 32 // Specifies the number of Index Entries in the block. + // * Block Positions QWORD varies // Specifies a list of byte offsets of the beginnings of the blocks relative to the beginning of the first Data Packet (i.e., the beginning of the Data Object + 50 bytes). The number of entries in this list is specified by the value of the Index Specifiers Count field. The order of those byte offsets is tied to the order in which Index Specifiers are listed. + // * Index Entries array of: varies // + // * * Offsets DWORD varies // An offset value of 0xffffffff indicates an invalid offset value + + // shortcut + $thisfile_asf['asf_index_object'] = array(); + $thisfile_asf_asfindexobject = &$thisfile_asf['asf_index_object']; + + $ASFIndexObjectData = $NextObjectDataHeader.fread($fd, 34 - 24); + $offset = 24; + + $thisfile_asf_asfindexobject['objectid'] = $NextObjectGUID; + $thisfile_asf_asfindexobject['objectid_guid'] = $NextObjectGUIDtext; + $thisfile_asf_asfindexobject['objectsize'] = $NextObjectSize; + + $thisfile_asf_asfindexobject['entry_time_interval'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); + $offset += 4; + $thisfile_asf_asfindexobject['index_specifiers_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2)); + $offset += 2; + $thisfile_asf_asfindexobject['index_blocks_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); + $offset += 4; + + $ASFIndexObjectData .= fread($fd, 4 * $thisfile_asf_asfindexobject['index_specifiers_count']); + for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { + $IndexSpecifierStreamNumber = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2)); + $offset += 2; + $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['stream_number'] = $IndexSpecifierStreamNumber; + $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 2)); + $offset += 2; + $thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type_text'] = $this->ASFIndexObjectIndexTypeLookup($thisfile_asf_asfindexobject['index_specifiers'][$IndexSpecifiersCounter]['index_type']); + } + + $ASFIndexObjectData .= fread($fd, 4); + $thisfile_asf_asfindexobject['index_entry_count'] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); + $offset += 4; + + $ASFIndexObjectData .= fread($fd, 8 * $thisfile_asf_asfindexobject['index_specifiers_count']); + for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { + $thisfile_asf_asfindexobject['block_positions'][$IndexSpecifiersCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 8)); + $offset += 8; + } + + $ASFIndexObjectData .= fread($fd, 4 * $thisfile_asf_asfindexobject['index_specifiers_count'] * $thisfile_asf_asfindexobject['index_entry_count']); + for ($IndexEntryCounter = 0; $IndexEntryCounter < $thisfile_asf_asfindexobject['index_entry_count']; $IndexEntryCounter++) { + for ($IndexSpecifiersCounter = 0; $IndexSpecifiersCounter < $thisfile_asf_asfindexobject['index_specifiers_count']; $IndexSpecifiersCounter++) { + $thisfile_asf_asfindexobject['offsets'][$IndexSpecifiersCounter][$IndexEntryCounter] = getid3_lib::LittleEndian2Int(substr($ASFIndexObjectData, $offset, 4)); + $offset += 4; + } + } + break; + + + default: + // Implementations shall ignore any standard or non-standard object that they do not know how to handle. + if ($this->GUIDname($NextObjectGUIDtext)) { + $ThisFileInfo['warning'][] = 'unhandled GUID "'.$this->GUIDname($NextObjectGUIDtext).'" {'.$NextObjectGUIDtext.'} in ASF body at offset '.($offset - 16 - 8); + } else { + $ThisFileInfo['warning'][] = 'unknown GUID {'.$NextObjectGUIDtext.'} in ASF body at offset '.(ftell($fd) - 16 - 8); + } + fseek($fd, ($NextObjectSize - 16 - 8), SEEK_CUR); + break; + } + } + + if (isset($thisfile_asf_codeclistobject['codec_entries']) && is_array($thisfile_asf_codeclistobject['codec_entries'])) { + foreach ($thisfile_asf_codeclistobject['codec_entries'] as $streamnumber => $streamdata) { + switch ($streamdata['information']) { + case 'WMV1': + case 'WMV2': + case 'WMV3': + $thisfile_video['dataformat'] = 'wmv'; + $ThisFileInfo['mime_type'] = 'video/x-ms-wmv'; + break; + + case 'MP42': + case 'MP43': + case 'MP4S': + case 'mp4s': + $thisfile_video['dataformat'] = 'asf'; + $ThisFileInfo['mime_type'] = 'video/x-ms-asf'; + break; + + default: + switch ($streamdata['type_raw']) { + case 1: + if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) { + $thisfile_video['dataformat'] = 'wmv'; + if ($ThisFileInfo['mime_type'] == 'video/x-ms-asf') { + $ThisFileInfo['mime_type'] = 'video/x-ms-wmv'; + } + } + break; + + case 2: + if (strstr($this->TrimConvert($streamdata['name']), 'Windows Media')) { + $thisfile_audio['dataformat'] = 'wma'; + if ($ThisFileInfo['mime_type'] == 'video/x-ms-asf') { + $ThisFileInfo['mime_type'] = 'audio/x-ms-wma'; + } + } + break; + + } + break; + } + } + } + + switch ($thisfile_audio['codec']) { + case 'MPEG Layer-3': + $thisfile_audio['dataformat'] = 'mp3'; + break; + + default: + break; + } + + if (isset($thisfile_asf_codeclistobject['codec_entries'])) { + foreach ($thisfile_asf_codeclistobject['codec_entries'] as $streamnumber => $streamdata) { + switch ($streamdata['type_raw']) { + + case 1: // video + $thisfile_video['encoder'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][$streamnumber]['name']); + break; + + case 2: // audio + $thisfile_audio['encoder'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][$streamnumber]['name']); + + // AH 2003-10-01 + $thisfile_audio['encoder_options'] = $this->TrimConvert($thisfile_asf_codeclistobject['codec_entries'][0]['description']); + + $thisfile_audio['codec'] = $thisfile_audio['encoder']; + break; + + default: + $ThisFileInfo['warning'][] = 'Unknown streamtype: [codec_list_object][codec_entries]['.$streamnumber.'][type_raw] == '.$streamdata['type_raw']; + break; + + } + } + } + + if (isset($ThisFileInfo['audio'])) { + $thisfile_audio['lossless'] = (isset($thisfile_audio['lossless']) ? $thisfile_audio['lossless'] : false); + $thisfile_audio['dataformat'] = (!empty($thisfile_audio['dataformat']) ? $thisfile_audio['dataformat'] : 'asf'); + } + if (!empty($thisfile_video['dataformat'])) { + $thisfile_video['lossless'] = (isset($thisfile_audio['lossless']) ? $thisfile_audio['lossless'] : false); + $thisfile_video['pixel_aspect_ratio'] = (isset($thisfile_audio['pixel_aspect_ratio']) ? $thisfile_audio['pixel_aspect_ratio'] : (float) 1); + $thisfile_video['dataformat'] = (!empty($thisfile_video['dataformat']) ? $thisfile_video['dataformat'] : 'asf'); + } + + return true; + } + + function ASFCodecListObjectTypeLookup($CodecListType) { + static $ASFCodecListObjectTypeLookup = array(); + if (empty($ASFCodecListObjectTypeLookup)) { + $ASFCodecListObjectTypeLookup[0x0001] = 'Video Codec'; + $ASFCodecListObjectTypeLookup[0x0002] = 'Audio Codec'; + $ASFCodecListObjectTypeLookup[0xFFFF] = 'Unknown Codec'; + } + + return (isset($ASFCodecListObjectTypeLookup[$CodecListType]) ? $ASFCodecListObjectTypeLookup[$CodecListType] : 'Invalid Codec Type'); + } + + function KnownGUIDs() { + static $GUIDarray = array(); + if (empty($GUIDarray)) { + $GUIDarray['GETID3_ASF_Extended_Stream_Properties_Object'] = '14E6A5CB-C672-4332-8399-A96952065B5A'; + $GUIDarray['GETID3_ASF_Padding_Object'] = '1806D474-CADF-4509-A4BA-9AABCB96AAE8'; + $GUIDarray['GETID3_ASF_Payload_Ext_Syst_Pixel_Aspect_Ratio'] = '1B1EE554-F9EA-4BC8-821A-376B74E4C4B8'; + $GUIDarray['GETID3_ASF_Script_Command_Object'] = '1EFB1A30-0B62-11D0-A39B-00A0C90348F6'; + $GUIDarray['GETID3_ASF_No_Error_Correction'] = '20FB5700-5B55-11CF-A8FD-00805F5C442B'; + $GUIDarray['GETID3_ASF_Content_Branding_Object'] = '2211B3FA-BD23-11D2-B4B7-00A0C955FC6E'; + $GUIDarray['GETID3_ASF_Content_Encryption_Object'] = '2211B3FB-BD23-11D2-B4B7-00A0C955FC6E'; + $GUIDarray['GETID3_ASF_Digital_Signature_Object'] = '2211B3FC-BD23-11D2-B4B7-00A0C955FC6E'; + $GUIDarray['GETID3_ASF_Extended_Content_Encryption_Object'] = '298AE614-2622-4C17-B935-DAE07EE9289C'; + $GUIDarray['GETID3_ASF_Simple_Index_Object'] = '33000890-E5B1-11CF-89F4-00A0C90349CB'; + $GUIDarray['GETID3_ASF_Degradable_JPEG_Media'] = '35907DE0-E415-11CF-A917-00805F5C442B'; + $GUIDarray['GETID3_ASF_Payload_Extension_System_Timecode'] = '399595EC-8667-4E2D-8FDB-98814CE76C1E'; + $GUIDarray['GETID3_ASF_Binary_Media'] = '3AFB65E2-47EF-40F2-AC2C-70A90D71D343'; + $GUIDarray['GETID3_ASF_Timecode_Index_Object'] = '3CB73FD0-0C4A-4803-953D-EDF7B6228F0C'; + $GUIDarray['GETID3_ASF_Metadata_Library_Object'] = '44231C94-9498-49D1-A141-1D134E457054'; + $GUIDarray['GETID3_ASF_Reserved_3'] = '4B1ACBE3-100B-11D0-A39B-00A0C90348F6'; + $GUIDarray['GETID3_ASF_Reserved_4'] = '4CFEDB20-75F6-11CF-9C0F-00A0C90349CB'; + $GUIDarray['GETID3_ASF_Command_Media'] = '59DACFC0-59E6-11D0-A3AC-00A0C90348F6'; + $GUIDarray['GETID3_ASF_Header_Extension_Object'] = '5FBF03B5-A92E-11CF-8EE3-00C00C205365'; + $GUIDarray['GETID3_ASF_Media_Object_Index_Parameters_Obj'] = '6B203BAD-3F11-4E84-ACA8-D7613DE2CFA7'; + $GUIDarray['GETID3_ASF_Header_Object'] = '75B22630-668E-11CF-A6D9-00AA0062CE6C'; + $GUIDarray['GETID3_ASF_Content_Description_Object'] = '75B22633-668E-11CF-A6D9-00AA0062CE6C'; + $GUIDarray['GETID3_ASF_Error_Correction_Object'] = '75B22635-668E-11CF-A6D9-00AA0062CE6C'; + $GUIDarray['GETID3_ASF_Data_Object'] = '75B22636-668E-11CF-A6D9-00AA0062CE6C'; + $GUIDarray['GETID3_ASF_Web_Stream_Media_Subtype'] = '776257D4-C627-41CB-8F81-7AC7FF1C40CC'; + $GUIDarray['GETID3_ASF_Stream_Bitrate_Properties_Object'] = '7BF875CE-468D-11D1-8D82-006097C9A2B2'; + $GUIDarray['GETID3_ASF_Language_List_Object'] = '7C4346A9-EFE0-4BFC-B229-393EDE415C85'; + $GUIDarray['GETID3_ASF_Codec_List_Object'] = '86D15240-311D-11D0-A3A4-00A0C90348F6'; + $GUIDarray['GETID3_ASF_Reserved_2'] = '86D15241-311D-11D0-A3A4-00A0C90348F6'; + $GUIDarray['GETID3_ASF_File_Properties_Object'] = '8CABDCA1-A947-11CF-8EE4-00C00C205365'; + $GUIDarray['GETID3_ASF_File_Transfer_Media'] = '91BD222C-F21C-497A-8B6D-5AA86BFC0185'; + $GUIDarray['GETID3_ASF_Old_RTP_Extension_Data'] = '96800C63-4C94-11D1-837B-0080C7A37F95'; + $GUIDarray['GETID3_ASF_Advanced_Mutual_Exclusion_Object'] = 'A08649CF-4775-4670-8A16-6E35357566CD'; + $GUIDarray['GETID3_ASF_Bandwidth_Sharing_Object'] = 'A69609E6-517B-11D2-B6AF-00C04FD908E9'; + $GUIDarray['GETID3_ASF_Reserved_1'] = 'ABD3D211-A9BA-11cf-8EE6-00C00C205365'; + $GUIDarray['GETID3_ASF_Bandwidth_Sharing_Exclusive'] = 'AF6060AA-5197-11D2-B6AF-00C04FD908E9'; + $GUIDarray['GETID3_ASF_Bandwidth_Sharing_Partial'] = 'AF6060AB-5197-11D2-B6AF-00C04FD908E9'; + $GUIDarray['GETID3_ASF_JFIF_Media'] = 'B61BE100-5B4E-11CF-A8FD-00805F5C442B'; + $GUIDarray['GETID3_ASF_Stream_Properties_Object'] = 'B7DC0791-A9B7-11CF-8EE6-00C00C205365'; + $GUIDarray['GETID3_ASF_Video_Media'] = 'BC19EFC0-5B4D-11CF-A8FD-00805F5C442B'; + $GUIDarray['GETID3_ASF_Audio_Spread'] = 'BFC3CD50-618F-11CF-8BB2-00AA00B4E220'; + $GUIDarray['GETID3_ASF_Metadata_Object'] = 'C5F8CBEA-5BAF-4877-8467-AA8C44FA4CCA'; + $GUIDarray['GETID3_ASF_Payload_Ext_Syst_Sample_Duration'] = 'C6BD9450-867F-4907-83A3-C77921B733AD'; + $GUIDarray['GETID3_ASF_Group_Mutual_Exclusion_Object'] = 'D1465A40-5A79-4338-B71B-E36B8FD6C249'; + $GUIDarray['GETID3_ASF_Extended_Content_Description_Object'] = 'D2D0A440-E307-11D2-97F0-00A0C95EA850'; + $GUIDarray['GETID3_ASF_Stream_Prioritization_Object'] = 'D4FED15B-88D3-454F-81F0-ED5C45999E24'; + $GUIDarray['GETID3_ASF_Payload_Ext_System_Content_Type'] = 'D590DC20-07BC-436C-9CF7-F3BBFBF1A4DC'; + $GUIDarray['GETID3_ASF_Old_File_Properties_Object'] = 'D6E229D0-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_ASF_Header_Object'] = 'D6E229D1-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_ASF_Data_Object'] = 'D6E229D2-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Index_Object'] = 'D6E229D3-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Stream_Properties_Object'] = 'D6E229D4-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Content_Description_Object'] = 'D6E229D5-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Script_Command_Object'] = 'D6E229D6-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Marker_Object'] = 'D6E229D7-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Component_Download_Object'] = 'D6E229D8-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Stream_Group_Object'] = 'D6E229D9-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Scalable_Object'] = 'D6E229DA-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Prioritization_Object'] = 'D6E229DB-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Bitrate_Mutual_Exclusion_Object'] = 'D6E229DC-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Inter_Media_Dependency_Object'] = 'D6E229DD-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Rating_Object'] = 'D6E229DE-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Index_Parameters_Object'] = 'D6E229DF-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Color_Table_Object'] = 'D6E229E0-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Language_List_Object'] = 'D6E229E1-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Audio_Media'] = 'D6E229E2-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Video_Media'] = 'D6E229E3-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Image_Media'] = 'D6E229E4-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Timecode_Media'] = 'D6E229E5-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Text_Media'] = 'D6E229E6-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_MIDI_Media'] = 'D6E229E7-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Command_Media'] = 'D6E229E8-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_No_Error_Concealment'] = 'D6E229EA-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Scrambled_Audio'] = 'D6E229EB-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_No_Color_Table'] = 'D6E229EC-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_SMPTE_Time'] = 'D6E229ED-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_ASCII_Text'] = 'D6E229EE-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Unicode_Text'] = 'D6E229EF-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_HTML_Text'] = 'D6E229F0-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_URL_Command'] = 'D6E229F1-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Filename_Command'] = 'D6E229F2-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_ACM_Codec'] = 'D6E229F3-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_VCM_Codec'] = 'D6E229F4-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_QuickTime_Codec'] = 'D6E229F5-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_DirectShow_Transform_Filter'] = 'D6E229F6-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_DirectShow_Rendering_Filter'] = 'D6E229F7-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_No_Enhancement'] = 'D6E229F8-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Unknown_Enhancement_Type'] = 'D6E229F9-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Temporal_Enhancement'] = 'D6E229FA-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Spatial_Enhancement'] = 'D6E229FB-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Quality_Enhancement'] = 'D6E229FC-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Number_of_Channels_Enhancement'] = 'D6E229FD-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Frequency_Response_Enhancement'] = 'D6E229FE-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Media_Object'] = 'D6E229FF-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Mutex_Language'] = 'D6E22A00-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Mutex_Bitrate'] = 'D6E22A01-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Mutex_Unknown'] = 'D6E22A02-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_ASF_Placeholder_Object'] = 'D6E22A0E-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Old_Data_Unit_Extension_Object'] = 'D6E22A0F-35DA-11D1-9034-00A0C90349BE'; + $GUIDarray['GETID3_ASF_Web_Stream_Format'] = 'DA1E6B13-8359-4050-B398-388E965BF00C'; + $GUIDarray['GETID3_ASF_Payload_Ext_System_File_Name'] = 'E165EC0E-19ED-45D7-B4A7-25CBD1E28E9B'; + $GUIDarray['GETID3_ASF_Marker_Object'] = 'F487CD01-A951-11CF-8EE6-00C00C205365'; + $GUIDarray['GETID3_ASF_Timecode_Index_Parameters_Object'] = 'F55E496D-9797-4B5D-8C8B-604DFE9BFB24'; + $GUIDarray['GETID3_ASF_Audio_Media'] = 'F8699E40-5B4D-11CF-A8FD-00805F5C442B'; + $GUIDarray['GETID3_ASF_Media_Object_Index_Object'] = 'FEB103F8-12AD-4C64-840F-2A1D2F7AD48C'; + $GUIDarray['GETID3_ASF_Alt_Extended_Content_Encryption_Obj'] = 'FF889EF1-ADEE-40DA-9E71-98704BB928CE'; + } + return $GUIDarray; + } + + function GUIDname($GUIDstring) { + static $GUIDarray = array(); + if (empty($GUIDarray)) { + $GUIDarray = $this->KnownGUIDs(); + } + return array_search($GUIDstring, $GUIDarray); + } + + function ASFIndexObjectIndexTypeLookup($id) { + static $ASFIndexObjectIndexTypeLookup = array(); + if (empty($ASFIndexObjectIndexTypeLookup)) { + $ASFIndexObjectIndexTypeLookup[1] = 'Nearest Past Data Packet'; + $ASFIndexObjectIndexTypeLookup[2] = 'Nearest Past Media Object'; + $ASFIndexObjectIndexTypeLookup[3] = 'Nearest Past Cleanpoint'; + } + return (isset($ASFIndexObjectIndexTypeLookup[$id]) ? $ASFIndexObjectIndexTypeLookup[$id] : 'invalid'); + } + + function GUIDtoBytestring($GUIDstring) { + // Microsoft defines these 16-byte (128-bit) GUIDs in the strangest way: + // first 4 bytes are in little-endian order + // next 2 bytes are appended in little-endian order + // next 2 bytes are appended in little-endian order + // next 2 bytes are appended in big-endian order + // next 6 bytes are appended in big-endian order + + // AaBbCcDd-EeFf-GgHh-IiJj-KkLlMmNnOoPp is stored as this 16-byte string: + // $Dd $Cc $Bb $Aa $Ff $Ee $Hh $Gg $Ii $Jj $Kk $Ll $Mm $Nn $Oo $Pp + + $hexbytecharstring = chr(hexdec(substr($GUIDstring, 6, 2))); + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 4, 2))); + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 2, 2))); + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 0, 2))); + + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 11, 2))); + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 9, 2))); + + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 16, 2))); + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 14, 2))); + + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 19, 2))); + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 21, 2))); + + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 24, 2))); + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 26, 2))); + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 28, 2))); + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 30, 2))); + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 32, 2))); + $hexbytecharstring .= chr(hexdec(substr($GUIDstring, 34, 2))); + + return $hexbytecharstring; + } + + function BytestringToGUID($Bytestring) { + $GUIDstring = str_pad(dechex(ord($Bytestring{3})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring{2})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring{1})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring{0})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= '-'; + $GUIDstring .= str_pad(dechex(ord($Bytestring{5})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring{4})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= '-'; + $GUIDstring .= str_pad(dechex(ord($Bytestring{7})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring{6})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= '-'; + $GUIDstring .= str_pad(dechex(ord($Bytestring{8})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring{9})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= '-'; + $GUIDstring .= str_pad(dechex(ord($Bytestring{10})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring{11})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring{12})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring{13})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring{14})), 2, '0', STR_PAD_LEFT); + $GUIDstring .= str_pad(dechex(ord($Bytestring{15})), 2, '0', STR_PAD_LEFT); + + return strtoupper($GUIDstring); + } + + function FILETIMEtoUNIXtime($FILETIME, $round=true) { + // FILETIME is a 64-bit unsigned integer representing + // the number of 100-nanosecond intervals since January 1, 1601 + // UNIX timestamp is number of seconds since January 1, 1970 + // 116444736000000000 = 10000000 * 60 * 60 * 24 * 365 * 369 + 89 leap days + if ($round) { + return intval(round(($FILETIME - 116444736000000000) / 10000000)); + } + return ($FILETIME - 116444736000000000) / 10000000; + } + + function WMpictureTypeLookup($WMpictureType) { + static $WMpictureTypeLookup = array(); + if (empty($WMpictureTypeLookup)) { + $WMpictureTypeLookup[0x03] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Front Cover'); + $WMpictureTypeLookup[0x04] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Back Cover'); + $WMpictureTypeLookup[0x00] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'User Defined'); + $WMpictureTypeLookup[0x05] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Leaflet Page'); + $WMpictureTypeLookup[0x06] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Media Label'); + $WMpictureTypeLookup[0x07] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Lead Artist'); + $WMpictureTypeLookup[0x08] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Artist'); + $WMpictureTypeLookup[0x09] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Conductor'); + $WMpictureTypeLookup[0x0A] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Band'); + $WMpictureTypeLookup[0x0B] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Composer'); + $WMpictureTypeLookup[0x0C] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Lyricist'); + $WMpictureTypeLookup[0x0D] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Recording Location'); + $WMpictureTypeLookup[0x0E] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'During Recording'); + $WMpictureTypeLookup[0x0F] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'During Performance'); + $WMpictureTypeLookup[0x10] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Video Screen Capture'); + $WMpictureTypeLookup[0x12] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Illustration'); + $WMpictureTypeLookup[0x13] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Band Logotype'); + $WMpictureTypeLookup[0x14] = getid3_lib::iconv_fallback('ISO-8859-1', 'UTF-16LE', 'Publisher Logotype'); + } + return @$WMpictureTypeLookup[$WMpictureType]; + } + + + // Remove terminator 00 00 and convert UNICODE to Latin-1 + function TrimConvert($string) { + + // remove terminator, only if present (it should be, but...) + if (substr($string, strlen($string) - 2, 2) == "\x00\x00") { + $string = substr($string, 0, strlen($string) - 2); + } + + // convert + return trim(getid3_lib::iconv_fallback('UTF-16LE', 'ISO-8859-1', $string), ' '); + } + + + function TrimTerm($string) { + + // remove terminator, only if present (it should be, but...) + if (substr($string, -2) == "\x00\x00") { + $string = substr($string, 0, -2); + } + return $string; + } + + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio-video.bink.php b/modules/id3/getid3/module.audio-video.bink.php new file mode 100644 index 00000000..2ebc6fe7 --- /dev/null +++ b/modules/id3/getid3/module.audio-video.bink.php @@ -0,0 +1,70 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.bink.php // +// module for analyzing Bink or Smacker audio-video files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_bink +{ + + function getid3_bink(&$fd, &$ThisFileInfo) { + +$ThisFileInfo['error'][] = 'Bink / Smacker files not properly processed by this version of getID3()'; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $fileTypeID = fread($fd, 3); + switch ($fileTypeID) { + case 'BIK': + return $this->ParseBink($fd, $ThisFileInfo); + break; + + case 'SMK': + return $this->ParseSmacker($fd, $ThisFileInfo); + break; + + default: + $ThisFileInfo['error'][] = 'Expecting "BIK" or "SMK" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$fileTypeID.'"'; + return false; + break; + } + + return true; + + } + + function ParseBink(&$fd, &$ThisFileInfo) { + $ThisFileInfo['fileformat'] = 'bink'; + $ThisFileInfo['video']['dataformat'] = 'bink'; + + $fileData = 'BIK'.fread($fd, 13); + + $ThisFileInfo['bink']['data_size'] = getid3_lib::LittleEndian2Int(substr($fileData, 4, 4)); + $ThisFileInfo['bink']['frame_count'] = getid3_lib::LittleEndian2Int(substr($fileData, 8, 2)); + + if (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) != ($ThisFileInfo['bink']['data_size'] + 8)) { + $ThisFileInfo['error'][] = 'Probably truncated file: expecting '.$ThisFileInfo['bink']['data_size'].' bytes, found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']); + } + + return true; + } + + function ParseSmacker(&$fd, &$ThisFileInfo) { + $ThisFileInfo['fileformat'] = 'smacker'; + $ThisFileInfo['video']['dataformat'] = 'smacker'; + + return false; + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio-video.matroska.php b/modules/id3/getid3/module.audio-video.matroska.php new file mode 100644 index 00000000..11ac9290 --- /dev/null +++ b/modules/id3/getid3/module.audio-video.matroska.php @@ -0,0 +1,78 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.matriska.php // +// module for analyzing Matroska containers // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_matroska +{ + + function getid3_matroska(&$fd, &$ThisFileInfo) { + + $ThisFileInfo['fileformat'] = 'matroska'; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + + //$ThisFileInfo['matroska']['raw']['a'] = $this->EBML2Int(fread($fd, 4)); + + $ThisFileInfo['error'][] = 'Mastroka parsing not enabled in this version of getID3()'; + return false; + + } + + + function EBML2Int($EBMLstring) { + // http://matroska.org/specs/ + + // Element ID coded with an UTF-8 like system: + // 1xxx xxxx - Class A IDs (2^7 -2 possible values) (base 0x8X) + // 01xx xxxx xxxx xxxx - Class B IDs (2^14-2 possible values) (base 0x4X 0xXX) + // 001x xxxx xxxx xxxx xxxx xxxx - Class C IDs (2^21-2 possible values) (base 0x2X 0xXX 0xXX) + // 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - Class D IDs (2^28-2 possible values) (base 0x1X 0xXX 0xXX 0xXX) + // Values with all x at 0 and 1 are reserved (hence the -2). + + // Data size, in octets, is also coded with an UTF-8 like system : + // 1xxx xxxx - value 0 to 2^7-2 + // 01xx xxxx xxxx xxxx - value 0 to 2^14-2 + // 001x xxxx xxxx xxxx xxxx xxxx - value 0 to 2^21-2 + // 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^28-2 + // 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^35-2 + // 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^42-2 + // 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^49-2 + // 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx - value 0 to 2^56-2 + + if (0x80 & ord($EBMLstring{0})) { + $EBMLstring{0} = chr(ord($EBMLstring{0}) & 0x7F); + } elseif (0x40 & ord($EBMLstring{0})) { + $EBMLstring{0} = chr(ord($EBMLstring{0}) & 0x3F); + } elseif (0x20 & ord($EBMLstring{0})) { + $EBMLstring{0} = chr(ord($EBMLstring{0}) & 0x1F); + } elseif (0x10 & ord($EBMLstring{0})) { + $EBMLstring{0} = chr(ord($EBMLstring{0}) & 0x0F); + } elseif (0x08 & ord($EBMLstring{0})) { + $EBMLstring{0} = chr(ord($EBMLstring{0}) & 0x07); + } elseif (0x04 & ord($EBMLstring{0})) { + $EBMLstring{0} = chr(ord($EBMLstring{0}) & 0x03); + } elseif (0x02 & ord($EBMLstring{0})) { + $EBMLstring{0} = chr(ord($EBMLstring{0}) & 0x01); + } elseif (0x01 & ord($EBMLstring{0})) { + $EBMLstring{0} = chr(ord($EBMLstring{0}) & 0x00); + } else { + return false; + } + return getid3_lib::BigEndian2Int($EBMLstring); + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio-video.mpeg.php b/modules/id3/getid3/module.audio-video.mpeg.php new file mode 100644 index 00000000..8f487848 --- /dev/null +++ b/modules/id3/getid3/module.audio-video.mpeg.php @@ -0,0 +1,292 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.mpeg.php // +// module for analyzing MPEG files // +// dependencies: module.audio.mp3.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); + +define('GETID3_MPEG_VIDEO_PICTURE_START', "\x00\x00\x01\x00"); +define('GETID3_MPEG_VIDEO_USER_DATA_START', "\x00\x00\x01\xB2"); +define('GETID3_MPEG_VIDEO_SEQUENCE_HEADER', "\x00\x00\x01\xB3"); +define('GETID3_MPEG_VIDEO_SEQUENCE_ERROR', "\x00\x00\x01\xB4"); +define('GETID3_MPEG_VIDEO_EXTENSION_START', "\x00\x00\x01\xB5"); +define('GETID3_MPEG_VIDEO_SEQUENCE_END', "\x00\x00\x01\xB7"); +define('GETID3_MPEG_VIDEO_GROUP_START', "\x00\x00\x01\xB8"); +define('GETID3_MPEG_AUDIO_START', "\x00\x00\x01\xC0"); + + +class getid3_mpeg +{ + + function getid3_mpeg(&$fd, &$ThisFileInfo) { + if ($ThisFileInfo['avdataend'] <= $ThisFileInfo['avdataoffset']) { + $ThisFileInfo['error'][] = '"avdataend" ('.$ThisFileInfo['avdataend'].') is unexpectedly less-than-or-equal-to "avdataoffset" ('.$ThisFileInfo['avdataoffset'].')'; + return false; + } + $ThisFileInfo['fileformat'] = 'mpeg'; + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $MPEGstreamData = fread($fd, min(100000, $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])); + $MPEGstreamDataLength = strlen($MPEGstreamData); + + $foundVideo = true; + $VideoChunkOffset = 0; + while (substr($MPEGstreamData, $VideoChunkOffset++, 4) !== GETID3_MPEG_VIDEO_SEQUENCE_HEADER) { + if ($VideoChunkOffset >= $MPEGstreamDataLength) { + $foundVideo = false; + break; + } + } + if ($foundVideo) { + + // Start code 32 bits + // horizontal frame size 12 bits + // vertical frame size 12 bits + // pixel aspect ratio 4 bits + // frame rate 4 bits + // bitrate 18 bits + // marker bit 1 bit + // VBV buffer size 10 bits + // constrained parameter flag 1 bit + // intra quant. matrix flag 1 bit + // intra quant. matrix values 512 bits (present if matrix flag == 1) + // non-intra quant. matrix flag 1 bit + // non-intra quant. matrix values 512 bits (present if matrix flag == 1) + + $ThisFileInfo['video']['dataformat'] = 'mpeg'; + + $VideoChunkOffset += (strlen(GETID3_MPEG_VIDEO_SEQUENCE_HEADER) - 1); + + $FrameSizeDWORD = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 3)); + $VideoChunkOffset += 3; + + $AspectRatioFrameRateDWORD = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 1)); + $VideoChunkOffset += 1; + + $assortedinformation = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 4)); + $VideoChunkOffset += 4; + + $ThisFileInfo['mpeg']['video']['raw']['framesize_horizontal'] = ($FrameSizeDWORD & 0xFFF000) >> 12; // 12 bits for horizontal frame size + $ThisFileInfo['mpeg']['video']['raw']['framesize_vertical'] = ($FrameSizeDWORD & 0x000FFF); // 12 bits for vertical frame size + $ThisFileInfo['mpeg']['video']['raw']['pixel_aspect_ratio'] = ($AspectRatioFrameRateDWORD & 0xF0) >> 4; + $ThisFileInfo['mpeg']['video']['raw']['frame_rate'] = ($AspectRatioFrameRateDWORD & 0x0F); + + $ThisFileInfo['mpeg']['video']['framesize_horizontal'] = $ThisFileInfo['mpeg']['video']['raw']['framesize_horizontal']; + $ThisFileInfo['mpeg']['video']['framesize_vertical'] = $ThisFileInfo['mpeg']['video']['raw']['framesize_vertical']; + + $ThisFileInfo['mpeg']['video']['pixel_aspect_ratio'] = $this->MPEGvideoAspectRatioLookup($ThisFileInfo['mpeg']['video']['raw']['pixel_aspect_ratio']); + $ThisFileInfo['mpeg']['video']['pixel_aspect_ratio_text'] = $this->MPEGvideoAspectRatioTextLookup($ThisFileInfo['mpeg']['video']['raw']['pixel_aspect_ratio']); + $ThisFileInfo['mpeg']['video']['frame_rate'] = $this->MPEGvideoFramerateLookup($ThisFileInfo['mpeg']['video']['raw']['frame_rate']); + + $ThisFileInfo['mpeg']['video']['raw']['bitrate'] = getid3_lib::Bin2Dec(substr($assortedinformation, 0, 18)); + $ThisFileInfo['mpeg']['video']['raw']['marker_bit'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 18, 1)); + $ThisFileInfo['mpeg']['video']['raw']['vbv_buffer_size'] = getid3_lib::Bin2Dec(substr($assortedinformation, 19, 10)); + $ThisFileInfo['mpeg']['video']['raw']['constrained_param_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 29, 1)); + $ThisFileInfo['mpeg']['video']['raw']['intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 30, 1)); + if ($ThisFileInfo['mpeg']['video']['raw']['intra_quant_flag']) { + + // read 512 bits + $ThisFileInfo['mpeg']['video']['raw']['intra_quant'] = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)); + $VideoChunkOffset += 64; + + $ThisFileInfo['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($ThisFileInfo['mpeg']['video']['raw']['intra_quant'], 511, 1)); + $ThisFileInfo['mpeg']['video']['raw']['intra_quant'] = getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)).substr(getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)), 0, 511); + + if ($ThisFileInfo['mpeg']['video']['raw']['non_intra_quant_flag']) { + $ThisFileInfo['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); + $VideoChunkOffset += 64; + } + + } else { + + $ThisFileInfo['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)); + if ($ThisFileInfo['mpeg']['video']['raw']['non_intra_quant_flag']) { + $ThisFileInfo['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64); + $VideoChunkOffset += 64; + } + + } + + if ($ThisFileInfo['mpeg']['video']['raw']['bitrate'] == 0x3FFFF) { // 18 set bits + + $ThisFileInfo['warning'][] = 'This version of getID3() ['.GETID3_VERSION.'] cannot determine average bitrate of VBR MPEG video files'; + $ThisFileInfo['mpeg']['video']['bitrate_mode'] = 'vbr'; + + } else { + + $ThisFileInfo['mpeg']['video']['bitrate'] = $ThisFileInfo['mpeg']['video']['raw']['bitrate'] * 400; + $ThisFileInfo['mpeg']['video']['bitrate_mode'] = 'cbr'; + $ThisFileInfo['video']['bitrate'] = $ThisFileInfo['mpeg']['video']['bitrate']; + + } + + $ThisFileInfo['video']['resolution_x'] = $ThisFileInfo['mpeg']['video']['framesize_horizontal']; + $ThisFileInfo['video']['resolution_y'] = $ThisFileInfo['mpeg']['video']['framesize_vertical']; + $ThisFileInfo['video']['frame_rate'] = $ThisFileInfo['mpeg']['video']['frame_rate']; + $ThisFileInfo['video']['bitrate_mode'] = $ThisFileInfo['mpeg']['video']['bitrate_mode']; + $ThisFileInfo['video']['pixel_aspect_ratio'] = $ThisFileInfo['mpeg']['video']['pixel_aspect_ratio']; + $ThisFileInfo['video']['lossless'] = false; + $ThisFileInfo['video']['bits_per_sample'] = 24; + + } else { + + $ThisFileInfo['error'][] = 'Could not find start of video block in the first 100,000 bytes (or before end of file) - this might not be an MPEG-video file?'; + + } + + //0x000001B3 begins the sequence_header of every MPEG video stream. + //But in MPEG-2, this header must immediately be followed by an + //extension_start_code (0x000001B5) with a sequence_extension ID (1). + //(This extension contains all the additional MPEG-2 stuff.) + //MPEG-1 doesn't have this extension, so that's a sure way to tell the + //difference between MPEG-1 and MPEG-2 video streams. + + if (substr($MPEGstreamData, $VideoChunkOffset, 4) == GETID3_MPEG_VIDEO_EXTENSION_START) { + $ThisFileInfo['video']['codec'] = 'MPEG-2'; + } else { + $ThisFileInfo['video']['codec'] = 'MPEG-1'; + } + + + $AudioChunkOffset = 0; + while (true) { + while (substr($MPEGstreamData, $AudioChunkOffset++, 4) !== GETID3_MPEG_AUDIO_START) { + if ($AudioChunkOffset >= $MPEGstreamDataLength) { + break 2; + } + } + + for ($i = 0; $i <= 7; $i++) { + // some files have the MPEG-audio header 8 bytes after the end of the $00 $00 $01 $C0 signature, some have it up to 13 bytes (or more?) after + // I have no idea why or what the difference is, so this is a stupid hack. + // If anybody has any better idea of what's going on, please let me know - info@getid3.org + + $dummy = $ThisFileInfo; + if (getid3_mp3::decodeMPEGaudioHeader($fd, ($AudioChunkOffset + 3) + 8 + $i, $dummy, false)) { + $ThisFileInfo = $dummy; + $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + $ThisFileInfo['audio']['lossless'] = false; + break 2; + + } + } + } + + // Temporary hack to account for interleaving overhead: + if (!empty($ThisFileInfo['video']['bitrate']) && !empty($ThisFileInfo['audio']['bitrate'])) { + $ThisFileInfo['playtime_seconds'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / ($ThisFileInfo['video']['bitrate'] + $ThisFileInfo['audio']['bitrate']); + + // Interleaved MPEG audio/video files have a certain amount of overhead that varies + // by both video and audio bitrates, and not in any sensible, linear/logarithmic patter + // Use interpolated lookup tables to approximately guess how much is overhead, because + // playtime is calculated as filesize / total-bitrate + $ThisFileInfo['playtime_seconds'] *= $this->MPEGsystemNonOverheadPercentage($ThisFileInfo['video']['bitrate'], $ThisFileInfo['audio']['bitrate']); + + //switch ($ThisFileInfo['video']['bitrate']) { + // case('5000000'): + // $multiplier = 0.93292642112380355828048824319889; + // break; + // case('5500000'): + // $multiplier = 0.93582895375200989965359777343219; + // break; + // case('6000000'): + // $multiplier = 0.93796247714820932532911373859139; + // break; + // case('7000000'): + // $multiplier = 0.9413264083635103463010117778776; + // break; + // default: + // $multiplier = 1; + // break; + //} + //$ThisFileInfo['playtime_seconds'] *= $multiplier; + //$ThisFileInfo['warning'][] = 'Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.'; + if ($ThisFileInfo['video']['bitrate'] < 50000) { + $ThisFileInfo['warning'][] = 'Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.'; + } + } + + return true; + } + + + function MPEGsystemNonOverheadPercentage($VideoBitrate, $AudioBitrate) { + $OverheadPercentage = 0; + + $AudioBitrate = max(min($AudioBitrate / 1000, 384), 32); // limit to range of 32kbps - 384kbps (should be only legal bitrates, but maybe VBR?) + $VideoBitrate = max(min($VideoBitrate / 1000, 10000), 10); // limit to range of 10kbps - 10Mbps (beyond that curves flatten anyways, no big loss) + + + //OMBB[audiobitrate] = array(video-10kbps, video-100kbps, video-1000kbps, video-10000kbps) + $OverheadMultiplierByBitrate[32] = array(0, 0.9676287944368530, 0.9802276264360310, 0.9844916183244460, 0.9852821845179940); + $OverheadMultiplierByBitrate[48] = array(0, 0.9779100089209830, 0.9787770035359320, 0.9846738664076130, 0.9852683013799960); + $OverheadMultiplierByBitrate[56] = array(0, 0.9731249855367600, 0.9776624308938040, 0.9832606361852130, 0.9843922606633340); + $OverheadMultiplierByBitrate[64] = array(0, 0.9755642683275760, 0.9795256705493390, 0.9836573009193170, 0.9851122539404470); + $OverheadMultiplierByBitrate[96] = array(0, 0.9788025247497290, 0.9798553314148700, 0.9822956869792560, 0.9834815119124690); + $OverheadMultiplierByBitrate[128] = array(0, 0.9816940050925480, 0.9821675936072120, 0.9829756927470870, 0.9839763420152050); + $OverheadMultiplierByBitrate[160] = array(0, 0.9825894094561180, 0.9820913399073960, 0.9823907143253970, 0.9832821783651570); + $OverheadMultiplierByBitrate[192] = array(0, 0.9832038474336260, 0.9825731694317960, 0.9821028622712400, 0.9828262076447620); + $OverheadMultiplierByBitrate[224] = array(0, 0.9836516298538770, 0.9824718601823890, 0.9818302180625380, 0.9823735101626480); + $OverheadMultiplierByBitrate[256] = array(0, 0.9845863022094920, 0.9837229411967540, 0.9824521662210830, 0.9828645172100790); + $OverheadMultiplierByBitrate[320] = array(0, 0.9849565280263180, 0.9837683142805110, 0.9822885275960400, 0.9824424382727190); + $OverheadMultiplierByBitrate[384] = array(0, 0.9856094774357600, 0.9844573394432720, 0.9825970399837330, 0.9824673808303890); + + $BitrateToUseMin = 32; + $BitrateToUseMax = 32; + $previousBitrate = 32; + foreach ($OverheadMultiplierByBitrate as $key => $value) { + if ($AudioBitrate >= $previousBitrate) { + $BitrateToUseMin = $previousBitrate; + } + if ($AudioBitrate < $key) { + $BitrateToUseMax = $key; + break; + } + $previousBitrate = $key; + } + $FactorA = ($BitrateToUseMax - $AudioBitrate) / ($BitrateToUseMax - $BitrateToUseMin); + + $VideoBitrateLog10 = log10($VideoBitrate); + $VideoFactorMin1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][floor($VideoBitrateLog10)]; + $VideoFactorMin2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][floor($VideoBitrateLog10)]; + $VideoFactorMax1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][ceil($VideoBitrateLog10)]; + $VideoFactorMax2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][ceil($VideoBitrateLog10)]; + $FactorV = $VideoBitrateLog10 - floor($VideoBitrateLog10); + + $OverheadPercentage = $VideoFactorMin1 * $FactorA * $FactorV; + $OverheadPercentage += $VideoFactorMin2 * (1 - $FactorA) * $FactorV; + $OverheadPercentage += $VideoFactorMax1 * $FactorA * (1 - $FactorV); + $OverheadPercentage += $VideoFactorMax2 * (1 - $FactorA) * (1 - $FactorV); + + return $OverheadPercentage; + } + + + function MPEGvideoFramerateLookup($rawframerate) { + $MPEGvideoFramerateLookup = array(0, 23.976, 24, 25, 29.97, 30, 50, 59.94, 60); + return (isset($MPEGvideoFramerateLookup[$rawframerate]) ? (float) $MPEGvideoFramerateLookup[$rawframerate] : (float) 0); + } + + function MPEGvideoAspectRatioLookup($rawaspectratio) { + $MPEGvideoAspectRatioLookup = array(0, 1, 0.6735, 0.7031, 0.7615, 0.8055, 0.8437, 0.8935, 0.9157, 0.9815, 1.0255, 1.0695, 1.0950, 1.1575, 1.2015, 0); + return (isset($MPEGvideoAspectRatioLookup[$rawaspectratio]) ? (float) $MPEGvideoAspectRatioLookup[$rawaspectratio] : (float) 0); + } + + function MPEGvideoAspectRatioTextLookup($rawaspectratio) { + $MPEGvideoAspectRatioTextLookup = array('forbidden', 'square pixels', '0.6735', '16:9, 625 line, PAL', '0.7615', '0.8055', '16:9, 525 line, NTSC', '0.8935', '4:3, 625 line, PAL, CCIR601', '0.9815', '1.0255', '1.0695', '4:3, 525 line, NTSC, CCIR601', '1.1575', '1.2015', 'reserved'); + return (isset($MPEGvideoAspectRatioTextLookup[$rawaspectratio]) ? $MPEGvideoAspectRatioTextLookup[$rawaspectratio] : ''); + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio-video.nsv.php b/modules/id3/getid3/module.audio-video.nsv.php new file mode 100644 index 00000000..dab03389 --- /dev/null +++ b/modules/id3/getid3/module.audio-video.nsv.php @@ -0,0 +1,224 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.nsv.php // +// module for analyzing Nullsoft NSV files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_nsv +{ + + function getid3_nsv(&$fd, &$ThisFileInfo) { + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $NSVheader = fread($fd, 4); + + switch ($NSVheader) { + case 'NSVs': + if ($this->getNSVsHeaderFilepointer($fd, $ThisFileInfo, 0)) { + $ThisFileInfo['fileformat'] = 'nsv'; + $ThisFileInfo['audio']['dataformat'] = 'nsv'; + $ThisFileInfo['video']['dataformat'] = 'nsv'; + $ThisFileInfo['audio']['lossless'] = false; + $ThisFileInfo['video']['lossless'] = false; + } + break; + + case 'NSVf': + if ($this->getNSVfHeaderFilepointer($fd, $ThisFileInfo, 0)) { + $ThisFileInfo['fileformat'] = 'nsv'; + $ThisFileInfo['audio']['dataformat'] = 'nsv'; + $ThisFileInfo['video']['dataformat'] = 'nsv'; + $ThisFileInfo['audio']['lossless'] = false; + $ThisFileInfo['video']['lossless'] = false; + $this->getNSVsHeaderFilepointer($fd, $ThisFileInfo, $ThisFileInfo['nsv']['NSVf']['header_length']); + } + break; + + default: + $ThisFileInfo['error'][] = 'Expecting "NSVs" or "NSVf" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$NSVheader.'"'; + return false; + break; + } + + if (!isset($ThisFileInfo['nsv']['NSVf'])) { + $ThisFileInfo['warning'][] = 'NSVf header not present - cannot calculate playtime or bitrate'; + } + + return true; + } + + function getNSVsHeaderFilepointer(&$fd, &$ThisFileInfo, $fileoffset) { + fseek($fd, $fileoffset, SEEK_SET); + $NSVsheader = fread($fd, 28); + $offset = 0; + + $ThisFileInfo['nsv']['NSVs']['identifier'] = substr($NSVsheader, $offset, 4); + $offset += 4; + + if ($ThisFileInfo['nsv']['NSVs']['identifier'] != 'NSVs') { + $ThisFileInfo['error'][] = 'expected "NSVs" at offset ('.$fileoffset.'), found "'.$ThisFileInfo['nsv']['NSVs']['identifier'].'" instead'; + unset($ThisFileInfo['nsv']['NSVs']); + return false; + } + + $ThisFileInfo['nsv']['NSVs']['offset'] = $fileoffset; + + $ThisFileInfo['nsv']['NSVs']['video_codec'] = substr($NSVsheader, $offset, 4); + $offset += 4; + $ThisFileInfo['nsv']['NSVs']['audio_codec'] = substr($NSVsheader, $offset, 4); + $offset += 4; + $ThisFileInfo['nsv']['NSVs']['resolution_x'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2)); + $offset += 2; + $ThisFileInfo['nsv']['NSVs']['resolution_y'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2)); + $offset += 2; + + $ThisFileInfo['nsv']['NSVs']['framerate_index'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$ThisFileInfo['nsv']['NSVs']['unknown1b'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$ThisFileInfo['nsv']['NSVs']['unknown1c'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$ThisFileInfo['nsv']['NSVs']['unknown1d'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$ThisFileInfo['nsv']['NSVs']['unknown2a'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$ThisFileInfo['nsv']['NSVs']['unknown2b'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$ThisFileInfo['nsv']['NSVs']['unknown2c'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + //$ThisFileInfo['nsv']['NSVs']['unknown2d'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + + switch ($ThisFileInfo['nsv']['NSVs']['audio_codec']) { + case 'PCM ': + $ThisFileInfo['nsv']['NSVs']['bits_channel'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + $ThisFileInfo['nsv']['NSVs']['channels'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 1)); + $offset += 1; + $ThisFileInfo['nsv']['NSVs']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 2)); + $offset += 2; + + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['nsv']['NSVs']['sample_rate']; + break; + + case 'MP3 ': + case 'NONE': + default: + //$ThisFileInfo['nsv']['NSVs']['unknown3'] = getid3_lib::LittleEndian2Int(substr($NSVsheader, $offset, 4)); + $offset += 4; + break; + } + + $ThisFileInfo['video']['resolution_x'] = $ThisFileInfo['nsv']['NSVs']['resolution_x']; + $ThisFileInfo['video']['resolution_y'] = $ThisFileInfo['nsv']['NSVs']['resolution_y']; + $ThisFileInfo['nsv']['NSVs']['frame_rate'] = $this->NSVframerateLookup($ThisFileInfo['nsv']['NSVs']['framerate_index']); + $ThisFileInfo['video']['frame_rate'] = $ThisFileInfo['nsv']['NSVs']['frame_rate']; + $ThisFileInfo['video']['bits_per_sample'] = 24; + $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1; + + return true; + } + + function getNSVfHeaderFilepointer(&$fd, &$ThisFileInfo, $fileoffset, $getTOCoffsets=false) { + fseek($fd, $fileoffset, SEEK_SET); + $NSVfheader = fread($fd, 28); + $offset = 0; + + $ThisFileInfo['nsv']['NSVf']['identifier'] = substr($NSVfheader, $offset, 4); + $offset += 4; + + if ($ThisFileInfo['nsv']['NSVf']['identifier'] != 'NSVf') { + $ThisFileInfo['error'][] = 'expected "NSVf" at offset ('.$fileoffset.'), found "'.$ThisFileInfo['nsv']['NSVf']['identifier'].'" instead'; + unset($ThisFileInfo['nsv']['NSVf']); + return false; + } + + $ThisFileInfo['nsv']['NSVs']['offset'] = $fileoffset; + + $ThisFileInfo['nsv']['NSVf']['header_length'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + $ThisFileInfo['nsv']['NSVf']['file_size'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + + if ($ThisFileInfo['nsv']['NSVf']['file_size'] > $ThisFileInfo['avdataend']) { + $ThisFileInfo['warning'][] = 'truncated file - NSVf header indicates '.$ThisFileInfo['nsv']['NSVf']['file_size'].' bytes, file actually '.$ThisFileInfo['avdataend'].' bytes'; + } + + $ThisFileInfo['nsv']['NSVf']['playtime_ms'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + $ThisFileInfo['nsv']['NSVf']['meta_size'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + $ThisFileInfo['nsv']['NSVf']['TOC_entries_1'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + $ThisFileInfo['nsv']['NSVf']['TOC_entries_2'] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + + if ($ThisFileInfo['nsv']['NSVf']['playtime_ms'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt NSV file: NSVf.playtime_ms == zero'; + return false; + } + + $NSVfheader .= fread($fd, $ThisFileInfo['nsv']['NSVf']['meta_size'] + (4 * $ThisFileInfo['nsv']['NSVf']['TOC_entries_1']) + (4 * $ThisFileInfo['nsv']['NSVf']['TOC_entries_2'])); + $NSVfheaderlength = strlen($NSVfheader); + $ThisFileInfo['nsv']['NSVf']['metadata'] = substr($NSVfheader, $offset, $ThisFileInfo['nsv']['NSVf']['meta_size']); + $offset += $ThisFileInfo['nsv']['NSVf']['meta_size']; + + if ($getTOCoffsets) { + $TOCcounter = 0; + while ($TOCcounter < $ThisFileInfo['nsv']['NSVf']['TOC_entries_1']) { + if ($TOCcounter < $ThisFileInfo['nsv']['NSVf']['TOC_entries_1']) { + $ThisFileInfo['nsv']['NSVf']['TOC_1'][$TOCcounter] = getid3_lib::LittleEndian2Int(substr($NSVfheader, $offset, 4)); + $offset += 4; + $TOCcounter++; + } + } + } + + if (trim($ThisFileInfo['nsv']['NSVf']['metadata']) != '') { + $ThisFileInfo['nsv']['NSVf']['metadata'] = str_replace('`', "\x01", $ThisFileInfo['nsv']['NSVf']['metadata']); + $CommentPairArray = explode("\x01".' ', $ThisFileInfo['nsv']['NSVf']['metadata']); + foreach ($CommentPairArray as $CommentPair) { + if (strstr($CommentPair, '='."\x01")) { + list($key, $value) = explode('='."\x01", $CommentPair, 2); + $ThisFileInfo['nsv']['comments'][strtolower($key)][] = trim(str_replace("\x01", '', $value)); + } + } + } + + $ThisFileInfo['playtime_seconds'] = $ThisFileInfo['nsv']['NSVf']['playtime_ms'] / 1000; + $ThisFileInfo['bitrate'] = ($ThisFileInfo['nsv']['NSVf']['file_size'] * 8) / $ThisFileInfo['playtime_seconds']; + + return true; + } + + + function NSVframerateLookup($framerateindex) { + if ($framerateindex <= 127) { + return (float) $framerateindex; + } + + static $NSVframerateLookup = array(); + if (empty($NSVframerateLookup)) { + $NSVframerateLookup[129] = (float) 29.970; + $NSVframerateLookup[131] = (float) 23.976; + $NSVframerateLookup[133] = (float) 14.985; + $NSVframerateLookup[197] = (float) 59.940; + $NSVframerateLookup[199] = (float) 47.952; + } + return (isset($NSVframerateLookup[$framerateindex]) ? $NSVframerateLookup[$framerateindex] : false); + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio-video.quicktime.php b/modules/id3/getid3/module.audio-video.quicktime.php new file mode 100644 index 00000000..3b93d81a --- /dev/null +++ b/modules/id3/getid3/module.audio-video.quicktime.php @@ -0,0 +1,1292 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.quicktime.php // +// module for analyzing Quicktime and MP3-in-MP4 files // +// dependencies: module.audio.mp3.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); + +class getid3_quicktime +{ + + function getid3_quicktime(&$fd, &$ThisFileInfo, $ReturnAtomData=true, $ParseAllPossibleAtoms=false) { + + $ThisFileInfo['fileformat'] = 'quicktime'; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + + $offset = 0; + $atomcounter = 0; + + while ($offset < $ThisFileInfo['avdataend']) { + fseek($fd, $offset, SEEK_SET); + $AtomHeader = fread($fd, 8); + + $atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4)); + $atomname = substr($AtomHeader, 4, 4); + $ThisFileInfo['quicktime'][$atomname]['name'] = $atomname; + $ThisFileInfo['quicktime'][$atomname]['size'] = $atomsize; + $ThisFileInfo['quicktime'][$atomname]['offset'] = $offset; + + if (($offset + $atomsize) > $ThisFileInfo['avdataend']) { + $ThisFileInfo['error'][] = 'Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)'; + return false; + } + + if ($atomsize == 0) { + // Furthermore, for historical reasons the list of atoms is optionally + // terminated by a 32-bit integer set to 0. If you are writing a program + // to read user data atoms, you should allow for the terminating 0. + break; + } + switch ($atomname) { + case 'mdat': // Media DATa atom + // 'mdat' contains the actual data for the audio/video + if (($atomsize > 8) && (!isset($ThisFileInfo['avdataend_tmp']) || ($ThisFileInfo['quicktime'][$atomname]['size'] > ($ThisFileInfo['avdataend_tmp'] - $ThisFileInfo['avdataoffset'])))) { + + $ThisFileInfo['avdataoffset'] = $ThisFileInfo['quicktime'][$atomname]['offset'] + 8; + $OldAVDataEnd = $ThisFileInfo['avdataend']; + $ThisFileInfo['avdataend'] = $ThisFileInfo['quicktime'][$atomname]['offset'] + $ThisFileInfo['quicktime'][$atomname]['size']; + + if (getid3_mp3::MPEGaudioHeaderValid(getid3_mp3::MPEGaudioHeaderDecode(fread($fd, 4)))) { + getid3_mp3::getOnlyMPEGaudioInfo($fd, $ThisFileInfo, $ThisFileInfo['avdataoffset'], false); + if (isset($ThisFileInfo['mpeg']['audio'])) { + $ThisFileInfo['audio']['dataformat'] = 'mp3'; + $ThisFileInfo['audio']['codec'] = (!empty($ThisFileInfo['mpeg']['audio']['encoder']) ? $ThisFileInfo['mpeg']['audio']['encoder'] : (!empty($ThisFileInfo['mpeg']['audio']['codec']) ? $ThisFileInfo['mpeg']['audio']['codec'] : (!empty($ThisFileInfo['mpeg']['audio']['LAME']) ? 'LAME' :'mp3'))); + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; + $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; + $ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); + $ThisFileInfo['bitrate'] = $ThisFileInfo['audio']['bitrate']; + } + } + $ThisFileInfo['avdataend'] = $OldAVDataEnd; + unset($OldAVDataEnd); + + } + break; + + case 'free': // FREE space atom + case 'skip': // SKIP atom + case 'wide': // 64-bit expansion placeholder atom + // 'free', 'skip' and 'wide' are just padding, contains no useful data at all + break; + + default: + $atomHierarchy = array(); + $ThisFileInfo['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, fread($fd, $atomsize), $ThisFileInfo, $offset, $atomHierarchy, $ParseAllPossibleAtoms); + break; + } + + $offset += $atomsize; + $atomcounter++; + } + + if (!empty($ThisFileInfo['avdataend_tmp'])) { + // this value is assigned to a temp value and then erased because + // otherwise any atoms beyond the 'mdat' atom would not get parsed + $ThisFileInfo['avdataend'] = $ThisFileInfo['avdataend_tmp']; + unset($ThisFileInfo['avdataend_tmp']); + } + + if (!isset($ThisFileInfo['bitrate']) && isset($ThisFileInfo['playtime_seconds'])) { + $ThisFileInfo['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + } + if (isset($ThisFileInfo['bitrate']) && !isset($ThisFileInfo['audio']['bitrate']) && !isset($ThisFileInfo['quicktime']['video'])) { + $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['bitrate']; + } + + if (($ThisFileInfo['audio']['dataformat'] == 'mp4') && empty($ThisFileInfo['video']['resolution_x'])) { + $ThisFileInfo['fileformat'] = 'mp4'; + $ThisFileInfo['mime_type'] = 'audio/mp4'; + unset($ThisFileInfo['video']['dataformat']); + } + + if (!$ReturnAtomData) { + unset($ThisFileInfo['quicktime']['moov']); + } + + if (empty($ThisFileInfo['audio']['dataformat']) && !empty($ThisFileInfo['quicktime']['audio'])) { + $ThisFileInfo['audio']['dataformat'] = 'quicktime'; + } + if (empty($ThisFileInfo['video']['dataformat']) && !empty($ThisFileInfo['quicktime']['video'])) { + $ThisFileInfo['video']['dataformat'] = 'quicktime'; + } + + return true; + } + + function QuicktimeParseAtom($atomname, $atomsize, $atomdata, &$ThisFileInfo, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { + // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm + + array_push($atomHierarchy, $atomname); + $atomstructure['hierarchy'] = implode(' ', $atomHierarchy); + $atomstructure['name'] = $atomname; + $atomstructure['size'] = $atomsize; + $atomstructure['offset'] = $baseoffset; + + switch ($atomname) { + case 'moov': // MOVie container atom + case 'trak': // TRAcK container atom + case 'clip': // CLIPping container atom + case 'matt': // track MATTe container atom + case 'edts': // EDiTS container atom + case 'tref': // Track REFerence container atom + case 'mdia': // MeDIA container atom + case 'minf': // Media INFormation container atom + case 'dinf': // Data INFormation container atom + case 'udta': // User DaTA container atom + case 'stbl': // Sample TaBLe container atom + case 'cmov': // Compressed MOVie container atom + case 'rmra': // Reference Movie Record Atom + case 'rmda': // Reference Movie Descriptor Atom + case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR) + $atomstructure['subatoms'] = $this->QuicktimeParseContainerAtom($atomdata, $ThisFileInfo, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms); + break; + + + case '©cpy': + case '©day': + case '©dir': + case '©ed1': + case '©ed2': + case '©ed3': + case '©ed4': + case '©ed5': + case '©ed6': + case '©ed7': + case '©ed8': + case '©ed9': + case '©fmt': + case '©inf': + case '©prd': + case '©prf': + case '©req': + case '©src': + case '©wrt': + case '©nam': + case '©cmt': + case '©wrn': + case '©hst': + case '©mak': + case '©mod': + case '©PRD': + case '©swr': + case '©aut': + case '©ART': + case '©trk': + case '©alb': + case '©com': + case '©gen': + case '©ope': + case '©url': + case '©enc': + $atomstructure['data_length'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 2)); + $atomstructure['language_id'] = getid3_lib::BigEndian2Int(substr($atomdata, 2, 2)); + $atomstructure['data'] = substr($atomdata, 4); + + $atomstructure['language'] = $this->QuicktimeLanguageLookup($atomstructure['language_id']); + if (empty($ThisFileInfo['comments']['language']) || (!in_array($atomstructure['language'], $ThisFileInfo['comments']['language']))) { + $ThisFileInfo['comments']['language'][] = $atomstructure['language']; + } + $this->CopyToAppropriateCommentsSection($atomname, $atomstructure['data'], $ThisFileInfo); + break; + + + case 'play': // auto-PLAY atom + $atomstructure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + + $ThisFileInfo['quicktime']['autoplay'] = $atomstructure['autoplay']; + break; + + + case 'WLOC': // Window LOCation atom + $atomstructure['location_x'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 2)); + $atomstructure['location_y'] = getid3_lib::BigEndian2Int(substr($atomdata, 2, 2)); + break; + + + case 'LOOP': // LOOPing atom + case 'SelO': // play SELection Only atom + case 'AllF': // play ALL Frames atom + $atomstructure['data'] = getid3_lib::BigEndian2Int($atomdata); + break; + + + case 'name': // + case 'MCPS': // Media Cleaner PRo + case '@PRM': // adobe PReMiere version + case '@PRQ': // adobe PRemiere Quicktime version + $atomstructure['data'] = $atomdata; + break; + + + case 'cmvd': // Compressed MooV Data atom + // Code by ubergeekØubergeek*tv based on information from + // http://developer.apple.com/quicktime/icefloe/dispatch012.html + $atomstructure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 4)); + + $CompressedFileData = substr($atomdata, 4); + if ($UncompressedHeader = @gzuncompress($CompressedFileData)) { + $atomstructure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, $ThisFileInfo, 0, $atomHierarchy, $ParseAllPossibleAtoms); + } else { + $ThisFileInfo['warning'][] = 'Error decompressing compressed MOV atom at offset '.$atomstructure['offset']; + } + break; + + + case 'dcom': // Data COMpression atom + $atomstructure['compression_id'] = $atomdata; + $atomstructure['compression_text'] = $this->QuicktimeDCOMLookup($atomdata); + break; + + + case 'rdrf': // Reference movie Data ReFerence atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); + $atomstructure['flags']['internal_data'] = (bool) ($atomstructure['flags_raw'] & 0x000001); + + $atomstructure['reference_type_name'] = substr($atomdata, 4, 4); + $atomstructure['reference_length'] = getid3_lib::BigEndian2Int(substr($atomdata, 8, 4)); + switch ($atomstructure['reference_type_name']) { + case 'url ': + $atomstructure['url'] = $this->NoNullString(substr($atomdata, 12)); + break; + + case 'alis': + $atomstructure['file_alias'] = substr($atomdata, 12); + break; + + case 'rsrc': + $atomstructure['resource_alias'] = substr($atomdata, 12); + break; + + default: + $atomstructure['data'] = substr($atomdata, 12); + break; + } + break; + + + case 'rmqu': // Reference Movie QUality atom + $atomstructure['movie_quality'] = getid3_lib::BigEndian2Int($atomdata); + break; + + + case 'rmcs': // Reference Movie Cpu Speed atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 2)); + break; + + + case 'rmvc': // Reference Movie Version Check atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['gestalt_selector'] = substr($atomdata, 4, 4); + $atomstructure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atomdata, 8, 4)); + $atomstructure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atomdata, 12, 4)); + $atomstructure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atomdata, 14, 2)); + break; + + + case 'rmcd': // Reference Movie Component check atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['component_type'] = substr($atomdata, 4, 4); + $atomstructure['component_subtype'] = substr($atomdata, 8, 4); + $atomstructure['component_manufacturer'] = substr($atomdata, 12, 4); + $atomstructure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 16, 4)); + $atomstructure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atomdata, 20, 4)); + $atomstructure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atomdata, 24, 4)); + break; + + + case 'rmdr': // Reference Movie Data Rate atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['data_rate'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + + $atomstructure['data_rate_bps'] = $atomstructure['data_rate'] * 10; + break; + + + case 'rmla': // Reference Movie Language Atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['language_id'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 2)); + + $atomstructure['language'] = $this->QuicktimeLanguageLookup($atomstructure['language_id']); + if (empty($ThisFileInfo['comments']['language']) || (!in_array($atomstructure['language'], $ThisFileInfo['comments']['language']))) { + $ThisFileInfo['comments']['language'][] = $atomstructure['language']; + } + break; + + + case 'rmla': // Reference Movie Language Atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['track_id'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 2)); + break; + + + case 'ptv ': // Print To Video - defines a movie's full screen mode + // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm + $atomstructure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 2)); + $atomstructure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atomdata, 2, 2)); // hardcoded: 0x0000 + $atomstructure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 2)); // hardcoded: 0x0000 + $atomstructure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atomdata, 6, 1)); + $atomstructure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atomdata, 7, 1)); + + $atomstructure['flags']['play_on_open'] = (bool) $atomstructure['play_on_open_flag']; + $atomstructure['flags']['slide_show'] = (bool) $atomstructure['slide_show_flag']; + + $ptv_lookup[0] = 'normal'; + $ptv_lookup[1] = 'double'; + $ptv_lookup[2] = 'half'; + $ptv_lookup[3] = 'full'; + $ptv_lookup[4] = 'current'; + if (isset($ptv_lookup[$atomstructure['display_size_raw']])) { + $atomstructure['display_size'] = $ptv_lookup[$atomstructure['display_size_raw']]; + } else { + $ThisFileInfo['warning'][] = 'unknown "ptv " display constant ('.$atomstructure['display_size_raw'].')'; + } + break; + + + case 'stsd': // Sample Table Sample Description atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['number_entries'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + $stsdEntriesDataOffset = 8; + for ($i = 0; $i < $atomstructure['number_entries']; $i++) { + $atomstructure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atomdata, $stsdEntriesDataOffset, 4)); + $stsdEntriesDataOffset += 4; + $atomstructure['sample_description_table'][$i]['data_format'] = substr($atomdata, $stsdEntriesDataOffset, 4); + $stsdEntriesDataOffset += 4; + $atomstructure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atomdata, $stsdEntriesDataOffset, 6)); + $stsdEntriesDataOffset += 6; + $atomstructure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atomdata, $stsdEntriesDataOffset, 2)); + $stsdEntriesDataOffset += 2; + $atomstructure['sample_description_table'][$i]['data'] = substr($atomdata, $stsdEntriesDataOffset, ($atomstructure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2)); + $stsdEntriesDataOffset += ($atomstructure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2); + + $atomstructure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 0, 2)); + $atomstructure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 2, 2)); + $atomstructure['sample_description_table'][$i]['encoder_vendor'] = substr($atomstructure['sample_description_table'][$i]['data'], 4, 4); + + switch ($atomstructure['sample_description_table'][$i]['encoder_vendor']) { + + case "\x00\x00\x00\x00": + // audio atom + $atomstructure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 8, 2)); + $atomstructure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 10, 2)); + $atomstructure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 12, 2)); + $atomstructure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 14, 2)); + $atomstructure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atomstructure['sample_description_table'][$i]['data'], 16, 4)); + + switch ($atomstructure['sample_description_table'][$i]['data_format']) { + case 'mp4v': + $ThisFileInfo['fileformat'] = 'mp4'; + $ThisFileInfo['error'][] = 'This version ('.GETID3_VERSION.') of getID3() does not fully support MPEG-4 audio/video streams'; + break; + + case 'qtvr': + $ThisFileInfo['video']['dataformat'] = 'quicktimevr'; + break; + + case 'mp4a': + default: + $ThisFileInfo['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atomstructure['sample_description_table'][$i]['data_format']); + $ThisFileInfo['quicktime']['audio']['sample_rate'] = $atomstructure['sample_description_table'][$i]['audio_sample_rate']; + $ThisFileInfo['quicktime']['audio']['channels'] = $atomstructure['sample_description_table'][$i]['audio_channels']; + $ThisFileInfo['quicktime']['audio']['bit_depth'] = $atomstructure['sample_description_table'][$i]['audio_bit_depth']; + $ThisFileInfo['audio']['codec'] = $ThisFileInfo['quicktime']['audio']['codec']; + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['quicktime']['audio']['sample_rate']; + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['quicktime']['audio']['channels']; + $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['quicktime']['audio']['bit_depth']; + switch ($atomstructure['sample_description_table'][$i]['data_format']) { + case 'raw ': // PCM + case 'alac': // Apple Lossless Audio Codec + $ThisFileInfo['audio']['lossless'] = true; + break; + default: + $ThisFileInfo['audio']['lossless'] = false; + break; + } + break; + } + break; + + default: + switch ($atomstructure['sample_description_table'][$i]['data_format']) { + case 'mp4s': + $ThisFileInfo['fileformat'] = 'mp4'; + break; + + default: + // video atom + $atomstructure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 8, 4)); + $atomstructure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 12, 4)); + $atomstructure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 16, 2)); + $atomstructure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 18, 2)); + $atomstructure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atomstructure['sample_description_table'][$i]['data'], 20, 4)); + $atomstructure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atomstructure['sample_description_table'][$i]['data'], 24, 4)); + $atomstructure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 28, 4)); + $atomstructure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 32, 2)); + $atomstructure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 34, 1)); + $atomstructure['sample_description_table'][$i]['video_encoder_name'] = substr($atomstructure['sample_description_table'][$i]['data'], 35, $atomstructure['sample_description_table'][$i]['video_encoder_name_len']); + $atomstructure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 66, 2)); + $atomstructure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atomstructure['sample_description_table'][$i]['data'], 68, 2)); + + $atomstructure['sample_description_table'][$i]['video_pixel_color_type'] = (($atomstructure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color'); + $atomstructure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atomstructure['sample_description_table'][$i]['video_pixel_color_depth']); + + if ($atomstructure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') { + $ThisFileInfo['quicktime']['video']['codec_fourcc'] = $atomstructure['sample_description_table'][$i]['data_format']; + $ThisFileInfo['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atomstructure['sample_description_table'][$i]['data_format']); + $ThisFileInfo['quicktime']['video']['codec'] = $atomstructure['sample_description_table'][$i]['video_encoder_name']; + $ThisFileInfo['quicktime']['video']['color_depth'] = $atomstructure['sample_description_table'][$i]['video_pixel_color_depth']; + $ThisFileInfo['quicktime']['video']['color_depth_name'] = $atomstructure['sample_description_table'][$i]['video_pixel_color_name']; + + $ThisFileInfo['video']['codec'] = $ThisFileInfo['quicktime']['video']['codec']; + $ThisFileInfo['video']['bits_per_sample'] = $ThisFileInfo['quicktime']['video']['color_depth']; + } + $ThisFileInfo['video']['lossless'] = false; + $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1; + break; + } + break; + } + switch (strtolower($atomstructure['sample_description_table'][$i]['data_format'])) { + case 'mp4a': + $ThisFileInfo['audio']['dataformat'] = 'mp4'; + $ThisFileInfo['quicktime']['audio']['codec'] = 'mp4'; + break; + + case '3ivx': + case '3iv1': + case '3iv2': + $ThisFileInfo['video']['dataformat'] = '3ivx'; + break; + + case 'xvid': + $ThisFileInfo['video']['dataformat'] = 'xvid'; + break; + + case 'mp4v': + $ThisFileInfo['video']['dataformat'] = 'mpeg4'; + break; + + case 'divx': + case 'div1': + case 'div2': + case 'div3': + case 'div4': + case 'div5': + case 'div6': + $TDIVXileInfo['video']['dataformat'] = 'divx'; + break; + + default: + // do nothing + break; + } + unset($atomstructure['sample_description_table'][$i]['data']); + } + break; + + + case 'stts': // Sample Table Time-to-Sample atom + //if ($ParseAllPossibleAtoms) { + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['number_entries'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + $sttsEntriesDataOffset = 8; + $FrameRateCalculatorArray = array(); + for ($i = 0; $i < $atomstructure['number_entries']; $i++) { + $atomstructure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atomdata, $sttsEntriesDataOffset, 4)); + $sttsEntriesDataOffset += 4; + $atomstructure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atomdata, $sttsEntriesDataOffset, 4)); + $sttsEntriesDataOffset += 4; + + if (!empty($ThisFileInfo['quicktime']['time_scale'])) { + $stts_new_framerate = $ThisFileInfo['quicktime']['time_scale'] / $atomstructure['time_to_sample_table'][$i]['sample_duration']; + if ($stts_new_framerate <= 60) { + // some atoms have durations of "1" giving a very large framerate, which probably is not right + $ThisFileInfo['video']['frame_rate'] = max(@$ThisFileInfo['video']['frame_rate'], $stts_new_framerate); + } + } + //@$FrameRateCalculatorArray[($ThisFileInfo['quicktime']['time_scale'] / $atomstructure['time_to_sample_table'][$i]['sample_duration'])] += $atomstructure['time_to_sample_table'][$i]['sample_count']; + } + //$sttsFramesTotal = 0; + //$sttsSecondsTotal = 0; + //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) { + // if (($frames_per_second > 60) || ($frames_per_second < 1)) { + // // not video FPS information, probably audio information + // $sttsFramesTotal = 0; + // $sttsSecondsTotal = 0; + // break; + // } + // $sttsFramesTotal += $frame_count; + // $sttsSecondsTotal += $frame_count / $frames_per_second; + //} + //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) { + // if (($sttsFramesTotal / $sttsSecondsTotal) > @$ThisFileInfo['video']['frame_rate']) { + // $ThisFileInfo['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal; + // } + //} + //} + break; + + + case 'stss': // Sample Table Sync Sample (key frames) atom + if ($ParseAllPossibleAtoms) { + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['number_entries'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + $stssEntriesDataOffset = 8; + for ($i = 0; $i < $atomstructure['number_entries']; $i++) { + $atomstructure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atomdata, $stssEntriesDataOffset, 4)); + $stssEntriesDataOffset += 4; + } + } + break; + + + case 'stsc': // Sample Table Sample-to-Chunk atom + if ($ParseAllPossibleAtoms) { + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['number_entries'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + $stscEntriesDataOffset = 8; + for ($i = 0; $i < $atomstructure['number_entries']; $i++) { + $atomstructure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atomdata, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + $atomstructure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atomdata, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + $atomstructure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atomdata, $stscEntriesDataOffset, 4)); + $stscEntriesDataOffset += 4; + } + } + break; + + + case 'stsz': // Sample Table SiZe atom + if ($ParseAllPossibleAtoms) { + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['sample_size'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + $atomstructure['number_entries'] = getid3_lib::BigEndian2Int(substr($atomdata, 8, 4)); + $stszEntriesDataOffset = 12; + if ($atomstructure['sample_size'] == 0) { + for ($i = 0; $i < $atomstructure['number_entries']; $i++) { + $atomstructure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atomdata, $stszEntriesDataOffset, 4)); + $stszEntriesDataOffset += 4; + } + } + } + break; + + + case 'stco': // Sample Table Chunk Offset atom + if ($ParseAllPossibleAtoms) { + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['number_entries'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + $stcoEntriesDataOffset = 8; + for ($i = 0; $i < $atomstructure['number_entries']; $i++) { + $atomstructure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atomdata, $stcoEntriesDataOffset, 4)); + $stcoEntriesDataOffset += 4; + } + } + break; + + + case 'dref': // Data REFerence atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['number_entries'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + $drefDataOffset = 8; + for ($i = 0; $i < $atomstructure['number_entries']; $i++) { + $atomstructure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atomdata, $drefDataOffset, 4)); + $drefDataOffset += 4; + $atomstructure['data_references'][$i]['type'] = substr($atomdata, $drefDataOffset, 4); + $drefDataOffset += 4; + $atomstructure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atomdata, $drefDataOffset, 1)); + $drefDataOffset += 1; + $atomstructure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, $drefDataOffset, 3)); // hardcoded: 0x0000 + $drefDataOffset += 3; + $atomstructure['data_references'][$i]['data'] = substr($atomdata, $drefDataOffset, ($atomstructure['data_references'][$i]['size'] - 4 - 4 - 1 - 3)); + $drefDataOffset += ($atomstructure['data_references'][$i]['size'] - 4 - 4 - 1 - 3); + + $atomstructure['data_references'][$i]['flags']['self_reference'] = (bool) ($atomstructure['data_references'][$i]['flags_raw'] & 0x001); + } + break; + + + case 'gmin': // base Media INformation atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 2)); + $atomstructure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atomdata, 6, 2)); + $atomstructure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atomdata, 8, 2)); + $atomstructure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atomdata, 10, 2)); + $atomstructure['balance'] = getid3_lib::BigEndian2Int(substr($atomdata, 12, 2)); + $atomstructure['reserved'] = getid3_lib::BigEndian2Int(substr($atomdata, 14, 2)); + break; + + + case 'smhd': // Sound Media information HeaDer atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['balance'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 2)); + $atomstructure['reserved'] = getid3_lib::BigEndian2Int(substr($atomdata, 6, 2)); + break; + + + case 'vmhd': // Video Media information HeaDer atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); + $atomstructure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 2)); + $atomstructure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atomdata, 6, 2)); + $atomstructure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atomdata, 8, 2)); + $atomstructure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atomdata, 10, 2)); + + $atomstructure['flags']['no_lean_ahead'] = (bool) ($atomstructure['flags_raw'] & 0x001); + break; + + + case 'hdlr': // HanDLeR reference atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['component_type'] = substr($atomdata, 4, 4); + $atomstructure['component_subtype'] = substr($atomdata, 8, 4); + $atomstructure['component_manufacturer'] = substr($atomdata, 12, 4); + $atomstructure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 16, 4)); + $atomstructure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atomdata, 20, 4)); + $atomstructure['component_name'] = $this->Pascal2String(substr($atomdata, 24)); + + if (($atomstructure['component_subtype'] == 'STpn') && ($atomstructure['component_manufacturer'] == 'zzzz')) { + $ThisFileInfo['video']['dataformat'] = 'quicktimevr'; + } + break; + + + case 'mdhd': // MeDia HeaDer atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['creation_time'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + $atomstructure['modify_time'] = getid3_lib::BigEndian2Int(substr($atomdata, 8, 4)); + $atomstructure['time_scale'] = getid3_lib::BigEndian2Int(substr($atomdata, 12, 4)); + $atomstructure['duration'] = getid3_lib::BigEndian2Int(substr($atomdata, 16, 4)); + $atomstructure['language_id'] = getid3_lib::BigEndian2Int(substr($atomdata, 20, 2)); + $atomstructure['quality'] = getid3_lib::BigEndian2Int(substr($atomdata, 22, 2)); + + if ($atomstructure['time_scale'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt Quicktime file: mdhd.time_scale == zero'; + return false; + } + $atomstructure['creation_time_unix'] = getid3_lib::DateMac2Unix($atomstructure['creation_time']); + $atomstructure['modify_time_unix'] = getid3_lib::DateMac2Unix($atomstructure['modify_time']); + $atomstructure['playtime_seconds'] = $atomstructure['duration'] / $atomstructure['time_scale']; + $atomstructure['language'] = $this->QuicktimeLanguageLookup($atomstructure['language_id']); + if (empty($ThisFileInfo['comments']['language']) || (!in_array($atomstructure['language'], $ThisFileInfo['comments']['language']))) { + $ThisFileInfo['comments']['language'][] = $atomstructure['language']; + } + break; + + + case 'pnot': // Preview atom + $atomstructure['modification_date'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 4)); // "standard Macintosh format" + $atomstructure['version_number'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 2)); // hardcoded: 0x00 + $atomstructure['atom_type'] = substr($atomdata, 6, 4); // usually: 'PICT' + $atomstructure['atom_index'] = getid3_lib::BigEndian2Int(substr($atomdata, 10, 2)); // usually: 0x01 + + $atomstructure['modification_date_unix'] = getid3_lib::DateMac2Unix($atomstructure['modification_date']); + break; + + + case 'crgn': // Clipping ReGioN atom + $atomstructure['region_size'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 2)); // The Region size, Region boundary box, + $atomstructure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atomdata, 2, 8)); // and Clipping region data fields + $atomstructure['clipping_data'] = substr($atomdata, 10); // constitute a QuickDraw region. + break; + + + case 'load': // track LOAD settings atom + $atomstructure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 4)); + $atomstructure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + $atomstructure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 8, 4)); + $atomstructure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 12, 4)); + + $atomstructure['default_hints']['double_buffer'] = (bool) ($atomstructure['default_hints_raw'] & 0x0020); + $atomstructure['default_hints']['high_quality'] = (bool) ($atomstructure['default_hints_raw'] & 0x0100); + break; + + + case 'tmcd': // TiMe CoDe atom + case 'chap': // CHAPter list atom + case 'sync': // SYNChronization atom + case 'scpt': // tranSCriPT atom + case 'ssrc': // non-primary SouRCe atom + for ($i = 0; $i < (strlen($atomdata) % 4); $i++) { + $atomstructure['track_id'][$i] = getid3_lib::BigEndian2Int(substr($atomdata, $i * 4, 4)); + } + break; + + + case 'elst': // Edit LiST atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['number_entries'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + for ($i = 0; $i < $atomstructure['number_entries']; $i++ ) { + $atomstructure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atomdata, 8 + ($i * 12) + 0, 4)); + $atomstructure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atomdata, 8 + ($i * 12) + 4, 4)); + $atomstructure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atomdata, 8 + ($i * 12) + 8, 4)); + } + break; + + + case 'kmat': // compressed MATte atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); // hardcoded: 0x0000 + $atomstructure['matte_data_raw'] = substr($atomdata, 4); + break; + + + case 'ctab': // Color TABle atom + $atomstructure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 4)); // hardcoded: 0x00000000 + $atomstructure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 2)); // hardcoded: 0x8000 + $atomstructure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atomdata, 6, 2)) + 1; + for ($colortableentry = 0; $colortableentry < $atomstructure['color_table_size']; $colortableentry++) { + $atomstructure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atomdata, 8 + ($colortableentry * 8) + 0, 2)); + $atomstructure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atomdata, 8 + ($colortableentry * 8) + 2, 2)); + $atomstructure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atomdata, 8 + ($colortableentry * 8) + 4, 2)); + $atomstructure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atomdata, 8 + ($colortableentry * 8) + 6, 2)); + } + break; + + + case 'mvhd': // MoVie HeaDer atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); + $atomstructure['creation_time'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + $atomstructure['modify_time'] = getid3_lib::BigEndian2Int(substr($atomdata, 8, 4)); + $atomstructure['time_scale'] = getid3_lib::BigEndian2Int(substr($atomdata, 12, 4)); + $atomstructure['duration'] = getid3_lib::BigEndian2Int(substr($atomdata, 16, 4)); + $atomstructure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atomdata, 20, 4)); + $atomstructure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atomdata, 24, 2)); + $atomstructure['reserved'] = substr($atomdata, 26, 10); + $atomstructure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atomdata, 36, 4)); + $atomstructure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atomdata, 40, 4)); + $atomstructure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atomdata, 44, 4)); + $atomstructure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atomdata, 48, 4)); + $atomstructure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atomdata, 52, 4)); + $atomstructure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atomdata, 56, 4)); + $atomstructure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atomdata, 60, 4)); + $atomstructure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atomdata, 64, 4)); + $atomstructure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atomdata, 68, 4)); + $atomstructure['preview_time'] = getid3_lib::BigEndian2Int(substr($atomdata, 72, 4)); + $atomstructure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atomdata, 76, 4)); + $atomstructure['poster_time'] = getid3_lib::BigEndian2Int(substr($atomdata, 80, 4)); + $atomstructure['selection_time'] = getid3_lib::BigEndian2Int(substr($atomdata, 84, 4)); + $atomstructure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atomdata, 88, 4)); + $atomstructure['current_time'] = getid3_lib::BigEndian2Int(substr($atomdata, 92, 4)); + $atomstructure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atomdata, 96, 4)); + + if ($atomstructure['time_scale'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt Quicktime file: mvhd.time_scale == zero'; + return false; + } + $atomstructure['creation_time_unix'] = getid3_lib::DateMac2Unix($atomstructure['creation_time']); + $atomstructure['modify_time_unix'] = getid3_lib::DateMac2Unix($atomstructure['modify_time']); + $ThisFileInfo['quicktime']['time_scale'] = $atomstructure['time_scale']; + $ThisFileInfo['quicktime']['display_scale'] = $atomstructure['matrix_a']; + $ThisFileInfo['playtime_seconds'] = $atomstructure['duration'] / $atomstructure['time_scale']; + break; + + + case 'tkhd': // TracK HeaDer atom + $atomstructure['version'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 1)); + $atomstructure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atomdata, 1, 3)); + $atomstructure['creation_time'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + $atomstructure['modify_time'] = getid3_lib::BigEndian2Int(substr($atomdata, 8, 4)); + $atomstructure['trackid'] = getid3_lib::BigEndian2Int(substr($atomdata, 12, 4)); + $atomstructure['reserved1'] = getid3_lib::BigEndian2Int(substr($atomdata, 16, 4)); + $atomstructure['duration'] = getid3_lib::BigEndian2Int(substr($atomdata, 20, 4)); + $atomstructure['reserved2'] = getid3_lib::BigEndian2Int(substr($atomdata, 24, 8)); + $atomstructure['layer'] = getid3_lib::BigEndian2Int(substr($atomdata, 32, 2)); + $atomstructure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atomdata, 34, 2)); + $atomstructure['volume'] = getid3_lib::FixedPoint8_8(substr($atomdata, 36, 2)); + $atomstructure['reserved3'] = getid3_lib::BigEndian2Int(substr($atomdata, 38, 2)); + $atomstructure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atomdata, 40, 4)); + $atomstructure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atomdata, 44, 4)); + $atomstructure['matrix_u'] = getid3_lib::FixedPoint16_16(substr($atomdata, 48, 4)); + $atomstructure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atomdata, 52, 4)); + $atomstructure['matrix_v'] = getid3_lib::FixedPoint16_16(substr($atomdata, 56, 4)); + $atomstructure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atomdata, 60, 4)); + $atomstructure['matrix_x'] = getid3_lib::FixedPoint2_30(substr($atomdata, 64, 4)); + $atomstructure['matrix_y'] = getid3_lib::FixedPoint2_30(substr($atomdata, 68, 4)); + $atomstructure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atomdata, 72, 4)); + $atomstructure['width'] = getid3_lib::FixedPoint16_16(substr($atomdata, 76, 4)); + $atomstructure['height'] = getid3_lib::FixedPoint16_16(substr($atomdata, 80, 4)); + + $atomstructure['flags']['enabled'] = (bool) ($atomstructure['flags_raw'] & 0x0001); + $atomstructure['flags']['in_movie'] = (bool) ($atomstructure['flags_raw'] & 0x0002); + $atomstructure['flags']['in_preview'] = (bool) ($atomstructure['flags_raw'] & 0x0004); + $atomstructure['flags']['in_poster'] = (bool) ($atomstructure['flags_raw'] & 0x0008); + $atomstructure['creation_time_unix'] = getid3_lib::DateMac2Unix($atomstructure['creation_time']); + $atomstructure['modify_time_unix'] = getid3_lib::DateMac2Unix($atomstructure['modify_time']); + + if (!isset($ThisFileInfo['video']['resolution_x']) || !isset($ThisFileInfo['video']['resolution_y'])) { + $ThisFileInfo['video']['resolution_x'] = $atomstructure['width']; + $ThisFileInfo['video']['resolution_y'] = $atomstructure['height']; + } + $ThisFileInfo['video']['resolution_x'] = max($ThisFileInfo['video']['resolution_x'], $atomstructure['width']); + $ThisFileInfo['video']['resolution_y'] = max($ThisFileInfo['video']['resolution_y'], $atomstructure['height']); + if (!empty($ThisFileInfo['video']['resolution_x']) && !empty($ThisFileInfo['video']['resolution_y'])) { + $ThisFileInfo['quicktime']['video']['resolution_x'] = $ThisFileInfo['video']['resolution_x']; + $ThisFileInfo['quicktime']['video']['resolution_y'] = $ThisFileInfo['video']['resolution_y']; + } else { + unset($ThisFileInfo['video']['resolution_x']); + unset($ThisFileInfo['video']['resolution_y']); + unset($ThisFileInfo['quicktime']['video']); + } + break; + + + case 'meta': // METAdata atom + // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt + $NextTagPosition = strpos($atomdata, '©'); + while ($NextTagPosition < strlen($atomdata)) { + $metaItemSize = getid3_lib::BigEndian2Int(substr($atomdata, $NextTagPosition - 4, 4)) - 4; + if ($metaItemSize == -4) { + break; + } + $metaItemRaw = substr($atomdata, $NextTagPosition, $metaItemSize); + $metaItemKey = substr($metaItemRaw, 0, 4); + $metaItemData = substr($metaItemRaw, 20); + $NextTagPosition += $metaItemSize + 4; + + $this->CopyToAppropriateCommentsSection($metaItemKey, $metaItemData, $ThisFileInfo); + } + break; + + case 'ftyp': // FileTYPe (?) atom (for MP4 it seems) + $atomstructure['signature'] = substr($atomdata, 0, 4); + $atomstructure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atomdata, 4, 4)); + $atomstructure['fourcc'] = substr($atomdata, 8, 4); + break; + + case 'mdat': // Media DATa atom + case 'free': // FREE space atom + case 'skip': // SKIP atom + case 'wide': // 64-bit expansion placeholder atom + // 'mdat' data is too big to deal with, contains no useful metadata + // 'free', 'skip' and 'wide' are just padding, contains no useful data at all + + // When writing QuickTime files, it is sometimes necessary to update an atom's size. + // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom + // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime + // puts an 8-byte placeholder atom before any atoms it may have to update the size of. + // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the + // placeholder atom can be overwritten to obtain the necessary 8 extra bytes. + // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ). + break; + + + case 'nsav': // NoSAVe atom + // http://developer.apple.com/technotes/tn/tn2038.html + $atomstructure['data'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 4)); + break; + + case 'ctyp': // Controller TYPe atom (seen on QTVR) + // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt + // some controller names are: + // 0x00 + 'std' for linear movie + // 'none' for no controls + $atomstructure['ctyp'] = substr($atomdata, 0, 4); + switch ($atomstructure['ctyp']) { + case 'qtvr': + $ThisFileInfo['video']['dataformat'] = 'quicktimevr'; + break; + } + break; + + case 'pano': // PANOrama track (seen on QTVR) + $atomstructure['pano'] = getid3_lib::BigEndian2Int(substr($atomdata, 0, 4)); + break; + + case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR) + for ($i = 0; $i < ($atomstructure['size'] - 8); $i += 4) { + $atomstructure['imgt'][] = getid3_lib::BigEndian2Int(substr($atomdata, $i, 4)); + } + break; + + case 'FXTC': // Something to do with Adobe After Effects (?) + case 'PrmA': + case 'code': + case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html + // Observed-but-not-handled atom types are just listed here + // to prevent warnings being generated + $atomstructure['data'] = $atomdata; + break; + + default: + $ThisFileInfo['warning'][] = 'Unknown QuickTime atom type: "'.$atomname.'" at offset '.$baseoffset; + $atomstructure['data'] = $atomdata; + break; + } + array_pop($atomHierarchy); + return $atomstructure; + } + + function QuicktimeParseContainerAtom($atomdata, &$ThisFileInfo, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) { + $atomstructure = false; + $subatomoffset = 0; + $subatomcounter = 0; + if ((strlen($atomdata) == 4) && (getid3_lib::BigEndian2Int($atomdata) == 0x00000000)) { + return false; + } + while ($subatomoffset < strlen($atomdata)) { + $subatomsize = getid3_lib::BigEndian2Int(substr($atomdata, $subatomoffset + 0, 4)); + $subatomname = substr($atomdata, $subatomoffset + 4, 4); + $subatomdata = substr($atomdata, $subatomoffset + 8, $subatomsize - 8); + if ($subatomsize == 0) { + // Furthermore, for historical reasons the list of atoms is optionally + // terminated by a 32-bit integer set to 0. If you are writing a program + // to read user data atoms, you should allow for the terminating 0. + return $atomstructure; + } + + $atomstructure[$subatomcounter] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $ThisFileInfo, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms); + + $subatomoffset += $subatomsize; + $subatomcounter++; + } + return $atomstructure; + } + + + function QuicktimeLanguageLookup($languageid) { + static $QuicktimeLanguageLookup = array(); + if (empty($QuicktimeLanguageLookup)) { + $QuicktimeLanguageLookup[0] = 'English'; + $QuicktimeLanguageLookup[1] = 'French'; + $QuicktimeLanguageLookup[2] = 'German'; + $QuicktimeLanguageLookup[3] = 'Italian'; + $QuicktimeLanguageLookup[4] = 'Dutch'; + $QuicktimeLanguageLookup[5] = 'Swedish'; + $QuicktimeLanguageLookup[6] = 'Spanish'; + $QuicktimeLanguageLookup[7] = 'Danish'; + $QuicktimeLanguageLookup[8] = 'Portuguese'; + $QuicktimeLanguageLookup[9] = 'Norwegian'; + $QuicktimeLanguageLookup[10] = 'Hebrew'; + $QuicktimeLanguageLookup[11] = 'Japanese'; + $QuicktimeLanguageLookup[12] = 'Arabic'; + $QuicktimeLanguageLookup[13] = 'Finnish'; + $QuicktimeLanguageLookup[14] = 'Greek'; + $QuicktimeLanguageLookup[15] = 'Icelandic'; + $QuicktimeLanguageLookup[16] = 'Maltese'; + $QuicktimeLanguageLookup[17] = 'Turkish'; + $QuicktimeLanguageLookup[18] = 'Croatian'; + $QuicktimeLanguageLookup[19] = 'Chinese (Traditional)'; + $QuicktimeLanguageLookup[20] = 'Urdu'; + $QuicktimeLanguageLookup[21] = 'Hindi'; + $QuicktimeLanguageLookup[22] = 'Thai'; + $QuicktimeLanguageLookup[23] = 'Korean'; + $QuicktimeLanguageLookup[24] = 'Lithuanian'; + $QuicktimeLanguageLookup[25] = 'Polish'; + $QuicktimeLanguageLookup[26] = 'Hungarian'; + $QuicktimeLanguageLookup[27] = 'Estonian'; + $QuicktimeLanguageLookup[28] = 'Lettish'; + $QuicktimeLanguageLookup[28] = 'Latvian'; + $QuicktimeLanguageLookup[29] = 'Saamisk'; + $QuicktimeLanguageLookup[29] = 'Lappish'; + $QuicktimeLanguageLookup[30] = 'Faeroese'; + $QuicktimeLanguageLookup[31] = 'Farsi'; + $QuicktimeLanguageLookup[31] = 'Persian'; + $QuicktimeLanguageLookup[32] = 'Russian'; + $QuicktimeLanguageLookup[33] = 'Chinese (Simplified)'; + $QuicktimeLanguageLookup[34] = 'Flemish'; + $QuicktimeLanguageLookup[35] = 'Irish'; + $QuicktimeLanguageLookup[36] = 'Albanian'; + $QuicktimeLanguageLookup[37] = 'Romanian'; + $QuicktimeLanguageLookup[38] = 'Czech'; + $QuicktimeLanguageLookup[39] = 'Slovak'; + $QuicktimeLanguageLookup[40] = 'Slovenian'; + $QuicktimeLanguageLookup[41] = 'Yiddish'; + $QuicktimeLanguageLookup[42] = 'Serbian'; + $QuicktimeLanguageLookup[43] = 'Macedonian'; + $QuicktimeLanguageLookup[44] = 'Bulgarian'; + $QuicktimeLanguageLookup[45] = 'Ukrainian'; + $QuicktimeLanguageLookup[46] = 'Byelorussian'; + $QuicktimeLanguageLookup[47] = 'Uzbek'; + $QuicktimeLanguageLookup[48] = 'Kazakh'; + $QuicktimeLanguageLookup[49] = 'Azerbaijani'; + $QuicktimeLanguageLookup[50] = 'AzerbaijanAr'; + $QuicktimeLanguageLookup[51] = 'Armenian'; + $QuicktimeLanguageLookup[52] = 'Georgian'; + $QuicktimeLanguageLookup[53] = 'Moldavian'; + $QuicktimeLanguageLookup[54] = 'Kirghiz'; + $QuicktimeLanguageLookup[55] = 'Tajiki'; + $QuicktimeLanguageLookup[56] = 'Turkmen'; + $QuicktimeLanguageLookup[57] = 'Mongolian'; + $QuicktimeLanguageLookup[58] = 'MongolianCyr'; + $QuicktimeLanguageLookup[59] = 'Pashto'; + $QuicktimeLanguageLookup[60] = 'Kurdish'; + $QuicktimeLanguageLookup[61] = 'Kashmiri'; + $QuicktimeLanguageLookup[62] = 'Sindhi'; + $QuicktimeLanguageLookup[63] = 'Tibetan'; + $QuicktimeLanguageLookup[64] = 'Nepali'; + $QuicktimeLanguageLookup[65] = 'Sanskrit'; + $QuicktimeLanguageLookup[66] = 'Marathi'; + $QuicktimeLanguageLookup[67] = 'Bengali'; + $QuicktimeLanguageLookup[68] = 'Assamese'; + $QuicktimeLanguageLookup[69] = 'Gujarati'; + $QuicktimeLanguageLookup[70] = 'Punjabi'; + $QuicktimeLanguageLookup[71] = 'Oriya'; + $QuicktimeLanguageLookup[72] = 'Malayalam'; + $QuicktimeLanguageLookup[73] = 'Kannada'; + $QuicktimeLanguageLookup[74] = 'Tamil'; + $QuicktimeLanguageLookup[75] = 'Telugu'; + $QuicktimeLanguageLookup[76] = 'Sinhalese'; + $QuicktimeLanguageLookup[77] = 'Burmese'; + $QuicktimeLanguageLookup[78] = 'Khmer'; + $QuicktimeLanguageLookup[79] = 'Lao'; + $QuicktimeLanguageLookup[80] = 'Vietnamese'; + $QuicktimeLanguageLookup[81] = 'Indonesian'; + $QuicktimeLanguageLookup[82] = 'Tagalog'; + $QuicktimeLanguageLookup[83] = 'MalayRoman'; + $QuicktimeLanguageLookup[84] = 'MalayArabic'; + $QuicktimeLanguageLookup[85] = 'Amharic'; + $QuicktimeLanguageLookup[86] = 'Tigrinya'; + $QuicktimeLanguageLookup[87] = 'Galla'; + $QuicktimeLanguageLookup[87] = 'Oromo'; + $QuicktimeLanguageLookup[88] = 'Somali'; + $QuicktimeLanguageLookup[89] = 'Swahili'; + $QuicktimeLanguageLookup[90] = 'Ruanda'; + $QuicktimeLanguageLookup[91] = 'Rundi'; + $QuicktimeLanguageLookup[92] = 'Chewa'; + $QuicktimeLanguageLookup[93] = 'Malagasy'; + $QuicktimeLanguageLookup[94] = 'Esperanto'; + $QuicktimeLanguageLookup[128] = 'Welsh'; + $QuicktimeLanguageLookup[129] = 'Basque'; + $QuicktimeLanguageLookup[130] = 'Catalan'; + $QuicktimeLanguageLookup[131] = 'Latin'; + $QuicktimeLanguageLookup[132] = 'Quechua'; + $QuicktimeLanguageLookup[133] = 'Guarani'; + $QuicktimeLanguageLookup[134] = 'Aymara'; + $QuicktimeLanguageLookup[135] = 'Tatar'; + $QuicktimeLanguageLookup[136] = 'Uighur'; + $QuicktimeLanguageLookup[137] = 'Dzongkha'; + $QuicktimeLanguageLookup[138] = 'JavaneseRom'; + } + return (isset($QuicktimeLanguageLookup[$languageid]) ? $QuicktimeLanguageLookup[$languageid] : 'invalid'); + } + + function QuicktimeVideoCodecLookup($codecid) { + static $QuicktimeVideoCodecLookup = array(); + if (empty($QuicktimeVideoCodecLookup)) { + $QuicktimeVideoCodecLookup['3IVX'] = '3ivx MPEG-4'; + $QuicktimeVideoCodecLookup['3IV1'] = '3ivx MPEG-4 v1'; + $QuicktimeVideoCodecLookup['3IV2'] = '3ivx MPEG-4 v2'; + $QuicktimeVideoCodecLookup['avr '] = 'AVR-JPEG'; + $QuicktimeVideoCodecLookup['base'] = 'Base'; + $QuicktimeVideoCodecLookup['WRLE'] = 'BMP'; + $QuicktimeVideoCodecLookup['cvid'] = 'Cinepak'; + $QuicktimeVideoCodecLookup['clou'] = 'Cloud'; + $QuicktimeVideoCodecLookup['cmyk'] = 'CMYK'; + $QuicktimeVideoCodecLookup['yuv2'] = 'ComponentVideo'; + $QuicktimeVideoCodecLookup['yuvu'] = 'ComponentVideoSigned'; + $QuicktimeVideoCodecLookup['yuvs'] = 'ComponentVideoUnsigned'; + $QuicktimeVideoCodecLookup['dvc '] = 'DVC-NTSC'; + $QuicktimeVideoCodecLookup['dvcp'] = 'DVC-PAL'; + $QuicktimeVideoCodecLookup['dvpn'] = 'DVCPro-NTSC'; + $QuicktimeVideoCodecLookup['dvpp'] = 'DVCPro-PAL'; + $QuicktimeVideoCodecLookup['fire'] = 'Fire'; + $QuicktimeVideoCodecLookup['flic'] = 'FLC'; + $QuicktimeVideoCodecLookup['b48r'] = '48RGB'; + $QuicktimeVideoCodecLookup['gif '] = 'GIF'; + $QuicktimeVideoCodecLookup['smc '] = 'Graphics'; + $QuicktimeVideoCodecLookup['h261'] = 'H261'; + $QuicktimeVideoCodecLookup['h263'] = 'H263'; + $QuicktimeVideoCodecLookup['IV41'] = 'Indeo4'; + $QuicktimeVideoCodecLookup['jpeg'] = 'JPEG'; + $QuicktimeVideoCodecLookup['PNTG'] = 'MacPaint'; + $QuicktimeVideoCodecLookup['msvc'] = 'Microsoft Video1'; + $QuicktimeVideoCodecLookup['mjpa'] = 'Motion JPEG-A'; + $QuicktimeVideoCodecLookup['mjpb'] = 'Motion JPEG-B'; + $QuicktimeVideoCodecLookup['myuv'] = 'MPEG YUV420'; + $QuicktimeVideoCodecLookup['dmb1'] = 'OpenDML JPEG'; + $QuicktimeVideoCodecLookup['kpcd'] = 'PhotoCD'; + $QuicktimeVideoCodecLookup['8BPS'] = 'Planar RGB'; + $QuicktimeVideoCodecLookup['png '] = 'PNG'; + $QuicktimeVideoCodecLookup['qdrw'] = 'QuickDraw'; + $QuicktimeVideoCodecLookup['qdgx'] = 'QuickDrawGX'; + $QuicktimeVideoCodecLookup['raw '] = 'RAW'; + $QuicktimeVideoCodecLookup['.SGI'] = 'SGI'; + $QuicktimeVideoCodecLookup['b16g'] = '16Gray'; + $QuicktimeVideoCodecLookup['b64a'] = '64ARGB'; + $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 1'; + $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 3'; + $QuicktimeVideoCodecLookup['syv9'] = 'Sorenson YUV9'; + $QuicktimeVideoCodecLookup['tga '] = 'Targa'; + $QuicktimeVideoCodecLookup['b32a'] = '32AlphaGray'; + $QuicktimeVideoCodecLookup['tiff'] = 'TIFF'; + $QuicktimeVideoCodecLookup['path'] = 'Vector'; + $QuicktimeVideoCodecLookup['rpza'] = 'Video'; + $QuicktimeVideoCodecLookup['ripl'] = 'WaterRipple'; + $QuicktimeVideoCodecLookup['WRAW'] = 'Windows RAW'; + $QuicktimeVideoCodecLookup['y420'] = 'YUV420'; + } + return (isset($QuicktimeVideoCodecLookup[$codecid]) ? $QuicktimeVideoCodecLookup[$codecid] : ''); + } + + function QuicktimeAudioCodecLookup($codecid) { + static $QuicktimeAudioCodecLookup = array(); + if (empty($QuicktimeAudioCodecLookup)) { + $QuicktimeAudioCodecLookup['.mp3'] = 'Fraunhofer MPEG Layer-III alias'; + $QuicktimeAudioCodecLookup['aac '] = 'ISO/IEC 14496-3 AAC'; + $QuicktimeAudioCodecLookup['agsm'] = 'Apple GSM 10:1'; + $QuicktimeAudioCodecLookup['alac'] = 'Apple Lossless Audio Codec'; + $QuicktimeAudioCodecLookup['alaw'] = 'A-law 2:1'; + $QuicktimeAudioCodecLookup['conv'] = 'Sample Format'; + $QuicktimeAudioCodecLookup['dvca'] = 'DV'; + $QuicktimeAudioCodecLookup['dvi '] = 'DV 4:1'; + $QuicktimeAudioCodecLookup['eqal'] = 'Frequency Equalizer'; + $QuicktimeAudioCodecLookup['fl32'] = '32-bit Floating Point'; + $QuicktimeAudioCodecLookup['fl64'] = '64-bit Floating Point'; + $QuicktimeAudioCodecLookup['ima4'] = 'Interactive Multimedia Association 4:1'; + $QuicktimeAudioCodecLookup['in24'] = '24-bit Integer'; + $QuicktimeAudioCodecLookup['in32'] = '32-bit Integer'; + $QuicktimeAudioCodecLookup['lpc '] = 'LPC 23:1'; + $QuicktimeAudioCodecLookup['MAC3'] = 'Macintosh Audio Compression/Expansion (MACE) 3:1'; + $QuicktimeAudioCodecLookup['MAC6'] = 'Macintosh Audio Compression/Expansion (MACE) 6:1'; + $QuicktimeAudioCodecLookup['mixb'] = '8-bit Mixer'; + $QuicktimeAudioCodecLookup['mixw'] = '16-bit Mixer'; + $QuicktimeAudioCodecLookup['mp4a'] = 'ISO/IEC 14496-3 AAC'; + $QuicktimeAudioCodecLookup['MS'."\x00\x02"] = 'Microsoft ADPCM'; + $QuicktimeAudioCodecLookup['MS'."\x00\x11"] = 'DV IMA'; + $QuicktimeAudioCodecLookup['MS'."\x00\x55"] = 'Fraunhofer MPEG Layer III'; + $QuicktimeAudioCodecLookup['NONE'] = 'No Encoding'; + $QuicktimeAudioCodecLookup['Qclp'] = 'Qualcomm PureVoice'; + $QuicktimeAudioCodecLookup['QDM2'] = 'QDesign Music 2'; + $QuicktimeAudioCodecLookup['QDMC'] = 'QDesign Music 1'; + $QuicktimeAudioCodecLookup['ratb'] = '8-bit Rate'; + $QuicktimeAudioCodecLookup['ratw'] = '16-bit Rate'; + $QuicktimeAudioCodecLookup['raw '] = 'raw PCM'; + $QuicktimeAudioCodecLookup['sour'] = 'Sound Source'; + $QuicktimeAudioCodecLookup['sowt'] = 'signed/two\'s complement (Little Endian)'; + $QuicktimeAudioCodecLookup['str1'] = 'Iomega MPEG layer II'; + $QuicktimeAudioCodecLookup['str2'] = 'Iomega MPEG *layer II'; + $QuicktimeAudioCodecLookup['str3'] = 'Iomega MPEG **layer II'; + $QuicktimeAudioCodecLookup['str4'] = 'Iomega MPEG ***layer II'; + $QuicktimeAudioCodecLookup['twos'] = 'signed/two\'s complement (Big Endian)'; + $QuicktimeAudioCodecLookup['ulaw'] = 'mu-law 2:1'; + } + return (isset($QuicktimeAudioCodecLookup[$codecid]) ? $QuicktimeAudioCodecLookup[$codecid] : ''); + } + + function QuicktimeDCOMLookup($compressionid) { + static $QuicktimeDCOMLookup = array(); + if (empty($QuicktimeDCOMLookup)) { + $QuicktimeDCOMLookup['zlib'] = 'ZLib Deflate'; + $QuicktimeDCOMLookup['adec'] = 'Apple Compression'; + } + return (isset($QuicktimeDCOMLookup[$compressionid]) ? $QuicktimeDCOMLookup[$compressionid] : ''); + } + + function QuicktimeColorNameLookup($colordepthid) { + static $QuicktimeColorNameLookup = array(); + if (empty($QuicktimeColorNameLookup)) { + $QuicktimeColorNameLookup[1] = '2-color (monochrome)'; + $QuicktimeColorNameLookup[2] = '4-color'; + $QuicktimeColorNameLookup[4] = '16-color'; + $QuicktimeColorNameLookup[8] = '256-color'; + $QuicktimeColorNameLookup[16] = 'thousands (16-bit color)'; + $QuicktimeColorNameLookup[24] = 'millions (24-bit color)'; + $QuicktimeColorNameLookup[32] = 'millions+ (32-bit color)'; + $QuicktimeColorNameLookup[33] = 'black & white'; + $QuicktimeColorNameLookup[34] = '4-gray'; + $QuicktimeColorNameLookup[36] = '16-gray'; + $QuicktimeColorNameLookup[40] = '256-gray'; + } + return (isset($QuicktimeColorNameLookup[$colordepthid]) ? $QuicktimeColorNameLookup[$colordepthid] : 'invalid'); + } + + function CopyToAppropriateCommentsSection($keyname, $data, &$ThisFileInfo) { + static $handyatomtranslatorarray = array(); + if (empty($handyatomtranslatorarray)) { + $handyatomtranslatorarray['©cpy'] = 'copyright'; + $handyatomtranslatorarray['©day'] = 'creation_date'; + $handyatomtranslatorarray['©dir'] = 'director'; + $handyatomtranslatorarray['©ed1'] = 'edit1'; + $handyatomtranslatorarray['©ed2'] = 'edit2'; + $handyatomtranslatorarray['©ed3'] = 'edit3'; + $handyatomtranslatorarray['©ed4'] = 'edit4'; + $handyatomtranslatorarray['©ed5'] = 'edit5'; + $handyatomtranslatorarray['©ed6'] = 'edit6'; + $handyatomtranslatorarray['©ed7'] = 'edit7'; + $handyatomtranslatorarray['©ed8'] = 'edit8'; + $handyatomtranslatorarray['©ed9'] = 'edit9'; + $handyatomtranslatorarray['©fmt'] = 'format'; + $handyatomtranslatorarray['©inf'] = 'information'; + $handyatomtranslatorarray['©prd'] = 'producer'; + $handyatomtranslatorarray['©prf'] = 'performers'; + $handyatomtranslatorarray['©req'] = 'system_requirements'; + $handyatomtranslatorarray['©src'] = 'source_credit'; + $handyatomtranslatorarray['©wrt'] = 'writer'; + + // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt + $handyatomtranslatorarray['©nam'] = 'title'; + $handyatomtranslatorarray['©cmt'] = 'comment'; + $handyatomtranslatorarray['©wrn'] = 'warning'; + $handyatomtranslatorarray['©hst'] = 'host_computer'; + $handyatomtranslatorarray['©mak'] = 'make'; + $handyatomtranslatorarray['©mod'] = 'model'; + $handyatomtranslatorarray['©PRD'] = 'product'; + $handyatomtranslatorarray['©swr'] = 'software'; + $handyatomtranslatorarray['©aut'] = 'author'; + $handyatomtranslatorarray['©ART'] = 'artist'; + $handyatomtranslatorarray['©trk'] = 'track'; + $handyatomtranslatorarray['©alb'] = 'album'; + $handyatomtranslatorarray['©com'] = 'comment'; + $handyatomtranslatorarray['©gen'] = 'genre'; + $handyatomtranslatorarray['©ope'] = 'composer'; + $handyatomtranslatorarray['©url'] = 'url'; + $handyatomtranslatorarray['©enc'] = 'encoder'; + } + if (isset($handyatomtranslatorarray[$keyname])) { + $ThisFileInfo['quicktime']['comments'][$handyatomtranslatorarray[$keyname]][] = $data; + } + + return true; + } + + function NoNullString($nullterminatedstring) { + // remove the single null terminator on null terminated strings + if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") { + return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1); + } + return $nullterminatedstring; + } + + function Pascal2String($pascalstring) { + // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string + return substr($pascalstring, 1); + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio-video.real.php b/modules/id3/getid3/module.audio-video.real.php new file mode 100644 index 00000000..23c8fefc --- /dev/null +++ b/modules/id3/getid3/module.audio-video.real.php @@ -0,0 +1,528 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.real.php // +// module for analyzing Real Audio/Video files // +// dependencies: module.audio-video.riff.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + +class getid3_real +{ + + function getid3_real(&$fd, &$ThisFileInfo) { + $ThisFileInfo['fileformat'] = 'real'; + $ThisFileInfo['bitrate'] = 0; + $ThisFileInfo['playtime_seconds'] = 0; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $ChunkCounter = 0; + while (ftell($fd) < $ThisFileInfo['avdataend']) { + $ChunkData = fread($fd, 8); + $ChunkName = substr($ChunkData, 0, 4); + $ChunkSize = getid3_lib::BigEndian2Int(substr($ChunkData, 4, 4)); + + if ($ChunkName == '.ra'."\xFD") { + $ChunkData .= fread($fd, $ChunkSize - 8); + if ($this->ParseOldRAheader(substr($ChunkData, 0, 128), $ThisFileInfo['real']['old_ra_header'])) { + $ThisFileInfo['audio']['dataformat'] = 'real'; + $ThisFileInfo['audio']['lossless'] = false; + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['real']['old_ra_header']['sample_rate']; + $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['real']['old_ra_header']['bits_per_sample']; + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['real']['old_ra_header']['channels']; + + $ThisFileInfo['playtime_seconds'] = 60 * ($ThisFileInfo['real']['old_ra_header']['audio_bytes'] / $ThisFileInfo['real']['old_ra_header']['bytes_per_minute']); + $ThisFileInfo['audio']['bitrate'] = 8 * ($ThisFileInfo['real']['old_ra_header']['audio_bytes'] / $ThisFileInfo['playtime_seconds']); + $ThisFileInfo['audio']['codec'] = $this->RealAudioCodecFourCClookup($ThisFileInfo['real']['old_ra_header']['fourcc'], $ThisFileInfo['audio']['bitrate']); + + foreach ($ThisFileInfo['real']['old_ra_header']['comments'] as $key => $valuearray) { + if (strlen(trim($valuearray[0])) > 0) { + $ThisFileInfo['real']['comments'][$key][] = trim($valuearray[0]); + } + } + return true; + } + $ThisFileInfo['error'][] = 'There was a problem parsing this RealAudio file. Please submit it for analysis to http://www.getid3.org/upload/ or info@getid3.org'; + unset($ThisFileInfo['bitrate']); + unset($ThisFileInfo['playtime_seconds']); + return false; + } + + // shortcut + $ThisFileInfo['real']['chunks'][$ChunkCounter] = array(); + $thisfile_real_chunks_currentchunk = &$ThisFileInfo['real']['chunks'][$ChunkCounter]; + + $thisfile_real_chunks_currentchunk['name'] = $ChunkName; + $thisfile_real_chunks_currentchunk['offset'] = ftell($fd) - 8; + $thisfile_real_chunks_currentchunk['length'] = $ChunkSize; + if (($thisfile_real_chunks_currentchunk['offset'] + $thisfile_real_chunks_currentchunk['length']) > $ThisFileInfo['avdataend']) { + $ThisFileInfo['warning'][] = 'Chunk "'.$thisfile_real_chunks_currentchunk['name'].'" at offset '.$thisfile_real_chunks_currentchunk['offset'].' claims to be '.$thisfile_real_chunks_currentchunk['length'].' bytes long, which is beyond end of file'; + return false; + } + + if ($ChunkSize > (GETID3_FREAD_BUFFER_SIZE + 8)) { + + $ChunkData .= fread($fd, GETID3_FREAD_BUFFER_SIZE - 8); + fseek($fd, $thisfile_real_chunks_currentchunk['offset'] + $ChunkSize, SEEK_SET); + + } else { + + $ChunkData .= fread($fd, $ChunkSize - 8); + + } + $offset = 8; + + switch ($ChunkName) { + + case '.RMF': // RealMedia File Header + $thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); + $offset += 2; + switch ($thisfile_real_chunks_currentchunk['object_version']) { + + case 0: + $thisfile_real_chunks_currentchunk['file_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['headers_count'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + break; + + default: + //$ThisFileInfo['warning'][] = 'Expected .RMF-object_version to be "0", actual value is "'.$thisfile_real_chunks_currentchunk['object_version'].'" (should not be a problem)'; + break; + + } + break; + + + case 'PROP': // Properties Header + $thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); + $offset += 2; + if ($thisfile_real_chunks_currentchunk['object_version'] == 0) { + $thisfile_real_chunks_currentchunk['max_bit_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['avg_bit_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['max_packet_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['avg_packet_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['num_packets'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['duration'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['preroll'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['index_offset'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['data_offset'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['num_streams'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); + $offset += 2; + $thisfile_real_chunks_currentchunk['flags_raw'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); + $offset += 2; + $ThisFileInfo['playtime_seconds'] = $thisfile_real_chunks_currentchunk['duration'] / 1000; + if ($thisfile_real_chunks_currentchunk['duration'] > 0) { + $ThisFileInfo['bitrate'] += $thisfile_real_chunks_currentchunk['avg_bit_rate']; + } + $thisfile_real_chunks_currentchunk['flags']['save_enabled'] = (bool) ($thisfile_real_chunks_currentchunk['flags_raw'] & 0x0001); + $thisfile_real_chunks_currentchunk['flags']['perfect_play'] = (bool) ($thisfile_real_chunks_currentchunk['flags_raw'] & 0x0002); + $thisfile_real_chunks_currentchunk['flags']['live_broadcast'] = (bool) ($thisfile_real_chunks_currentchunk['flags_raw'] & 0x0004); + } + break; + + case 'MDPR': // Media Properties Header + $thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); + $offset += 2; + if ($thisfile_real_chunks_currentchunk['object_version'] == 0) { + $thisfile_real_chunks_currentchunk['stream_number'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); + $offset += 2; + $thisfile_real_chunks_currentchunk['max_bit_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['avg_bit_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['max_packet_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['avg_packet_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['start_time'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['preroll'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['duration'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['stream_name_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 1)); + $offset += 1; + $thisfile_real_chunks_currentchunk['stream_name'] = substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['stream_name_size']); + $offset += $thisfile_real_chunks_currentchunk['stream_name_size']; + $thisfile_real_chunks_currentchunk['mime_type_size'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 1)); + $offset += 1; + $thisfile_real_chunks_currentchunk['mime_type'] = substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['mime_type_size']); + $offset += $thisfile_real_chunks_currentchunk['mime_type_size']; + $thisfile_real_chunks_currentchunk['type_specific_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['type_specific_data'] = substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['type_specific_len']); + $offset += $thisfile_real_chunks_currentchunk['type_specific_len']; + + // shortcut + $thisfile_real_chunks_currentchunk_typespecificdata = &$thisfile_real_chunks_currentchunk['type_specific_data']; + + switch ($thisfile_real_chunks_currentchunk['mime_type']) { + case 'video/x-pn-realvideo': + case 'video/x-pn-multirate-realvideo': + // http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html + + // shortcut + $thisfile_real_chunks_currentchunk['video_info'] = array(); + $thisfile_real_chunks_currentchunk_videoinfo = &$thisfile_real_chunks_currentchunk['video_info']; + + $thisfile_real_chunks_currentchunk_videoinfo['dwSize'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 0, 4)); + $thisfile_real_chunks_currentchunk_videoinfo['fourcc1'] = substr($thisfile_real_chunks_currentchunk_typespecificdata, 4, 4); + $thisfile_real_chunks_currentchunk_videoinfo['fourcc2'] = substr($thisfile_real_chunks_currentchunk_typespecificdata, 8, 4); + $thisfile_real_chunks_currentchunk_videoinfo['width'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 12, 2)); + $thisfile_real_chunks_currentchunk_videoinfo['height'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 14, 2)); + $thisfile_real_chunks_currentchunk_videoinfo['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 16, 2)); + //$thisfile_real_chunks_currentchunk_videoinfo['unknown1'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 18, 2)); + //$thisfile_real_chunks_currentchunk_videoinfo['unknown2'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 20, 2)); + $thisfile_real_chunks_currentchunk_videoinfo['frames_per_second'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 22, 2)); + //$thisfile_real_chunks_currentchunk_videoinfo['unknown3'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 24, 2)); + //$thisfile_real_chunks_currentchunk_videoinfo['unknown4'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 26, 2)); + //$thisfile_real_chunks_currentchunk_videoinfo['unknown5'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 28, 2)); + //$thisfile_real_chunks_currentchunk_videoinfo['unknown6'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 30, 2)); + //$thisfile_real_chunks_currentchunk_videoinfo['unknown7'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 32, 2)); + //$thisfile_real_chunks_currentchunk_videoinfo['unknown8'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 34, 2)); + //$thisfile_real_chunks_currentchunk_videoinfo['unknown9'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 36, 2)); + + $thisfile_real_chunks_currentchunk_videoinfo['codec'] = getid3_riff::RIFFfourccLookup($thisfile_real_chunks_currentchunk_videoinfo['fourcc2']); + + $ThisFileInfo['video']['resolution_x'] = $thisfile_real_chunks_currentchunk_videoinfo['width']; + $ThisFileInfo['video']['resolution_y'] = $thisfile_real_chunks_currentchunk_videoinfo['height']; + $ThisFileInfo['video']['frame_rate'] = (float) $thisfile_real_chunks_currentchunk_videoinfo['frames_per_second']; + $ThisFileInfo['video']['codec'] = $thisfile_real_chunks_currentchunk_videoinfo['codec']; + $ThisFileInfo['video']['bits_per_sample'] = $thisfile_real_chunks_currentchunk_videoinfo['bits_per_sample']; + break; + + case 'audio/x-pn-realaudio': + case 'audio/x-pn-multirate-realaudio': + $this->ParseOldRAheader($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk['parsed_audio_data']); + + $ThisFileInfo['audio']['sample_rate'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['sample_rate']; + $ThisFileInfo['audio']['bits_per_sample'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['bits_per_sample']; + $ThisFileInfo['audio']['channels'] = $thisfile_real_chunks_currentchunk['parsed_audio_data']['channels']; + if (!empty($ThisFileInfo['audio']['dataformat'])) { + foreach ($ThisFileInfo['audio'] as $key => $value) { + if ($key != 'streams') { + $ThisFileInfo['audio']['streams'][$thisfile_real_chunks_currentchunk['stream_number']][$key] = $value; + } + } + } + break; + + case 'logical-fileinfo': + // shortcut + $thisfile_real_chunks_currentchunk['logical_fileinfo'] = array(); + $thisfile_real_chunks_currentchunk_logicalfileinfo = &$thisfile_real_chunks_currentchunk['logical_fileinfo']; + + $thisfile_real_chunks_currentchunk_logicalfileinfo_offset = 0; + $thisfile_real_chunks_currentchunk_logicalfileinfo['logical_fileinfo_length'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4)); + $thisfile_real_chunks_currentchunk_logicalfileinfo_offset += 4; + + //$thisfile_real_chunks_currentchunk_logicalfileinfo['unknown1'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4)); + $thisfile_real_chunks_currentchunk_logicalfileinfo_offset += 4; + + $thisfile_real_chunks_currentchunk_logicalfileinfo['num_tags'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4)); + $thisfile_real_chunks_currentchunk_logicalfileinfo_offset += 4; + + //$thisfile_real_chunks_currentchunk_logicalfileinfo['unknown2'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4)); + $thisfile_real_chunks_currentchunk_logicalfileinfo_offset += 4; + + //$thisfile_real_chunks_currentchunk_logicalfileinfo['d'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 1)); + + //$thisfile_real_chunks_currentchunk_logicalfileinfo['one_type'] = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 4)); + //$thisfile_real_chunks_currentchunk_logicalfileinfo_thislength = getid3_lib::BigEndian2Int(substr($thisfile_real_chunks_currentchunk_typespecificdata, 4 + $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, 2)); + //$thisfile_real_chunks_currentchunk_logicalfileinfo['one'] = substr($thisfile_real_chunks_currentchunk_typespecificdata, 6 + $thisfile_real_chunks_currentchunk_logicalfileinfo_offset, $thisfile_real_chunks_currentchunk_logicalfileinfo_thislength); + //$thisfile_real_chunks_currentchunk_logicalfileinfo_offset += (6 + $thisfile_real_chunks_currentchunk_logicalfileinfo_thislength); + + break; + + } + + + if (empty($ThisFileInfo['playtime_seconds'])) { + $ThisFileInfo['playtime_seconds'] = max($ThisFileInfo['playtime_seconds'], ($thisfile_real_chunks_currentchunk['duration'] + $thisfile_real_chunks_currentchunk['start_time']) / 1000); + } + if ($thisfile_real_chunks_currentchunk['duration'] > 0) { + switch ($thisfile_real_chunks_currentchunk['mime_type']) { + case 'audio/x-pn-realaudio': + case 'audio/x-pn-multirate-realaudio': + $ThisFileInfo['audio']['bitrate'] = (isset($ThisFileInfo['audio']['bitrate']) ? $ThisFileInfo['audio']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate']; + $ThisFileInfo['audio']['codec'] = $this->RealAudioCodecFourCClookup($thisfile_real_chunks_currentchunk['parsed_audio_data']['fourcc'], $ThisFileInfo['audio']['bitrate']); + $ThisFileInfo['audio']['dataformat'] = 'real'; + $ThisFileInfo['audio']['lossless'] = false; + break; + + case 'video/x-pn-realvideo': + case 'video/x-pn-multirate-realvideo': + $ThisFileInfo['video']['bitrate'] = (isset($ThisFileInfo['video']['bitrate']) ? $ThisFileInfo['video']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate']; + $ThisFileInfo['video']['bitrate_mode'] = 'cbr'; + $ThisFileInfo['video']['dataformat'] = 'real'; + $ThisFileInfo['video']['lossless'] = false; + $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1; + break; + + case 'audio/x-ralf-mpeg4-generic': + $ThisFileInfo['audio']['bitrate'] = (isset($ThisFileInfo['audio']['bitrate']) ? $ThisFileInfo['audio']['bitrate'] : 0) + $thisfile_real_chunks_currentchunk['avg_bit_rate']; + $ThisFileInfo['audio']['codec'] = 'RealAudio Lossless'; + $ThisFileInfo['audio']['dataformat'] = 'real'; + $ThisFileInfo['audio']['lossless'] = true; + break; + } + $ThisFileInfo['bitrate'] = (isset($ThisFileInfo['video']['bitrate']) ? $ThisFileInfo['video']['bitrate'] : 0) + (isset($ThisFileInfo['audio']['bitrate']) ? $ThisFileInfo['audio']['bitrate'] : 0); + } + } + break; + + case 'CONT': // Content Description Header (text comments) + $thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); + $offset += 2; + if ($thisfile_real_chunks_currentchunk['object_version'] == 0) { + $thisfile_real_chunks_currentchunk['title_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); + $offset += 2; + $thisfile_real_chunks_currentchunk['title'] = (string) substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['title_len']); + $offset += $thisfile_real_chunks_currentchunk['title_len']; + + $thisfile_real_chunks_currentchunk['artist_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); + $offset += 2; + $thisfile_real_chunks_currentchunk['artist'] = (string) substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['artist_len']); + $offset += $thisfile_real_chunks_currentchunk['artist_len']; + + $thisfile_real_chunks_currentchunk['copyright_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); + $offset += 2; + $thisfile_real_chunks_currentchunk['copyright'] = (string) substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['copyright_len']); + $offset += $thisfile_real_chunks_currentchunk['copyright_len']; + + $thisfile_real_chunks_currentchunk['comment_len'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); + $offset += 2; + $thisfile_real_chunks_currentchunk['comment'] = (string) substr($ChunkData, $offset, $thisfile_real_chunks_currentchunk['comment_len']); + $offset += $thisfile_real_chunks_currentchunk['comment_len']; + + + $commentkeystocopy = array('title'=>'title', 'artist'=>'artist', 'copyright'=>'copyright', 'comment'=>'comment'); + foreach ($commentkeystocopy as $key => $val) { + if ($thisfile_real_chunks_currentchunk[$key]) { + $ThisFileInfo['real']['comments'][$val][] = trim($thisfile_real_chunks_currentchunk[$key]); + } + } + + } + break; + + + case 'DATA': // Data Chunk Header + // do nothing + break; + + case 'INDX': // Index Section Header + $thisfile_real_chunks_currentchunk['object_version'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); + $offset += 2; + if ($thisfile_real_chunks_currentchunk['object_version'] == 0) { + $thisfile_real_chunks_currentchunk['num_indices'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + $thisfile_real_chunks_currentchunk['stream_number'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 2)); + $offset += 2; + $thisfile_real_chunks_currentchunk['next_index_header'] = getid3_lib::BigEndian2Int(substr($ChunkData, $offset, 4)); + $offset += 4; + + if ($thisfile_real_chunks_currentchunk['next_index_header'] == 0) { + // last index chunk found, ignore rest of file + break 2; + } else { + // non-last index chunk, seek to next index chunk (skipping actual index data) + fseek($fd, $thisfile_real_chunks_currentchunk['next_index_header'], SEEK_SET); + } + } + break; + + default: + $ThisFileInfo['warning'][] = 'Unhandled RealMedia chunk "'.$ChunkName.'" at offset '.$thisfile_real_chunks_currentchunk['offset']; + break; + } + $ChunkCounter++; + } + + if (!empty($ThisFileInfo['audio']['streams'])) { + $ThisFileInfo['audio']['bitrate'] = 0; + foreach ($ThisFileInfo['audio']['streams'] as $key => $valuearray) { + $ThisFileInfo['audio']['bitrate'] += $valuearray['bitrate']; + } + } + + return true; + } + + + function ParseOldRAheader($OldRAheaderData, &$ParsedArray) { + // http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html + + $ParsedArray = array(); + $ParsedArray['magic'] = substr($OldRAheaderData, 0, 4); + if ($ParsedArray['magic'] != '.ra'."\xFD") { + return false; + } + $ParsedArray['version1'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 4, 2)); + + if ($ParsedArray['version1'] < 3) { + + return false; + + } elseif ($ParsedArray['version1'] == 3) { + + $ParsedArray['fourcc1'] = '.ra3'; + $ParsedArray['bits_per_sample'] = 16; // hard-coded for old versions? + $ParsedArray['sample_rate'] = 8000; // hard-coded for old versions? + + $ParsedArray['header_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 6, 2)); + $ParsedArray['channels'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 8, 2)); // always 1 (?) + //$ParsedArray['unknown1'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 10, 2)); + //$ParsedArray['unknown2'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 12, 2)); + //$ParsedArray['unknown3'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 14, 2)); + $ParsedArray['bytes_per_minute'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 16, 2)); + $ParsedArray['audio_bytes'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 18, 4)); + $ParsedArray['comments_raw'] = substr($OldRAheaderData, 22, $ParsedArray['header_size'] - 22 + 1); // not including null terminator + + $commentoffset = 0; + $commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1)); + $ParsedArray['comments']['title'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength); + $commentoffset += $commentlength; + + $commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1)); + $ParsedArray['comments']['artist'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength); + $commentoffset += $commentlength; + + $commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1)); + $ParsedArray['comments']['copyright'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength); + $commentoffset += $commentlength; + + $commentoffset++; // final null terminator (?) + $commentoffset++; // fourcc length (?) should be 4 + $ParsedArray['fourcc'] = substr($OldRAheaderData, 23 + $commentoffset, 4); + + } elseif ($ParsedArray['version1'] <= 5) { + + //$ParsedArray['unknown1'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 6, 2)); + $ParsedArray['fourcc1'] = substr($OldRAheaderData, 8, 4); + $ParsedArray['file_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 12, 4)); + $ParsedArray['version2'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 16, 2)); + $ParsedArray['header_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 18, 4)); + $ParsedArray['codec_flavor_id'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 22, 2)); + $ParsedArray['coded_frame_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 24, 4)); + $ParsedArray['audio_bytes'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 28, 4)); + $ParsedArray['bytes_per_minute'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 32, 4)); + //$ParsedArray['unknown5'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 36, 4)); + $ParsedArray['sub_packet_h'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 40, 2)); + $ParsedArray['frame_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 42, 2)); + $ParsedArray['sub_packet_size'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 44, 2)); + //$ParsedArray['unknown6'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 46, 2)); + + switch ($ParsedArray['version1']) { + + case 4: + $ParsedArray['sample_rate'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 48, 2)); + //$ParsedArray['unknown8'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 50, 2)); + $ParsedArray['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 52, 2)); + $ParsedArray['channels'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 54, 2)); + $ParsedArray['length_fourcc2'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 56, 1)); + $ParsedArray['fourcc2'] = substr($OldRAheaderData, 57, 4); + $ParsedArray['length_fourcc3'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 61, 1)); + $ParsedArray['fourcc3'] = substr($OldRAheaderData, 62, 4); + //$ParsedArray['unknown9'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 66, 1)); + //$ParsedArray['unknown10'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 67, 2)); + $ParsedArray['comments_raw'] = substr($OldRAheaderData, 69, $ParsedArray['header_size'] - 69 + 16); + + $commentoffset = 0; + $commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1)); + $ParsedArray['comments']['title'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength); + $commentoffset += $commentlength; + + $commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1)); + $ParsedArray['comments']['artist'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength); + $commentoffset += $commentlength; + + $commentlength = getid3_lib::BigEndian2Int(substr($ParsedArray['comments_raw'], $commentoffset++, 1)); + $ParsedArray['comments']['copyright'][] = substr($ParsedArray['comments_raw'], $commentoffset, $commentlength); + $commentoffset += $commentlength; + break; + + case 5: + $ParsedArray['sample_rate'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 48, 4)); + $ParsedArray['sample_rate2'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 52, 4)); + $ParsedArray['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 56, 4)); + $ParsedArray['channels'] = getid3_lib::BigEndian2Int(substr($OldRAheaderData, 60, 2)); + $ParsedArray['genr'] = substr($OldRAheaderData, 62, 4); + $ParsedArray['fourcc3'] = substr($OldRAheaderData, 66, 4); + $ParsedArray['comments'] = array(); + break; + } + $ParsedArray['fourcc'] = $ParsedArray['fourcc3']; + + } + foreach ($ParsedArray['comments'] as $key => $value) { + if ($ParsedArray['comments'][$key][0] === false) { + $ParsedArray['comments'][$key][0] = ''; + } + } + + return true; + } + + function RealAudioCodecFourCClookup($fourcc, $bitrate) { + static $RealAudioCodecFourCClookup = array(); + if (empty($RealAudioCodecFourCClookup)) { + // http://www.its.msstate.edu/net/real/reports/config/tags.stats + // http://www.freelists.org/archives/matroska-devel/06-2003/fullthread18.html + + $RealAudioCodecFourCClookup['14_4'][8000] = 'RealAudio v2 (14.4kbps)'; + $RealAudioCodecFourCClookup['14.4'][8000] = 'RealAudio v2 (14.4kbps)'; + $RealAudioCodecFourCClookup['lpcJ'][8000] = 'RealAudio v2 (14.4kbps)'; + $RealAudioCodecFourCClookup['28_8'][15200] = 'RealAudio v2 (28.8kbps)'; + $RealAudioCodecFourCClookup['28.8'][15200] = 'RealAudio v2 (28.8kbps)'; + $RealAudioCodecFourCClookup['sipr'][4933] = 'RealAudio v4 (5kbps Voice)'; + $RealAudioCodecFourCClookup['sipr'][6444] = 'RealAudio v4 (6.5kbps Voice)'; + $RealAudioCodecFourCClookup['sipr'][8444] = 'RealAudio v4 (8.5kbps Voice)'; + $RealAudioCodecFourCClookup['sipr'][16000] = 'RealAudio v4 (16kbps Wideband)'; + $RealAudioCodecFourCClookup['dnet'][8000] = 'RealAudio v3 (8kbps Music)'; + $RealAudioCodecFourCClookup['dnet'][16000] = 'RealAudio v3 (16kbps Music Low Response)'; + $RealAudioCodecFourCClookup['dnet'][15963] = 'RealAudio v3 (16kbps Music Mid/High Response)'; + $RealAudioCodecFourCClookup['dnet'][20000] = 'RealAudio v3 (20kbps Music Stereo)'; + $RealAudioCodecFourCClookup['dnet'][32000] = 'RealAudio v3 (32kbps Music Mono)'; + $RealAudioCodecFourCClookup['dnet'][31951] = 'RealAudio v3 (32kbps Music Stereo)'; + $RealAudioCodecFourCClookup['dnet'][39965] = 'RealAudio v3 (40kbps Music Mono)'; + $RealAudioCodecFourCClookup['dnet'][40000] = 'RealAudio v3 (40kbps Music Stereo)'; + $RealAudioCodecFourCClookup['dnet'][79947] = 'RealAudio v3 (80kbps Music Mono)'; + $RealAudioCodecFourCClookup['dnet'][80000] = 'RealAudio v3 (80kbps Music Stereo)'; + + $RealAudioCodecFourCClookup['dnet'][0] = 'RealAudio v3'; + $RealAudioCodecFourCClookup['sipr'][0] = 'RealAudio v4'; + $RealAudioCodecFourCClookup['cook'][0] = 'RealAudio G2'; + $RealAudioCodecFourCClookup['atrc'][0] = 'RealAudio 8'; + } + $roundbitrate = intval(round($bitrate)); + if (isset($RealAudioCodecFourCClookup[$fourcc][$roundbitrate])) { + return $RealAudioCodecFourCClookup[$fourcc][$roundbitrate]; + } elseif (isset($RealAudioCodecFourCClookup[$fourcc][0])) { + return $RealAudioCodecFourCClookup[$fourcc][0]; + } + return $fourcc; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio-video.riff.php b/modules/id3/getid3/module.audio-video.riff.php new file mode 100644 index 00000000..30db6db9 --- /dev/null +++ b/modules/id3/getid3/module.audio-video.riff.php @@ -0,0 +1,1995 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.riff.php // +// module for analyzing RIFF files // +// multiple formats supported by this module: // +// Wave, AVI, AIFF/AIFC, (MP3,AC3)/RIFF, Wavpack v3, 8SVX // +// dependencies: module.audio.mp3.php // +// module.audio.ac3.php (optional) // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true); + +class getid3_riff +{ + + function getid3_riff(&$fd, &$ThisFileInfo) { + + // initialize these values to an empty array, otherwise they default to NULL + // and you can't append array values to a NULL value + $ThisFileInfo['riff'] = array('raw'=>array()); + + // Shortcuts + $thisfile_riff = &$ThisFileInfo['riff']; + $thisfile_riff_raw = &$thisfile_riff['raw']; + $thisfile_audio = &$ThisFileInfo['audio']; + $thisfile_video = &$ThisFileInfo['video']; + $thisfile_avdataoffset = &$ThisFileInfo['avdataoffset']; + $thisfile_avdataend = &$ThisFileInfo['avdataend']; + $thisfile_audio_dataformat = &$thisfile_audio['dataformat']; + $thisfile_riff_audio = &$thisfile_riff['audio']; + $thisfile_riff_video = &$thisfile_riff['video']; + + + $Original['avdataoffset'] = $thisfile_avdataoffset; + $Original['avdataend'] = $thisfile_avdataend; + + fseek($fd, $thisfile_avdataoffset, SEEK_SET); + $RIFFheader = fread($fd, 12); + $RIFFsubtype = substr($RIFFheader, 8, 4); + switch (substr($RIFFheader, 0, 4)) { + case 'FORM': + $ThisFileInfo['fileformat'] = 'aiff'; + $RIFFheaderSize = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($RIFFheader, 4, 4)); + $thisfile_riff[$RIFFsubtype] = getid3_riff::ParseRIFF($fd, $thisfile_avdataoffset + 12, $thisfile_avdataoffset + $RIFFheaderSize, $ThisFileInfo); + $thisfile_riff['header_size'] = $RIFFheaderSize; + break; + + case 'RIFF': + case 'SDSS': // SDSS is identical to RIFF, just renamed. Used by SmartSound QuickTracks (www.smartsound.com) + case 'RMP3': // RMP3 is identical to RIFF, just renamed. Used by [unknown program] when creating RIFF-MP3s + if ($RIFFsubtype == 'RMP3') { + // RMP3 is identical to WAVE, just renamed. Used by [unknown program] when creating RIFF-MP3s + $RIFFsubtype = 'WAVE'; + } + + $ThisFileInfo['fileformat'] = 'riff'; + $RIFFheaderSize = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($RIFFheader, 4, 4)); + $thisfile_riff[$RIFFsubtype] = getid3_riff::ParseRIFF($fd, $thisfile_avdataoffset + 12, $thisfile_avdataoffset + $RIFFheaderSize, $ThisFileInfo); + $thisfile_riff['header_size'] = $RIFFheaderSize; + if ($RIFFsubtype == 'WAVE') { + $thisfile_riff_WAVE = &$thisfile_riff['WAVE']; + } + break; + + default: + $ThisFileInfo['error'][] = 'Cannot parse RIFF (this is maybe not a RIFF / WAV / AVI file?) - expecting "FORM|RIFF|SDSS|RMP3" found "'.$RIFFsubtype.'" instead'; + unset($ThisFileInfo['fileformat']); + return false; + break; + } + + $streamindex = 0; + switch ($RIFFsubtype) { + case 'WAVE': + if (empty($thisfile_audio['bitrate_mode'])) { + $thisfile_audio['bitrate_mode'] = 'cbr'; + } + if (empty($thisfile_audio_dataformat)) { + $thisfile_audio_dataformat = 'wav'; + } + + if (isset($thisfile_riff_WAVE['data'][0]['offset'])) { + $thisfile_avdataoffset = $thisfile_riff_WAVE['data'][0]['offset'] + 8; + $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff_WAVE['data'][0]['size']; + } + if (isset($thisfile_riff_WAVE['fmt '][0]['data'])) { + + $thisfile_riff_audio[$streamindex] = getid3_riff::RIFFparseWAVEFORMATex($thisfile_riff_WAVE['fmt '][0]['data']); + $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; + if (@$thisfile_riff_audio[$streamindex]['bitrate'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt RIFF file: bitrate_audio == zero'; + return false; + } + $thisfile_riff_raw['fmt '] = $thisfile_riff_audio[$streamindex]['raw']; + unset($thisfile_riff_audio[$streamindex]['raw']); + $thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex]; + + $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]); + if (substr($thisfile_audio['codec'], 0, strlen('unknown: 0x')) == 'unknown: 0x') { + $ThisFileInfo['warning'][] = 'Audio codec = '.$thisfile_audio['codec']; + } + $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; + + $ThisFileInfo['playtime_seconds'] = (float) ((($thisfile_avdataend - $thisfile_avdataoffset) * 8) / $thisfile_audio['bitrate']); + + if (isset($thisfile_riff_WAVE['data'][0]['offset']) && isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { + $thisfile_audio['lossless'] = false; + switch ($thisfile_riff_raw['fmt ']['wFormatTag']) { + + case 0x0001: // PCM + $thisfile_audio['lossless'] = true; + break; + + case 0x2000: // AC-3 + $thisfile_audio_dataformat = 'ac3'; + break; + + default: + // do nothing + break; + + } + } + $thisfile_audio['streams'][$streamindex]['wformattag'] = $thisfile_audio['wformattag']; + $thisfile_audio['streams'][$streamindex]['bitrate_mode'] = $thisfile_audio['bitrate_mode']; + $thisfile_audio['streams'][$streamindex]['lossless'] = $thisfile_audio['lossless']; + $thisfile_audio['streams'][$streamindex]['dataformat'] = $thisfile_audio_dataformat; + } + + if (isset($thisfile_riff_WAVE['rgad'][0]['data'])) { + + // shortcuts + $rgadData = &$thisfile_riff_WAVE['rgad'][0]['data']; + $thisfile_riff_raw['rgad'] = array('track'=>array(), 'album'=>array()); + $thisfile_riff_raw_rgad = &$thisfile_riff_raw['rgad']; + $thisfile_riff_raw_rgad_track = &$thisfile_riff_raw_rgad['track']; + $thisfile_riff_raw_rgad_album = &$thisfile_riff_raw_rgad['album']; + + $thisfile_riff_raw_rgad['fPeakAmplitude'] = getid3_lib::LittleEndian2Float(substr($rgadData, 0, 4)); + $thisfile_riff_raw_rgad['nRadioRgAdjust'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($rgadData, 4, 2)); + $thisfile_riff_raw_rgad['nAudiophileRgAdjust'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($rgadData, 6, 2)); + + $nRadioRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nRadioRgAdjust']), 16, '0', STR_PAD_LEFT); + $nAudiophileRgAdjustBitstring = str_pad(getid3_lib::Dec2Bin($thisfile_riff_raw_rgad['nAudiophileRgAdjust']), 16, '0', STR_PAD_LEFT); + $thisfile_riff_raw_rgad_track['name'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 0, 3)); + $thisfile_riff_raw_rgad_track['originator'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 3, 3)); + $thisfile_riff_raw_rgad_track['signbit'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 6, 1)); + $thisfile_riff_raw_rgad_track['adjustment'] = getid3_lib::Bin2Dec(substr($nRadioRgAdjustBitstring, 7, 9)); + $thisfile_riff_raw_rgad_album['name'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 0, 3)); + $thisfile_riff_raw_rgad_album['originator'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 3, 3)); + $thisfile_riff_raw_rgad_album['signbit'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 6, 1)); + $thisfile_riff_raw_rgad_album['adjustment'] = getid3_lib::Bin2Dec(substr($nAudiophileRgAdjustBitstring, 7, 9)); + + $thisfile_riff['rgad']['peakamplitude'] = $thisfile_riff_raw_rgad['fPeakAmplitude']; + if (($thisfile_riff_raw_rgad_track['name'] != 0) && ($thisfile_riff_raw_rgad_track['originator'] != 0)) { + $thisfile_riff['rgad']['track']['name'] = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_track['name']); + $thisfile_riff['rgad']['track']['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_track['originator']); + $thisfile_riff['rgad']['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_track['adjustment'], $thisfile_riff_raw_rgad_track['signbit']); + } + if (($thisfile_riff_raw_rgad_album['name'] != 0) && ($thisfile_riff_raw_rgad_album['originator'] != 0)) { + $thisfile_riff['rgad']['album']['name'] = getid3_lib::RGADnameLookup($thisfile_riff_raw_rgad_album['name']); + $thisfile_riff['rgad']['album']['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_riff_raw_rgad_album['originator']); + $thisfile_riff['rgad']['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($thisfile_riff_raw_rgad_album['adjustment'], $thisfile_riff_raw_rgad_album['signbit']); + } + } + + if (isset($thisfile_riff_WAVE['fact'][0]['data'])) { + $thisfile_riff_raw['fact']['NumberOfSamples'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_WAVE['fact'][0]['data'], 0, 4)); + + // This should be a good way of calculating exact playtime, + // but some sample files have had incorrect number of samples, + // so cannot use this method + + // if (!empty($thisfile_riff_raw['fmt ']['nSamplesPerSec'])) { + // $ThisFileInfo['playtime_seconds'] = (float) $thisfile_riff_raw['fact']['NumberOfSamples'] / $thisfile_riff_raw['fmt ']['nSamplesPerSec']; + // } + } + if (!empty($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'])) { + $thisfile_audio['bitrate'] = getid3_lib::CastAsInt($thisfile_riff_raw['fmt ']['nAvgBytesPerSec'] * 8); + } + + if (isset($thisfile_riff_WAVE['bext'][0]['data'])) { + // shortcut + $thisfile_riff_WAVE_bext_0 = &$thisfile_riff_WAVE['bext'][0]; + + $thisfile_riff_WAVE_bext_0['title'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 0, 256)); + $thisfile_riff_WAVE_bext_0['author'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 256, 32)); + $thisfile_riff_WAVE_bext_0['reference'] = trim(substr($thisfile_riff_WAVE_bext_0['data'], 288, 32)); + $thisfile_riff_WAVE_bext_0['origin_date'] = substr($thisfile_riff_WAVE_bext_0['data'], 320, 10); + $thisfile_riff_WAVE_bext_0['origin_time'] = substr($thisfile_riff_WAVE_bext_0['data'], 330, 8); + $thisfile_riff_WAVE_bext_0['time_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 338, 8)); + $thisfile_riff_WAVE_bext_0['bwf_version'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 346, 1)); + $thisfile_riff_WAVE_bext_0['reserved'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_bext_0['data'], 347, 254)); + $thisfile_riff_WAVE_bext_0['coding_history'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_bext_0['data'], 601))); + + $thisfile_riff_WAVE_bext_0['origin_date_unix'] = mktime( + substr($thisfile_riff_WAVE_bext_0['origin_time'], 0, 2), + substr($thisfile_riff_WAVE_bext_0['origin_time'], 3, 2), + substr($thisfile_riff_WAVE_bext_0['origin_time'], 6, 2), + substr($thisfile_riff_WAVE_bext_0['origin_date'], 5, 2), + substr($thisfile_riff_WAVE_bext_0['origin_date'], 8, 2), + substr($thisfile_riff_WAVE_bext_0['origin_date'], 0, 4)); + + $thisfile_riff['comments']['author'][] = $thisfile_riff_WAVE_bext_0['author']; + $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_bext_0['title']; + } + + if (isset($thisfile_riff_WAVE['MEXT'][0]['data'])) { + // shortcut + $thisfile_riff_WAVE_MEXT_0 = &$thisfile_riff_WAVE['MEXT'][0]; + + $thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 0, 2)); + $thisfile_riff_WAVE_MEXT_0['flags']['homogenous'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0001); + if ($thisfile_riff_WAVE_MEXT_0['flags']['homogenous']) { + $thisfile_riff_WAVE_MEXT_0['flags']['padding'] = ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0002) ? false : true; + $thisfile_riff_WAVE_MEXT_0['flags']['22_or_44'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0004); + $thisfile_riff_WAVE_MEXT_0['flags']['free_format'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['sound_information'] & 0x0008); + + $thisfile_riff_WAVE_MEXT_0['nominal_frame_size'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 2, 2)); + } + $thisfile_riff_WAVE_MEXT_0['anciliary_data_length'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 6, 2)); + $thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_MEXT_0['data'], 8, 2)); + $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_left'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0001); + $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_free'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0002); + $thisfile_riff_WAVE_MEXT_0['flags']['anciliary_data_right'] = (bool) ($thisfile_riff_WAVE_MEXT_0['raw']['anciliary_data_def'] & 0x0004); + } + + if (isset($thisfile_riff_WAVE['cart'][0]['data'])) { + // shortcut + $thisfile_riff_WAVE_cart_0 = &$thisfile_riff_WAVE['cart'][0]; + + $thisfile_riff_WAVE_cart_0['version'] = substr($thisfile_riff_WAVE_cart_0['data'], 0, 4); + $thisfile_riff_WAVE_cart_0['title'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 4, 64)); + $thisfile_riff_WAVE_cart_0['artist'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 68, 64)); + $thisfile_riff_WAVE_cart_0['cut_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 132, 64)); + $thisfile_riff_WAVE_cart_0['client_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 196, 64)); + $thisfile_riff_WAVE_cart_0['category'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 260, 64)); + $thisfile_riff_WAVE_cart_0['classification'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 324, 64)); + $thisfile_riff_WAVE_cart_0['out_cue'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 388, 64)); + $thisfile_riff_WAVE_cart_0['start_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 452, 10)); + $thisfile_riff_WAVE_cart_0['start_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 462, 8)); + $thisfile_riff_WAVE_cart_0['end_date'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 470, 10)); + $thisfile_riff_WAVE_cart_0['end_time'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 480, 8)); + $thisfile_riff_WAVE_cart_0['producer_app_id'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 488, 64)); + $thisfile_riff_WAVE_cart_0['producer_app_version'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 552, 64)); + $thisfile_riff_WAVE_cart_0['user_defined_text'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 616, 64)); + $thisfile_riff_WAVE_cart_0['zero_db_reference'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 680, 4), true); + for ($i = 0; $i < 8; $i++) { + $thisfile_riff_WAVE_cart_0['post_time'][$i]['usage_fourcc'] = substr($thisfile_riff_WAVE_cart_0['data'], 684 + ($i * 8), 4); + $thisfile_riff_WAVE_cart_0['post_time'][$i]['timer_value'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE_cart_0['data'], 684 + ($i * 8) + 4, 4)); + } + $thisfile_riff_WAVE_cart_0['url'] = trim(substr($thisfile_riff_WAVE_cart_0['data'], 748, 1024)); + $thisfile_riff_WAVE_cart_0['tag_text'] = explode("\r\n", trim(substr($thisfile_riff_WAVE_cart_0['data'], 1772))); + + $thisfile_riff['comments']['artist'][] = $thisfile_riff_WAVE_cart_0['artist']; + $thisfile_riff['comments']['title'][] = $thisfile_riff_WAVE_cart_0['title']; + } + + if (!isset($thisfile_audio['bitrate']) && isset($thisfile_riff_audio[$streamindex]['bitrate'])) { + $thisfile_audio['bitrate'] = $thisfile_riff_audio[$streamindex]['bitrate']; + $ThisFileInfo['playtime_seconds'] = (float) ((($thisfile_avdataend - $thisfile_avdataoffset) * 8) / $thisfile_audio['bitrate']); + } + + if (!empty($ThisFileInfo['wavpack'])) { + $thisfile_audio_dataformat = 'wavpack'; + $thisfile_audio['bitrate_mode'] = 'vbr'; + $thisfile_audio['encoder'] = 'WavPack v'.$ThisFileInfo['wavpack']['version']; + + // Reset to the way it was - RIFF parsing will have messed this up + $thisfile_avdataend = $Original['avdataend']; + $thisfile_audio['bitrate'] = (($thisfile_avdataend - $thisfile_avdataoffset) * 8) / $ThisFileInfo['playtime_seconds']; + + fseek($fd, $thisfile_avdataoffset - 44, SEEK_SET); + $RIFFdata = fread($fd, 44); + $OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8; + $OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44; + + if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) { + $thisfile_avdataend -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize); + fseek($fd, $thisfile_avdataend, SEEK_SET); + $RIFFdata .= fread($fd, $OrignalRIFFheaderSize - $OrignalRIFFdataSize); + } + + // move the data chunk after all other chunks (if any) + // so that the RIFF parser doesn't see EOF when trying + // to skip over the data chunk + $RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8); + getid3_riff::ParseRIFFdata($RIFFdata, $ThisFileInfo); + } + + if (isset($thisfile_riff_raw['fmt ']['wFormatTag'])) { + switch ($thisfile_riff_raw['fmt ']['wFormatTag']) { + case 0x08AE: // ClearJump LiteWave + $thisfile_audio['bitrate_mode'] = 'vbr'; + $thisfile_audio_dataformat = 'litewave'; + + //typedef struct tagSLwFormat { + // WORD m_wCompFormat; // low byte defines compression method, high byte is compression flags + // DWORD m_dwScale; // scale factor for lossy compression + // DWORD m_dwBlockSize; // number of samples in encoded blocks + // WORD m_wQuality; // alias for the scale factor + // WORD m_wMarkDistance; // distance between marks in bytes + // WORD m_wReserved; + // + // //following paramters are ignored if CF_FILESRC is not set + // DWORD m_dwOrgSize; // original file size in bytes + // WORD m_bFactExists; // indicates if 'fact' chunk exists in the original file + // DWORD m_dwRiffChunkSize; // riff chunk size in the original file + // + // PCMWAVEFORMAT m_OrgWf; // original wave format + // }SLwFormat, *PSLwFormat; + + // shortcut + $thisfile_riff['litewave']['raw'] = array(); + $thisfile_riff_litewave = &$thisfile_riff['litewave']; + $thisfile_riff_litewave_raw = &$thisfile_riff_litewave['raw']; + + $thisfile_riff_litewave_raw['compression_method'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 18, 1)); + $thisfile_riff_litewave_raw['compression_flags'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 19, 1)); + $thisfile_riff_litewave_raw['m_dwScale'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 20, 4)); + $thisfile_riff_litewave_raw['m_dwBlockSize'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 24, 4)); + $thisfile_riff_litewave_raw['m_wQuality'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 28, 2)); + $thisfile_riff_litewave_raw['m_wMarkDistance'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 30, 2)); + $thisfile_riff_litewave_raw['m_wReserved'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 32, 2)); + $thisfile_riff_litewave_raw['m_dwOrgSize'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 34, 4)); + $thisfile_riff_litewave_raw['m_bFactExists'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 38, 2)); + $thisfile_riff_litewave_raw['m_dwRiffChunkSize'] = getid3_lib::LittleEndian2Int(substr($thisfile_riff_WAVE['fmt '][0]['data'], 40, 4)); + + //$thisfile_riff_litewave['quality_factor'] = intval(round((2000 - $thisfile_riff_litewave_raw['m_dwScale']) / 20)); + $thisfile_riff_litewave['quality_factor'] = $thisfile_riff_litewave_raw['m_wQuality']; + + $thisfile_riff_litewave['flags']['raw_source'] = ($thisfile_riff_litewave_raw['compression_flags'] & 0x01) ? false : true; + $thisfile_riff_litewave['flags']['vbr_blocksize'] = ($thisfile_riff_litewave_raw['compression_flags'] & 0x02) ? false : true; + $thisfile_riff_litewave['flags']['seekpoints'] = (bool) ($thisfile_riff_litewave_raw['compression_flags'] & 0x04); + + $thisfile_audio['lossless'] = (($thisfile_riff_litewave_raw['m_wQuality'] == 100) ? true : false); + $thisfile_audio['encoder_options'] = '-q'.$thisfile_riff_litewave['quality_factor']; + break; + + default: + break; + } + } + if ($thisfile_avdataend > $ThisFileInfo['filesize']) { + switch (@$thisfile_audio_dataformat) { + case 'wavpack': // WavPack + case 'lpac': // LPAC + case 'ofr': // OptimFROG + case 'ofs': // OptimFROG DualStream + // lossless compressed audio formats that keep original RIFF headers - skip warning + break; + + case 'litewave': + if (($thisfile_avdataend - $ThisFileInfo['filesize']) == 1) { + // LiteWave appears to incorrectly *not* pad actual output file + // to nearest WORD boundary so may appear to be short by one + // byte, in which case - skip warning + } else { + // Short by more than one byte, throw warning + $ThisFileInfo['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)).' bytes)'; + $thisfile_avdataend = $ThisFileInfo['filesize']; + } + break; + + default: + if ((($thisfile_avdataend - $ThisFileInfo['filesize']) == 1) && (($thisfile_riff[$RIFFsubtype]['data'][0]['size'] % 2) == 0) && ((($ThisFileInfo['filesize'] - $thisfile_avdataoffset) % 2) == 1)) { + // output file appears to be incorrectly *not* padded to nearest WORD boundary + // Output less severe warning + $ThisFileInfo['warning'][] = 'File should probably be padded to nearest WORD boundary, but it is not (expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' therefore short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)).' bytes)'; + $thisfile_avdataend = $ThisFileInfo['filesize']; + break; + } + // Short by more than one byte, throw warning + $ThisFileInfo['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['data'][0]['size'].' bytes of data, only found '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' (short by '.($thisfile_riff[$RIFFsubtype]['data'][0]['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)).' bytes)'; + $thisfile_avdataend = $ThisFileInfo['filesize']; + break; + } + } + if (!empty($ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes'])) { + if ((($thisfile_avdataend - $thisfile_avdataoffset) - $ThisFileInfo['mpeg']['audio']['LAME']['audio_bytes']) == 1) { + $thisfile_avdataend--; + $ThisFileInfo['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; + } + } + if (@$thisfile_audio_dataformat == 'ac3') { + unset($thisfile_audio['bits_per_sample']); + if (!empty($ThisFileInfo['ac3']['bitrate']) && ($ThisFileInfo['ac3']['bitrate'] != $thisfile_audio['bitrate'])) { + $thisfile_audio['bitrate'] = $ThisFileInfo['ac3']['bitrate']; + } + } + break; + + case 'AVI ': + $thisfile_video['bitrate_mode'] = 'vbr'; // maybe not, but probably + $thisfile_video['dataformat'] = 'avi'; + $ThisFileInfo['mime_type'] = 'video/avi'; + + if (isset($thisfile_riff[$RIFFsubtype]['movi']['offset'])) { + $thisfile_avdataoffset = $thisfile_riff[$RIFFsubtype]['movi']['offset'] + 8; + $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff[$RIFFsubtype]['movi']['size']; + if ($thisfile_avdataend > $ThisFileInfo['filesize']) { + $ThisFileInfo['warning'][] = 'Probably truncated file - expecting '.$thisfile_riff[$RIFFsubtype]['movi']['size'].' bytes of data, only found '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' (short by '.($thisfile_riff[$RIFFsubtype]['movi']['size'] - ($ThisFileInfo['filesize'] - $thisfile_avdataoffset)).' bytes)'; + $thisfile_avdataend = $ThisFileInfo['filesize']; + } + } + + if (isset($thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data'])) { + $avihData = $thisfile_riff['AVI ']['hdrl']['avih'][$streamindex]['data']; + + // shortcut + $thisfile_riff_raw['avih'] = array(); + $thisfile_riff_raw_avih = &$thisfile_riff_raw['avih']; + + $thisfile_riff_raw_avih['dwMicroSecPerFrame'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 0, 4)); // frame display rate (or 0L) + if ($thisfile_riff_raw_avih['dwMicroSecPerFrame'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt RIFF file: avih.dwMicroSecPerFrame == zero'; + return false; + } + $thisfile_riff_raw_avih['dwMaxBytesPerSec'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 4, 4)); // max. transfer rate + $thisfile_riff_raw_avih['dwPaddingGranularity'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 8, 4)); // pad to multiples of this size; normally 2K. + $thisfile_riff_raw_avih['dwFlags'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 12, 4)); // the ever-present flags + $thisfile_riff_raw_avih['dwTotalFrames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 16, 4)); // # frames in file + $thisfile_riff_raw_avih['dwInitialFrames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 20, 4)); + $thisfile_riff_raw_avih['dwStreams'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 24, 4)); + $thisfile_riff_raw_avih['dwSuggestedBufferSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 28, 4)); + $thisfile_riff_raw_avih['dwWidth'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 32, 4)); + $thisfile_riff_raw_avih['dwHeight'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 36, 4)); + $thisfile_riff_raw_avih['dwScale'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 40, 4)); + $thisfile_riff_raw_avih['dwRate'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 44, 4)); + $thisfile_riff_raw_avih['dwStart'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 48, 4)); + $thisfile_riff_raw_avih['dwLength'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($avihData, 52, 4)); + + $thisfile_riff_raw_avih['flags']['hasindex'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00000010); + $thisfile_riff_raw_avih['flags']['mustuseindex'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00000020); + $thisfile_riff_raw_avih['flags']['interleaved'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00000100); + $thisfile_riff_raw_avih['flags']['trustcktype'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00000800); + $thisfile_riff_raw_avih['flags']['capturedfile'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00010000); + $thisfile_riff_raw_avih['flags']['copyrighted'] = (bool) ($thisfile_riff_raw_avih['dwFlags'] & 0x00020010); + + // shortcut + $thisfile_riff_video[$streamindex] = array(); + $thisfile_riff_video_current = &$thisfile_riff_video[$streamindex]; + + if ($thisfile_riff_raw_avih['dwWidth'] > 0) { + $thisfile_riff_video_current['frame_width'] = $thisfile_riff_raw_avih['dwWidth']; + $thisfile_video['resolution_x'] = $thisfile_riff_video_current['frame_width']; + } + if ($thisfile_riff_raw_avih['dwHeight'] > 0) { + $thisfile_riff_video_current['frame_height'] = $thisfile_riff_raw_avih['dwHeight']; + $thisfile_video['resolution_y'] = $thisfile_riff_video_current['frame_height']; + } + if ($thisfile_riff_raw_avih['dwTotalFrames'] > 0) { + $thisfile_riff_video_current['total_frames'] = $thisfile_riff_raw_avih['dwTotalFrames']; + $thisfile_video['total_frames'] = $thisfile_riff_video_current['total_frames']; + } + + $thisfile_riff_video_current['frame_rate'] = round(1000000 / $thisfile_riff_raw_avih['dwMicroSecPerFrame'], 3); + $thisfile_video['frame_rate'] = $thisfile_riff_video_current['frame_rate']; + } + if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][0]['data'])) { + if (is_array($thisfile_riff['AVI ']['hdrl']['strl']['strh'])) { + for ($i = 0; $i < count($thisfile_riff['AVI ']['hdrl']['strl']['strh']); $i++) { + if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data'])) { + $strhData = $thisfile_riff['AVI ']['hdrl']['strl']['strh'][$i]['data']; + $strhfccType = substr($strhData, 0, 4); + + if (isset($thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data'])) { + $strfData = $thisfile_riff['AVI ']['hdrl']['strl']['strf'][$i]['data']; + + // shortcut + $thisfile_riff_raw_strf_strhfccType_streamindex = &$thisfile_riff_raw['strf'][$strhfccType][$streamindex]; + + switch ($strhfccType) { + case 'auds': + $thisfile_audio['bitrate_mode'] = 'cbr'; + $thisfile_audio_dataformat = 'wav'; + if (isset($thisfile_riff_audio) && is_array($thisfile_riff_audio)) { + $streamindex = count($thisfile_riff_audio); + } + + $thisfile_riff_audio[$streamindex] = getid3_riff::RIFFparseWAVEFORMATex($strfData); + $thisfile_audio['wformattag'] = $thisfile_riff_audio[$streamindex]['raw']['wFormatTag']; + + // shortcut + $thisfile_audio['streams'][$streamindex] = $thisfile_riff_audio[$streamindex]; + $thisfile_audio_streams_currentstream = &$thisfile_audio['streams'][$streamindex]; + + if ($thisfile_audio_streams_currentstream['bits_per_sample'] == 0) { + unset($thisfile_audio_streams_currentstream['bits_per_sample']); + } + $thisfile_audio_streams_currentstream['wformattag'] = $thisfile_audio_streams_currentstream['raw']['wFormatTag']; + unset($thisfile_audio_streams_currentstream['raw']); + + // shortcut + $thisfile_riff_raw['strf'][$strhfccType][$streamindex] = $thisfile_riff_audio[$streamindex]['raw']; + + unset($thisfile_riff_audio[$streamindex]['raw']); + $thisfile_audio = getid3_lib::array_merge_noclobber($thisfile_audio, $thisfile_riff_audio[$streamindex]); + + $thisfile_audio['lossless'] = false; + switch ($thisfile_riff_raw_strf_strhfccType_streamindex['wFormatTag']) { + case 0x0001: // PCM + $thisfile_audio_dataformat = 'wav'; + $thisfile_audio['lossless'] = true; + break; + + case 0x0050: // MPEG Layer 2 or Layer 1 + $thisfile_audio_dataformat = 'mp2'; // Assume Layer-2 + break; + + case 0x0055: // MPEG Layer 3 + $thisfile_audio_dataformat = 'mp3'; + break; + + case 0x00FF: // AAC + $thisfile_audio_dataformat = 'aac'; + break; + + case 0x0161: // Windows Media v7 / v8 / v9 + case 0x0162: // Windows Media Professional v9 + case 0x0163: // Windows Media Lossess v9 + $thisfile_audio_dataformat = 'wma'; + break; + + case 0x2000: // AC-3 + $thisfile_audio_dataformat = 'ac3'; + break; + + case 0x2001: // DTS + $thisfile_audio_dataformat = 'dts'; + break; + + default: + $thisfile_audio_dataformat = 'wav'; + break; + } + $thisfile_audio_streams_currentstream['dataformat'] = $thisfile_audio_dataformat; + $thisfile_audio_streams_currentstream['lossless'] = $thisfile_audio['lossless']; + $thisfile_audio_streams_currentstream['bitrate_mode'] = $thisfile_audio['bitrate_mode']; + break; + + + case 'iavs': + case 'vids': + // shortcut + $thisfile_riff_raw['strh'][$i] = array(); + $thisfile_riff_raw_strh_current = &$thisfile_riff_raw['strh'][$i]; + + $thisfile_riff_raw_strh_current['fccType'] = substr($strhData, 0, 4); // same as $strhfccType; + $thisfile_riff_raw_strh_current['fccHandler'] = substr($strhData, 4, 4); + $thisfile_riff_raw_strh_current['dwFlags'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 8, 4)); // Contains AVITF_* flags + $thisfile_riff_raw_strh_current['wPriority'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 12, 2)); + $thisfile_riff_raw_strh_current['wLanguage'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 14, 2)); + $thisfile_riff_raw_strh_current['dwInitialFrames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 16, 4)); + $thisfile_riff_raw_strh_current['dwScale'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 20, 4)); + $thisfile_riff_raw_strh_current['dwRate'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 24, 4)); + $thisfile_riff_raw_strh_current['dwStart'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 28, 4)); + $thisfile_riff_raw_strh_current['dwLength'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 32, 4)); + $thisfile_riff_raw_strh_current['dwSuggestedBufferSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 36, 4)); + $thisfile_riff_raw_strh_current['dwQuality'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 40, 4)); + $thisfile_riff_raw_strh_current['dwSampleSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 44, 4)); + $thisfile_riff_raw_strh_current['rcFrame'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strhData, 48, 4)); + + $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strh_current['fccHandler']); + $thisfile_video['fourcc'] = $thisfile_riff_raw_strh_current['fccHandler']; + if (!$thisfile_riff_video_current['codec'] && isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) && getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { + $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']); + $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; + } + $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; + $thisfile_video['pixel_aspect_ratio'] = (float) 1; + switch ($thisfile_riff_raw_strh_current['fccHandler']) { + case 'HFYU': // Huffman Lossless Codec + case 'IRAW': // Intel YUV Uncompressed + case 'YUY2': // Uncompressed YUV 4:2:2 + $thisfile_video['lossless'] = true; + break; + + default: + $thisfile_video['lossless'] = false; + break; + } + + switch ($strhfccType) { + case 'vids': + $thisfile_riff_raw_strf_strhfccType_streamindex['biSize'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 0, 4)); // number of bytes required by the BITMAPINFOHEADER structure + $thisfile_riff_raw_strf_strhfccType_streamindex['biWidth'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 4, 4)); // width of the bitmap in pixels + $thisfile_riff_raw_strf_strhfccType_streamindex['biHeight'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 8, 4)); // height of the bitmap in pixels. If biHeight is positive, the bitmap is a 'bottom-up' DIB and its origin is the lower left corner. If biHeight is negative, the bitmap is a 'top-down' DIB and its origin is the upper left corner + $thisfile_riff_raw_strf_strhfccType_streamindex['biPlanes'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 12, 2)); // number of color planes on the target device. In most cases this value must be set to 1 + $thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 14, 2)); // Specifies the number of bits per pixels + $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'] = substr($strfData, 16, 4); // + $thisfile_riff_raw_strf_strhfccType_streamindex['biSizeImage'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 20, 4)); // size of the bitmap data section of the image (the actual pixel data, excluding BITMAPINFOHEADER and RGBQUAD structures) + $thisfile_riff_raw_strf_strhfccType_streamindex['biXPelsPerMeter'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 24, 4)); // horizontal resolution, in pixels per metre, of the target device + $thisfile_riff_raw_strf_strhfccType_streamindex['biYPelsPerMeter'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 28, 4)); // vertical resolution, in pixels per metre, of the target device + $thisfile_riff_raw_strf_strhfccType_streamindex['biClrUsed'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 32, 4)); // actual number of color indices in the color table used by the bitmap. If this value is zero, the bitmap uses the maximum number of colors corresponding to the value of the biBitCount member for the compression mode specified by biCompression + $thisfile_riff_raw_strf_strhfccType_streamindex['biClrImportant'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($strfData, 36, 4)); // number of color indices that are considered important for displaying the bitmap. If this value is zero, all colors are important + + $thisfile_video['bits_per_sample'] = $thisfile_riff_raw_strf_strhfccType_streamindex['biBitCount']; + + if ($thisfile_riff_video_current['codec'] == 'DV') { + $thisfile_riff_video_current['dv_type'] = 2; + } + break; + + case 'iavs': + $thisfile_riff_video_current['dv_type'] = 1; + break; + } + break; + + default: + $ThisFileInfo['warning'][] = 'Unhandled fccType for stream ('.$i.'): "'.$strhfccType.'"'; + break; + + } + } + } + + if (isset($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) && getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc'])) { + + $thisfile_riff_video_current['codec'] = getid3_riff::RIFFfourccLookup($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']); + $thisfile_video['codec'] = $thisfile_riff_video_current['codec']; + $thisfile_video['fourcc'] = $thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']; + + switch ($thisfile_riff_raw_strf_strhfccType_streamindex['fourcc']) { + case 'HFYU': // Huffman Lossless Codec + case 'IRAW': // Intel YUV Uncompressed + case 'YUY2': // Uncompressed YUV 4:2:2 + $thisfile_video['lossless'] = true; + $thisfile_video['bits_per_sample'] = 24; + break; + + default: + $thisfile_video['lossless'] = false; + $thisfile_video['bits_per_sample'] = 24; + break; + } + + } + } + } + } + break; + + case 'CDDA': + $thisfile_audio['bitrate_mode'] = 'cbr'; + $thisfile_audio_dataformat = 'cda'; + $thisfile_audio['lossless'] = true; + unset($ThisFileInfo['mime_type']); + + $thisfile_avdataoffset = 44; + + if (isset($thisfile_riff['CDDA']['fmt '][0]['data'])) { + // shortcut + $thisfile_riff_CDDA_fmt_0 = &$thisfile_riff['CDDA']['fmt '][0]; + + $thisfile_riff_CDDA_fmt_0['unknown1'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 0, 2)); + $thisfile_riff_CDDA_fmt_0['track_num'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 2, 2)); + $thisfile_riff_CDDA_fmt_0['disc_id'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 4, 4)); + $thisfile_riff_CDDA_fmt_0['start_offset_frame'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 8, 4)); + $thisfile_riff_CDDA_fmt_0['playtime_frames'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 12, 4)); + $thisfile_riff_CDDA_fmt_0['unknown6'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 16, 4)); + $thisfile_riff_CDDA_fmt_0['unknown7'] = getid3_riff::EitherEndian2Int($ThisFileInfo, substr($thisfile_riff_CDDA_fmt_0['data'], 20, 4)); + + $thisfile_riff_CDDA_fmt_0['start_offset_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['start_offset_frame'] / 75; + $thisfile_riff_CDDA_fmt_0['playtime_seconds'] = (float) $thisfile_riff_CDDA_fmt_0['playtime_frames'] / 75; + $ThisFileInfo['comments']['track'] = $thisfile_riff_CDDA_fmt_0['track_num']; + $ThisFileInfo['playtime_seconds'] = $thisfile_riff_CDDA_fmt_0['playtime_seconds']; + + // hardcoded data for CD-audio + $thisfile_audio['sample_rate'] = 44100; + $thisfile_audio['channels'] = 2; + $thisfile_audio['bits_per_sample'] = 16; + $thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $thisfile_audio['channels'] * $thisfile_audio['bits_per_sample']; + $thisfile_audio['bitrate_mode'] = 'cbr'; + } + break; + + + case 'AIFF': + case 'AIFC': + $thisfile_audio['bitrate_mode'] = 'cbr'; + $thisfile_audio_dataformat = 'aiff'; + $thisfile_audio['lossless'] = true; + $ThisFileInfo['mime_type'] = 'audio/x-aiff'; + + if (isset($thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'])) { + $thisfile_avdataoffset = $thisfile_riff[$RIFFsubtype]['SSND'][0]['offset'] + 8; + $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff[$RIFFsubtype]['SSND'][0]['size']; + if ($thisfile_avdataend > $ThisFileInfo['filesize']) { + if (($thisfile_avdataend == ($ThisFileInfo['filesize'] + 1)) && (($ThisFileInfo['filesize'] % 2) == 1)) { + // structures rounded to 2-byte boundary, but dumb encoders + // forget to pad end of file to make this actually work + } else { + $ThisFileInfo['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['SSND'][0]['size'].' bytes of audio data, only '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' bytes found'; + } + $thisfile_avdataend = $ThisFileInfo['filesize']; + } + } + + if (isset($thisfile_riff[$RIFFsubtype]['COMM'][0]['data'])) { + + // shortcut + $thisfile_riff_RIFFsubtype_COMM_0_data = &$thisfile_riff[$RIFFsubtype]['COMM'][0]['data']; + + $thisfile_riff_audio['channels'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 0, 2), true); + $thisfile_riff_audio['total_samples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 2, 4), false); + $thisfile_riff_audio['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 6, 2), true); + $thisfile_riff_audio['sample_rate'] = (int) getid3_lib::BigEndian2Float(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 8, 10)); + + if ($thisfile_riff[$RIFFsubtype]['COMM'][0]['size'] > 18) { + $thisfile_riff_audio['codec_fourcc'] = substr($thisfile_riff_RIFFsubtype_COMM_0_data, 18, 4); + $CodecNameSize = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_COMM_0_data, 22, 1), false); + $thisfile_riff_audio['codec_name'] = substr($thisfile_riff_RIFFsubtype_COMM_0_data, 23, $CodecNameSize); + switch ($thisfile_riff_audio['codec_name']) { + case 'NONE': + $thisfile_audio['codec'] = 'Pulse Code Modulation (PCM)'; + $thisfile_audio['lossless'] = true; + break; + + case '': + switch ($thisfile_riff_audio['codec_fourcc']) { + // http://developer.apple.com/qa/snd/snd07.html + case 'sowt': + $thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Little-Endian PCM'; + $thisfile_audio['lossless'] = true; + break; + + case 'twos': + $thisfile_riff_audio['codec_name'] = 'Two\'s Compliment Big-Endian PCM'; + $thisfile_audio['lossless'] = true; + break; + + default: + break; + } + break; + + default: + $thisfile_audio['codec'] = $thisfile_riff_audio['codec_name']; + $thisfile_audio['lossless'] = false; + break; + } + } + + $thisfile_audio['channels'] = $thisfile_riff_audio['channels']; + if ($thisfile_riff_audio['bits_per_sample'] > 0) { + $thisfile_audio['bits_per_sample'] = $thisfile_riff_audio['bits_per_sample']; + } + $thisfile_audio['sample_rate'] = $thisfile_riff_audio['sample_rate']; + if ($thisfile_audio['sample_rate'] == 0) { + $ThisFileInfo['error'][] = 'Corrupted AIFF file: sample_rate == zero'; + return false; + } + $ThisFileInfo['playtime_seconds'] = $thisfile_riff_audio['total_samples'] / $thisfile_audio['sample_rate']; + } + + if (isset($thisfile_riff[$RIFFsubtype]['COMT'])) { + $offset = 0; + $CommentCount = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); + $offset += 2; + for ($i = 0; $i < $CommentCount; $i++) { + $ThisFileInfo['comments_raw'][$i]['timestamp'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 4), false); + $offset += 4; + $ThisFileInfo['comments_raw'][$i]['marker_id'] = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), true); + $offset += 2; + $CommentLength = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, 2), false); + $offset += 2; + $ThisFileInfo['comments_raw'][$i]['comment'] = substr($thisfile_riff[$RIFFsubtype]['COMT'][0]['data'], $offset, $CommentLength); + $offset += $CommentLength; + + $ThisFileInfo['comments_raw'][$i]['timestamp_unix'] = getid3_lib::DateMac2Unix($ThisFileInfo['comments_raw'][$i]['timestamp']); + $thisfile_riff['comments']['comment'][] = $ThisFileInfo['comments_raw'][$i]['comment']; + } + } + + $CommentsChunkNames = array('NAME'=>'title', 'author'=>'artist', '(c) '=>'copyright', 'ANNO'=>'comment'); + foreach ($CommentsChunkNames as $key => $value) { + if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) { + $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data']; + } + } + break; + + case '8SVX': + $thisfile_audio['bitrate_mode'] = 'cbr'; + $thisfile_audio_dataformat = '8svx'; + $thisfile_audio['bits_per_sample'] = 8; + $thisfile_audio['channels'] = 1; // overridden below, if need be + $ThisFileInfo['mime_type'] = 'audio/x-aiff'; + + if (isset($thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'])) { + $thisfile_avdataoffset = $thisfile_riff[$RIFFsubtype]['BODY'][0]['offset'] + 8; + $thisfile_avdataend = $thisfile_avdataoffset + $thisfile_riff[$RIFFsubtype]['BODY'][0]['size']; + if ($thisfile_avdataend > $ThisFileInfo['filesize']) { + $ThisFileInfo['warning'][] = 'Probable truncated AIFF file: expecting '.$thisfile_riff[$RIFFsubtype]['BODY'][0]['size'].' bytes of audio data, only '.($ThisFileInfo['filesize'] - $thisfile_avdataoffset).' bytes found'; + } + } + + if (isset($thisfile_riff[$RIFFsubtype]['VHDR'][0]['offset'])) { + // shortcut + $thisfile_riff_RIFFsubtype_VHDR_0 = &$thisfile_riff[$RIFFsubtype]['VHDR'][0]; + + $thisfile_riff_RIFFsubtype_VHDR_0['oneShotHiSamples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 0, 4)); + $thisfile_riff_RIFFsubtype_VHDR_0['repeatHiSamples'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 4, 4)); + $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerHiCycle'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 8, 4)); + $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 12, 2)); + $thisfile_riff_RIFFsubtype_VHDR_0['ctOctave'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 14, 1)); + $thisfile_riff_RIFFsubtype_VHDR_0['sCompression'] = getid3_lib::BigEndian2Int(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 15, 1)); + $thisfile_riff_RIFFsubtype_VHDR_0['Volume'] = getid3_lib::FixedPoint16_16(substr($thisfile_riff_RIFFsubtype_VHDR_0['data'], 16, 4)); + + $thisfile_audio['sample_rate'] = $thisfile_riff_RIFFsubtype_VHDR_0['samplesPerSec']; + + switch ($thisfile_riff_RIFFsubtype_VHDR_0['sCompression']) { + case 0: + $thisfile_audio['codec'] = 'Pulse Code Modulation (PCM)'; + $thisfile_audio['lossless'] = true; + $ActualBitsPerSample = 8; + break; + + case 1: + $thisfile_audio['codec'] = 'Fibonacci-delta encoding'; + $thisfile_audio['lossless'] = false; + $ActualBitsPerSample = 4; + break; + + default: + $ThisFileInfo['warning'][] = 'Unexpected sCompression value in 8SVX.VHDR chunk - expecting 0 or 1, found "'.sCompression.'"'; + break; + } + } + + if (isset($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'])) { + $ChannelsIndex = getid3_lib::BigEndian2Int(substr($thisfile_riff[$RIFFsubtype]['CHAN'][0]['data'], 0, 4)); + switch ($ChannelsIndex) { + case 6: // Stereo + $thisfile_audio['channels'] = 2; + break; + + case 2: // Left channel only + case 4: // Right channel only + $thisfile_audio['channels'] = 1; + break; + + default: + $ThisFileInfo['warning'][] = 'Unexpected value in 8SVX.CHAN chunk - expecting 2 or 4 or 6, found "'.$ChannelsIndex.'"'; + break; + } + + } + + $CommentsChunkNames = array('NAME'=>'title', 'author'=>'artist', '(c) '=>'copyright', 'ANNO'=>'comment'); + foreach ($CommentsChunkNames as $key => $value) { + if (isset($thisfile_riff[$RIFFsubtype][$key][0]['data'])) { + $thisfile_riff['comments'][$value][] = $thisfile_riff[$RIFFsubtype][$key][0]['data']; + } + } + + $thisfile_audio['bitrate'] = $thisfile_audio['sample_rate'] * $ActualBitsPerSample * $thisfile_audio['channels']; + if (!empty($thisfile_audio['bitrate'])) { + $ThisFileInfo['playtime_seconds'] = ($thisfile_avdataend - $thisfile_avdataoffset) / ($thisfile_audio['bitrate'] / 8); + } + break; + + + case 'CDXA': + $ThisFileInfo['mime_type'] = 'video/mpeg'; + if (!empty($thisfile_riff['CDXA']['data'][0]['size'])) { + $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.mpeg.php', __FILE__, false)) { + $dummy = $ThisFileInfo; + $dummy['error'] = array(); + $mpeg_scanner = new getid3_mpeg($fd, $dummy); + if (empty($dummy['error'])) { + $ThisFileInfo['audio'] = $dummy['audio']; + $ThisFileInfo['video'] = $dummy['video']; + $ThisFileInfo['mpeg'] = $dummy['mpeg']; + $ThisFileInfo['warning'] = $dummy['warning']; + } + } + } + break; + + + default: + $ThisFileInfo['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "'.$RIFFsubtype.'" instead'; + unset($ThisFileInfo['fileformat']); + break; + } + + if (isset($thisfile_riff_WAVE['DISP']) && is_array($thisfile_riff_WAVE['DISP'])) { + $thisfile_riff['comments']['title'][] = trim(substr($thisfile_riff_WAVE['DISP'][count($thisfile_riff_WAVE['DISP']) - 1]['data'], 4)); + } + if (isset($thisfile_riff_WAVE['INFO']) && is_array($thisfile_riff_WAVE['INFO'])) { + $this->RIFFcommentsParse($thisfile_riff_WAVE['INFO'], $thisfile_riff['comments']); + } + + if (empty($thisfile_audio['encoder']) && !empty($ThisFileInfo['mpeg']['audio']['LAME']['short_version'])) { + $thisfile_audio['encoder'] = $ThisFileInfo['mpeg']['audio']['LAME']['short_version']; + } + + if (!isset($ThisFileInfo['playtime_seconds'])) { + $ThisFileInfo['playtime_seconds'] = 0; + } + if (isset($thisfile_riff_raw['avih']['dwTotalFrames']) && isset($thisfile_riff_raw['avih']['dwMicroSecPerFrame'])) { + $ThisFileInfo['playtime_seconds'] = $thisfile_riff_raw['avih']['dwTotalFrames'] * ($thisfile_riff_raw['avih']['dwMicroSecPerFrame'] / 1000000); + } + + if ($ThisFileInfo['playtime_seconds'] > 0) { + if (isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { + + if (!isset($ThisFileInfo['bitrate'])) { + $ThisFileInfo['bitrate'] = ((($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds']) * 8); + } + + } elseif (isset($thisfile_riff_audio) && !isset($thisfile_riff_video)) { + + if (!isset($thisfile_audio['bitrate'])) { + $thisfile_audio['bitrate'] = ((($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds']) * 8); + } + + } elseif (!isset($thisfile_riff_audio) && isset($thisfile_riff_video)) { + + if (!isset($thisfile_video['bitrate'])) { + $thisfile_video['bitrate'] = ((($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds']) * 8); + } + + } + } + + + if (isset($thisfile_riff_video) && isset($thisfile_audio['bitrate']) && ($thisfile_audio['bitrate'] > 0) && ($ThisFileInfo['playtime_seconds'] > 0)) { + + $ThisFileInfo['bitrate'] = ((($thisfile_avdataend - $thisfile_avdataoffset) / $ThisFileInfo['playtime_seconds']) * 8); + $thisfile_audio['bitrate'] = 0; + $thisfile_video['bitrate'] = $ThisFileInfo['bitrate']; + foreach ($thisfile_riff_audio as $channelnumber => $audioinfoarray) { + $thisfile_video['bitrate'] -= $audioinfoarray['bitrate']; + $thisfile_audio['bitrate'] += $audioinfoarray['bitrate']; + } + if ($thisfile_video['bitrate'] <= 0) { + unset($thisfile_video['bitrate']); + } + if ($thisfile_audio['bitrate'] <= 0) { + unset($thisfile_audio['bitrate']); + } + } + + if (isset($ThisFileInfo['mpeg']['audio'])) { + $thisfile_audio_dataformat = 'mp'.$ThisFileInfo['mpeg']['audio']['layer']; + $thisfile_audio['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; + $thisfile_audio['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; + $thisfile_audio['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; + $thisfile_audio['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); + if (!empty($ThisFileInfo['mpeg']['audio']['codec'])) { + $thisfile_audio['codec'] = $ThisFileInfo['mpeg']['audio']['codec'].' '.$thisfile_audio['codec']; + } + if (!empty($thisfile_audio['streams'])) { + foreach ($thisfile_audio['streams'] as $streamnumber => $streamdata) { + if ($streamdata['dataformat'] == $thisfile_audio_dataformat) { + $thisfile_audio['streams'][$streamnumber]['sample_rate'] = $thisfile_audio['sample_rate']; + $thisfile_audio['streams'][$streamnumber]['channels'] = $thisfile_audio['channels']; + $thisfile_audio['streams'][$streamnumber]['bitrate'] = $thisfile_audio['bitrate']; + $thisfile_audio['streams'][$streamnumber]['bitrate_mode'] = $thisfile_audio['bitrate_mode']; + $thisfile_audio['streams'][$streamnumber]['codec'] = $thisfile_audio['codec']; + } + } + } + $thisfile_audio['encoder_options'] = getid3_mp3::GuessEncoderOptions($ThisFileInfo); + } + + + if (!empty($thisfile_riff_raw['fmt ']['wBitsPerSample']) && ($thisfile_riff_raw['fmt ']['wBitsPerSample'] > 0)) { + switch ($thisfile_audio_dataformat) { + case 'ac3': + // ignore bits_per_sample + break; + + default: + $thisfile_audio['bits_per_sample'] = $thisfile_riff_raw['fmt ']['wBitsPerSample']; + break; + } + } + + + if (empty($thisfile_riff_raw)) { + unset($thisfile_riff['raw']); + } + if (empty($thisfile_riff_audio)) { + unset($thisfile_riff['audio']); + } + if (empty($thisfile_riff_video)) { + unset($thisfile_riff['video']); + } + + return true; + } + + + function RIFFcommentsParse(&$RIFFinfoArray, &$CommentsTargetArray) {
+ $RIFFinfoKeyLookup = array( + 'IARL'=>'archivallocation', + 'IART'=>'artist', + 'ICDS'=>'costumedesigner', + 'ICMS'=>'commissionedby', + 'ICMT'=>'comment', + 'ICNT'=>'country', + 'ICOP'=>'copyright', + 'ICRD'=>'creationdate', + 'IDIM'=>'dimensions', + 'IDIT'=>'digitizationdate', + 'IDPI'=>'resolution', + 'IDST'=>'distributor', + 'IEDT'=>'editor', + 'IENG'=>'engineers', + 'IFRM'=>'accountofparts', + 'IGNR'=>'genre', + 'IKEY'=>'keywords', + 'ILGT'=>'lightness', + 'ILNG'=>'language', + 'IMED'=>'orignalmedium', + 'IMUS'=>'composer', + 'INAM'=>'title', + 'IPDS'=>'productiondesigner', + 'IPLT'=>'palette', + 'IPRD'=>'product', + 'IPRO'=>'producer', + 'IPRT'=>'part', + 'IRTD'=>'rating', + 'ISBJ'=>'subject', + 'ISFT'=>'software', + 'ISGN'=>'secondarygenre', + 'ISHP'=>'sharpness', + 'ISRC'=>'sourcesupplier', + 'ISRF'=>'digitizationsource', + 'ISTD'=>'productionstudio', + 'ISTR'=>'starring', + 'ITCH'=>'encoded_by', + 'IWEB'=>'url', + 'IWRI'=>'writer' + ); + foreach ($RIFFinfoKeyLookup as $key => $value) { + if (isset($RIFFinfoArray[$key])) { + foreach ($RIFFinfoArray[$key] as $commentid => $commentdata) { + if (trim($commentdata['data']) != '') { + @$CommentsTargetArray[$value][] = trim($commentdata['data']); + } + } + } + }
+ return true; + }
+ + function ParseRIFF(&$fd, $startoffset, $maxoffset, &$ThisFileInfo) { + + $maxoffset = min($maxoffset, $ThisFileInfo['avdataend']); + + $RIFFchunk = false; + + fseek($fd, $startoffset, SEEK_SET); + + while (ftell($fd) < $maxoffset) { + $chunkname = fread($fd, 4); + if (strlen($chunkname) < 4) { + $ThisFileInfo['error'][] = 'Expecting chunk name at offset '.(ftell($fd) - 4).' but found nothing. Aborting RIFF parsing.'; + break; + } + + $chunksize = getid3_riff::EitherEndian2Int($ThisFileInfo, fread($fd, 4)); + if ($chunksize == 0) { + $ThisFileInfo['error'][] = 'Chunk size at offset '.(ftell($fd) - 4).' is zero. Aborting RIFF parsing.'; + break; + } + if (($chunksize % 2) != 0) { + // all structures are packed on word boundaries + $chunksize++; + } + + switch ($chunkname) { + case 'LIST': + $listname = fread($fd, 4); + switch ($listname) { + case 'movi': + case 'rec ': + $RIFFchunk[$listname]['offset'] = ftell($fd) - 4; + $RIFFchunk[$listname]['size'] = $chunksize; + + static $ParsedAudioStream = false; + if ($ParsedAudioStream) { + + // skip over + + } else { + + $WhereWeWere = ftell($fd); + $AudioChunkHeader = fread($fd, 12); + $AudioChunkStreamNum = substr($AudioChunkHeader, 0, 2); + $AudioChunkStreamType = substr($AudioChunkHeader, 2, 2); + $AudioChunkSize = getid3_lib::LittleEndian2Int(substr($AudioChunkHeader, 4, 4)); + + if ($AudioChunkStreamType == 'wb') { + $FirstFourBytes = substr($AudioChunkHeader, 8, 4); + if (preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', $FirstFourBytes)) { + + // MP3 + if (getid3_mp3::MPEGaudioHeaderBytesValid($FirstFourBytes)) { + $dummy = $ThisFileInfo; + $dummy['avdataoffset'] = ftell($fd) - 4; + $dummy['avdataend'] = ftell($fd) + $AudioChunkSize; + getid3_mp3::getOnlyMPEGaudioInfo($fd, $dummy, $dummy['avdataoffset'], false); + if (isset($dummy['mpeg']['audio'])) { + $ThisFileInfo = $dummy; + $ThisFileInfo['audio']['dataformat'] = 'mp'.$ThisFileInfo['mpeg']['audio']['layer']; + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; + $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; + $ThisFileInfo['bitrate'] = $ThisFileInfo['audio']['bitrate']; + $ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); + } + } + + } elseif (preg_match('/^\x0B\x77/s', $FirstFourBytes)) { + + // AC3 + $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { + + $dummy = $ThisFileInfo; + $dummy['avdataoffset'] = ftell($fd) - 4; + $dummy['avdataend'] = ftell($fd) + $AudioChunkSize; + $dummy['error'] = array(); + $ac3_tag = new getid3_ac3($fd, $dummy); + if (empty($dummy['error'])) { + $ThisFileInfo['audio'] = $dummy['audio']; + $ThisFileInfo['ac3'] = $dummy['ac3']; + $ThisFileInfo['warning'] = $dummy['warning']; + } + + } + + } + + } + + $ParsedAudioStream = true; + fseek($fd, $WhereWeWere, SEEK_SET); + + } + fseek($fd, $chunksize - 4, SEEK_CUR); + break; + + default: + if (!isset($RIFFchunk[$listname])) { + $RIFFchunk[$listname] = array(); + } + $LISTchunkParent = $listname; + $LISTchunkMaxOffset = ftell($fd) - 4 + $chunksize; + if ($parsedChunk = getid3_riff::ParseRIFF($fd, ftell($fd), ftell($fd) + $chunksize - 4, $ThisFileInfo)) { + $RIFFchunk[$listname] = array_merge_recursive($RIFFchunk[$listname], $parsedChunk); + } + break; + } + break; + + default: + $thisindex = 0; + if (isset($RIFFchunk[$chunkname]) && is_array($RIFFchunk[$chunkname])) { + $thisindex = count($RIFFchunk[$chunkname]); + } + $RIFFchunk[$chunkname][$thisindex]['offset'] = ftell($fd) - 8; + $RIFFchunk[$chunkname][$thisindex]['size'] = $chunksize; + switch ($chunkname) { + case 'data': + $ThisFileInfo['avdataoffset'] = ftell($fd); + $ThisFileInfo['avdataend'] = $ThisFileInfo['avdataoffset'] + $chunksize; + + $RIFFdataChunkContentsTest = fread($fd, 36); + + if ((strlen($RIFFdataChunkContentsTest) > 0) && preg_match('/^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]/s', substr($RIFFdataChunkContentsTest, 0, 4))) { + + // Probably is MP3 data + if (getid3_mp3::MPEGaudioHeaderBytesValid(substr($RIFFdataChunkContentsTest, 0, 4))) { + getid3_mp3::getOnlyMPEGaudioInfo($fd, $ThisFileInfo, $RIFFchunk[$chunkname][$thisindex]['offset'], false); + } + + } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 0, 2) == "\x0B\x77")) { + + // This is probably AC-3 data + $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { + + $dummy = $ThisFileInfo; + $dummy['avdataoffset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; + $dummy['avdataend'] = $dummy['avdataoffset'] + $RIFFchunk[$chunkname][$thisindex]['size']; + $dummy['error'] = array(); + + $ac3_tag = new getid3_ac3($fd, $dummy); + if (empty($dummy['error'])) { + $ThisFileInfo['audio'] = $dummy['audio']; + $ThisFileInfo['ac3'] = $dummy['ac3']; + $ThisFileInfo['warning'] = $dummy['warning']; + } + + } + + } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 8, 2) == "\x77\x0B")) { + + // Dolby Digital WAV + // AC-3 content, but not encoded in same format as normal AC-3 file + // For one thing, byte order is swapped + + $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, false)) { + + // ok to use tmpfile here - only 56 bytes + if ($fd_temp = tmpfile()) { + + for ($i = 0; $i < 28; $i += 2) { + // swap byte order + fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 1, 1)); + fwrite($fd_temp, substr($RIFFdataChunkContentsTest, 8 + $i + 0, 1)); + } + + $dummy = $ThisFileInfo; + $dummy['avdataoffset'] = 0; + $dummy['avdataend'] = 20; + $dummy['error'] = array(); + $ac3_tag = new getid3_ac3($fd_temp, $dummy); + fclose($fd_temp); + if (empty($dummy['error'])) { + $ThisFileInfo['audio'] = $dummy['audio']; + $ThisFileInfo['ac3'] = $dummy['ac3']; + $ThisFileInfo['warning'] = $dummy['warning']; + } else { + $ThisFileInfo['error'][] = 'Errors parsing DolbyDigital WAV: '.explode(';', $dummy['error']); + } + + } else { + + $ThisFileInfo['error'][] = 'Could not create temporary file to analyze DolbyDigital WAV'; + + } + + } + + } elseif ((strlen($RIFFdataChunkContentsTest) > 0) && (substr($RIFFdataChunkContentsTest, 0, 4) == 'wvpk')) { + + // This is WavPack data + $ThisFileInfo['wavpack']['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; + $ThisFileInfo['wavpack']['size'] = getid3_lib::LittleEndian2Int(substr($RIFFdataChunkContentsTest, 4, 4)); + getid3_riff::RIFFparseWavPackHeader(substr($RIFFdataChunkContentsTest, 8, 28), $ThisFileInfo); + + } else { + + // This is some other kind of data (quite possibly just PCM) + // do nothing special, just skip it + + } + fseek($fd, $RIFFchunk[$chunkname][$thisindex]['offset'] + 8 + $chunksize, SEEK_SET); + break; + + case 'bext': + case 'cart': + case 'fmt ': + case 'MEXT': + case 'DISP': + // always read data in + $RIFFchunk[$chunkname][$thisindex]['data'] = fread($fd, $chunksize); + break; + + default: + if (!empty($LISTchunkParent) && (($RIFFchunk[$chunkname][$thisindex]['offset'] + $RIFFchunk[$chunkname][$thisindex]['size']) <= $LISTchunkMaxOffset)) { + $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['offset'] = $RIFFchunk[$chunkname][$thisindex]['offset']; + $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['size'] = $RIFFchunk[$chunkname][$thisindex]['size']; + unset($RIFFchunk[$chunkname][$thisindex]['offset']); + unset($RIFFchunk[$chunkname][$thisindex]['size']); + if (isset($RIFFchunk[$chunkname][$thisindex]) && empty($RIFFchunk[$chunkname][$thisindex])) { + unset($RIFFchunk[$chunkname][$thisindex]); + } + if (isset($RIFFchunk[$chunkname]) && empty($RIFFchunk[$chunkname])) { + unset($RIFFchunk[$chunkname]); + } + $RIFFchunk[$LISTchunkParent][$chunkname][$thisindex]['data'] = fread($fd, $chunksize); + } elseif ($chunksize < 2048) { + // only read data in if smaller than 2kB + $RIFFchunk[$chunkname][$thisindex]['data'] = fread($fd, $chunksize); + } else { + fseek($fd, $chunksize, SEEK_CUR); + } + break; + } + break; + + } + + } + + return $RIFFchunk; + } + + + function ParseRIFFdata(&$RIFFdata, &$ThisFileInfo) { + if ($RIFFdata) { + + $tempfile = tempnam('*', 'getID3'); + $fp_temp = fopen($tempfile, "wb"); + $RIFFdataLength = strlen($RIFFdata); + $NewLengthString = getid3_lib::LittleEndian2String($RIFFdataLength, 4); + for ($i = 0; $i < 4; $i++) { + $RIFFdata{$i + 4} = $NewLengthString{$i}; + } + fwrite($fp_temp, $RIFFdata); + fclose($fp_temp); + + $fp_temp = fopen($tempfile, "rb"); + $dummy = array('filesize'=>$RIFFdataLength, 'filenamepath'=>$ThisFileInfo['filenamepath'], 'tags'=>$ThisFileInfo['tags'], 'avdataoffset'=>0, 'avdataend'=>$RIFFdataLength, 'warning'=>$ThisFileInfo['warning'], 'error'=>$ThisFileInfo['error'], 'comments'=>$ThisFileInfo['comments'], 'audio'=>(isset($ThisFileInfo['audio']) ? $ThisFileInfo['audio'] : array()), 'video'=>(isset($ThisFileInfo['video']) ? $ThisFileInfo['video'] : array())); + $riff = new getid3_riff($fp_temp, $dummy); + $ThisFileInfo['riff'] = $dummy['riff']; + $ThisFileInfo['warning'] = $dummy['warning']; + $ThisFileInfo['error'] = $dummy['error']; + $ThisFileInfo['tags'] = $dummy['tags']; + $ThisFileInfo['comments'] = $dummy['comments']; + fclose($fp_temp); + unlink($tempfile); + return true; + } + return false; + } + + + function RIFFparseWAVEFORMATex($WaveFormatExData) { + // shortcut + $WaveFormatEx['raw'] = array(); + $WaveFormatEx_raw = &$WaveFormatEx['raw']; + + $WaveFormatEx_raw['wFormatTag'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 0, 2)); + $WaveFormatEx_raw['nChannels'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 2, 2)); + $WaveFormatEx_raw['nSamplesPerSec'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 4, 4)); + $WaveFormatEx_raw['nAvgBytesPerSec'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 8, 4)); + $WaveFormatEx_raw['nBlockAlign'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 12, 2)); + $WaveFormatEx_raw['wBitsPerSample'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 14, 2)); + if (strlen($WaveFormatExData) > 16) { + $WaveFormatEx_raw['cbSize'] = getid3_lib::LittleEndian2Int(substr($WaveFormatExData, 16, 2)); + } + + $WaveFormatEx['codec'] = getid3_riff::RIFFwFormatTagLookup($WaveFormatEx_raw['wFormatTag']); + $WaveFormatEx['channels'] = $WaveFormatEx_raw['nChannels']; + $WaveFormatEx['sample_rate'] = $WaveFormatEx_raw['nSamplesPerSec']; + $WaveFormatEx['bitrate'] = $WaveFormatEx_raw['nAvgBytesPerSec'] * 8; + $WaveFormatEx['bits_per_sample'] = $WaveFormatEx_raw['wBitsPerSample']; + + return $WaveFormatEx; + } + + + function RIFFparseWavPackHeader($WavPackChunkData, &$ThisFileInfo) { + // typedef struct { + // char ckID [4]; + // long ckSize; + // short version; + // short bits; // added for version 2.00 + // short flags, shift; // added for version 3.00 + // long total_samples, crc, crc2; + // char extension [4], extra_bc, extras [3]; + // } WavpackHeader; + + // shortcut + $ThisFileInfo['wavpack'] = array(); + $thisfile_wavpack = &$ThisFileInfo['wavpack']; + + $thisfile_wavpack['version'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 0, 2)); + if ($thisfile_wavpack['version'] >= 2) { + $thisfile_wavpack['bits'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 2, 2)); + } + if ($thisfile_wavpack['version'] >= 3) { + $thisfile_wavpack['flags_raw'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 4, 2)); + $thisfile_wavpack['shift'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 6, 2)); + $thisfile_wavpack['total_samples'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 8, 4)); + $thisfile_wavpack['crc1'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 12, 4)); + $thisfile_wavpack['crc2'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 16, 4)); + $thisfile_wavpack['extension'] = substr($WavPackChunkData, 20, 4); + $thisfile_wavpack['extra_bc'] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 24, 1)); + for ($i = 0; $i <= 2; $i++) { + $thisfile_wavpack['extras'][] = getid3_lib::LittleEndian2Int(substr($WavPackChunkData, 25 + $i, 1)); + } + + // shortcut + $thisfile_wavpack['flags'] = array(); + $thisfile_wavpack_flags = &$thisfile_wavpack['flags']; + + $thisfile_wavpack_flags['mono'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000001); + $thisfile_wavpack_flags['fast_mode'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000002); + $thisfile_wavpack_flags['raw_mode'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000004); + $thisfile_wavpack_flags['calc_noise'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000008); + $thisfile_wavpack_flags['high_quality'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000010); + $thisfile_wavpack_flags['3_byte_samples'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000020); + $thisfile_wavpack_flags['over_20_bits'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000040); + $thisfile_wavpack_flags['use_wvc'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000080); + $thisfile_wavpack_flags['noiseshaping'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000100); + $thisfile_wavpack_flags['very_fast_mode'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000200); + $thisfile_wavpack_flags['new_high_quality'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000400); + $thisfile_wavpack_flags['cancel_extreme'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x000800); + $thisfile_wavpack_flags['cross_decorrelation'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x001000); + $thisfile_wavpack_flags['new_decorrelation'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x002000); + $thisfile_wavpack_flags['joint_stereo'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x004000); + $thisfile_wavpack_flags['extra_decorrelation'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x008000); + $thisfile_wavpack_flags['override_noiseshape'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x010000); + $thisfile_wavpack_flags['override_jointstereo'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x020000); + $thisfile_wavpack_flags['copy_source_filetime'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x040000); + $thisfile_wavpack_flags['create_exe'] = (bool) ($thisfile_wavpack['flags_raw'] & 0x080000); + } + + return true; + } + + function RIFFwFormatTagLookup($wFormatTag) { + + $begin = __LINE__; + + /** This is not a comment! + + 0x0000 Microsoft Unknown Wave Format + 0x0001 Pulse Code Modulation (PCM) + 0x0002 Microsoft ADPCM + 0x0003 IEEE Float + 0x0004 Compaq Computer VSELP + 0x0005 IBM CVSD + 0x0006 Microsoft A-Law + 0x0007 Microsoft mu-Law + 0x0008 Microsoft DTS + 0x0010 OKI ADPCM + 0x0011 Intel DVI/IMA ADPCM + 0x0012 Videologic MediaSpace ADPCM + 0x0013 Sierra Semiconductor ADPCM + 0x0014 Antex Electronics G.723 ADPCM + 0x0015 DSP Solutions DigiSTD + 0x0016 DSP Solutions DigiFIX + 0x0017 Dialogic OKI ADPCM + 0x0018 MediaVision ADPCM + 0x0019 Hewlett-Packard CU + 0x0020 Yamaha ADPCM + 0x0021 Speech Compression Sonarc + 0x0022 DSP Group TrueSpeech + 0x0023 Echo Speech EchoSC1 + 0x0024 Audiofile AF36 + 0x0025 Audio Processing Technology APTX + 0x0026 AudioFile AF10 + 0x0027 Prosody 1612 + 0x0028 LRC + 0x0030 Dolby AC2 + 0x0031 Microsoft GSM 6.10 + 0x0032 MSNAudio + 0x0033 Antex Electronics ADPCME + 0x0034 Control Resources VQLPC + 0x0035 DSP Solutions DigiREAL + 0x0036 DSP Solutions DigiADPCM + 0x0037 Control Resources CR10 + 0x0038 Natural MicroSystems VBXADPCM + 0x0039 Crystal Semiconductor IMA ADPCM + 0x003A EchoSC3 + 0x003B Rockwell ADPCM + 0x003C Rockwell Digit LK + 0x003D Xebec + 0x0040 Antex Electronics G.721 ADPCM + 0x0041 G.728 CELP + 0x0042 MSG723 + 0x0050 MPEG Layer-2 or Layer-1 + 0x0052 RT24 + 0x0053 PAC + 0x0055 MPEG Layer-3 + 0x0059 Lucent G.723 + 0x0060 Cirrus + 0x0061 ESPCM + 0x0062 Voxware + 0x0063 Canopus Atrac + 0x0064 G.726 ADPCM + 0x0065 G.722 ADPCM + 0x0066 DSAT + 0x0067 DSAT Display + 0x0069 Voxware Byte Aligned + 0x0070 Voxware AC8 + 0x0071 Voxware AC10 + 0x0072 Voxware AC16 + 0x0073 Voxware AC20 + 0x0074 Voxware MetaVoice + 0x0075 Voxware MetaSound + 0x0076 Voxware RT29HW + 0x0077 Voxware VR12 + 0x0078 Voxware VR18 + 0x0079 Voxware TQ40 + 0x0080 Softsound + 0x0081 Voxware TQ60 + 0x0082 MSRT24 + 0x0083 G.729A + 0x0084 MVI MV12 + 0x0085 DF G.726 + 0x0086 DF GSM610 + 0x0088 ISIAudio + 0x0089 Onlive + 0x0091 SBC24 + 0x0092 Dolby AC3 SPDIF + 0x0093 MediaSonic G.723 + 0x0094 Aculab PLC Prosody 8kbps + 0x0097 ZyXEL ADPCM + 0x0098 Philips LPCBB + 0x0099 Packed + 0x00FF AAC + 0x0100 Rhetorex ADPCM + 0x0101 IBM mu-law + 0x0102 IBM A-law + 0x0103 IBM AVC Adaptive Differential Pulse Code Modulation (ADPCM) + 0x0111 Vivo G.723 + 0x0112 Vivo Siren + 0x0123 Digital G.723 + 0x0125 Sanyo LD ADPCM + 0x0130 Sipro Lab Telecom ACELP NET + 0x0131 Sipro Lab Telecom ACELP 4800 + 0x0132 Sipro Lab Telecom ACELP 8V3 + 0x0133 Sipro Lab Telecom G.729 + 0x0134 Sipro Lab Telecom G.729A + 0x0135 Sipro Lab Telecom Kelvin + 0x0140 Windows Media Video V8 + 0x0150 Qualcomm PureVoice + 0x0151 Qualcomm HalfRate + 0x0155 Ring Zero Systems TUB GSM + 0x0160 Microsoft Audio 1 + 0x0161 Windows Media Audio V7 / V8 / V9 + 0x0162 Windows Media Audio Professional V9 + 0x0163 Windows Media Audio Lossless V9 + 0x0200 Creative Labs ADPCM + 0x0202 Creative Labs Fastspeech8 + 0x0203 Creative Labs Fastspeech10 + 0x0210 UHER Informatic GmbH ADPCM + 0x0220 Quarterdeck + 0x0230 I-link Worldwide VC + 0x0240 Aureal RAW Sport + 0x0250 Interactive Products HSX + 0x0251 Interactive Products RPELP + 0x0260 Consistent Software CS2 + 0x0270 Sony SCX + 0x0300 Fujitsu FM Towns Snd + 0x0400 BTV Digital + 0x0401 Intel Music Coder + 0x0450 QDesign Music + 0x0680 VME VMPCM + 0x0681 AT&T Labs TPC + 0x08AE ClearJump LiteWave + 0x1000 Olivetti GSM + 0x1001 Olivetti ADPCM + 0x1002 Olivetti CELP + 0x1003 Olivetti SBC + 0x1004 Olivetti OPR + 0x1100 Lernout & Hauspie Codec (0x1100) + 0x1101 Lernout & Hauspie CELP Codec (0x1101) + 0x1102 Lernout & Hauspie SBC Codec (0x1102) + 0x1103 Lernout & Hauspie SBC Codec (0x1103) + 0x1104 Lernout & Hauspie SBC Codec (0x1104) + 0x1400 Norris + 0x1401 AT&T ISIAudio + 0x1500 Soundspace Music Compression + 0x181C VoxWare RT24 Speech + 0x1FC4 NCT Soft ALF2CD (www.nctsoft.com) + 0x2000 Dolby AC3 + 0x2001 Dolby DTS + 0x2002 WAVE_FORMAT_14_4 + 0x2003 WAVE_FORMAT_28_8 + 0x2004 WAVE_FORMAT_COOK + 0x2005 WAVE_FORMAT_DNET + 0x674F Ogg Vorbis 1 + 0x6750 Ogg Vorbis 2 + 0x6751 Ogg Vorbis 3 + 0x676F Ogg Vorbis 1+ + 0x6770 Ogg Vorbis 2+ + 0x6771 Ogg Vorbis 3+ + 0x7A21 GSM-AMR (CBR, no SID) + 0x7A22 GSM-AMR (VBR, including SID) + 0xFFFE WAVE_FORMAT_EXTENSIBLE + 0xFFFF WAVE_FORMAT_DEVELOPMENT + + */ + + return getid3_lib::EmbeddedLookup('0x'.str_pad(strtoupper(dechex($wFormatTag)), 4, '0', STR_PAD_LEFT), $begin, __LINE__, __FILE__, 'riff-wFormatTag'); + + } + + + function RIFFfourccLookup($fourcc) { + + $begin = __LINE__; + + /** This is not a comment! + + swot http://developer.apple.com/qa/snd/snd07.html + ____ No Codec (____) + _BIT BI_BITFIELDS (Raw RGB) + _JPG JPEG compressed + _PNG PNG compressed W3C/ISO/IEC (RFC-2083) + _RAW Full Frames (Uncompressed) + _RGB Raw RGB Bitmap + _RL4 RLE 4bpp RGB + _RL8 RLE 8bpp RGB + 3IV1 3ivx MPEG-4 v1 + 3IV2 3ivx MPEG-4 v2 + 3IVX 3ivx MPEG-4 + AASC Autodesk Animator + ABYR Kensington ?ABYR? + AEMI Array Microsystems VideoONE MPEG1-I Capture + AFLC Autodesk Animator FLC + AFLI Autodesk Animator FLI + AMPG Array Microsystems VideoONE MPEG + ANIM Intel RDX (ANIM) + AP41 AngelPotion Definitive + ASV1 Asus Video v1 + ASV2 Asus Video v2 + ASVX Asus Video 2.0 (audio) + AUR2 AuraVision Aura 2 Codec - YUV 4:2:2 + AURA AuraVision Aura 1 Codec - YUV 4:1:1 + AVDJ Independent JPEG Group\'s codec (AVDJ) + AVRN Independent JPEG Group\'s codec (AVRN) + AYUV 4:4:4 YUV (AYUV) + AZPR Quicktime Apple Video (AZPR) + BGR Raw RGB32 + BLZ0 Blizzard DivX MPEG-4 + BTVC Conexant Composite Video + BINK RAD Game Tools Bink Video + BT20 Conexant Prosumer Video + BTCV Conexant Composite Video Codec + BW10 Data Translation Broadway MPEG Capture + CC12 Intel YUV12 + CDVC Canopus DV + CFCC Digital Processing Systems DPS Perception + CGDI Microsoft Office 97 Camcorder Video + CHAM Winnov Caviara Champagne + CJPG Creative WebCam JPEG + CLJR Cirrus Logic YUV 4:1:1 + CMYK Common Data Format in Printing (Colorgraph) + CPLA Weitek 4:2:0 YUV Planar + CRAM Microsoft Video 1 (CRAM) + cvid Radius Cinepak + CVID Radius Cinepak + CWLT Microsoft Color WLT DIB + CYUV Creative Labs YUV + CYUY ATI YUV + D261 H.261 + D263 H.263 + DIB Device Independent Bitmap + DIV1 FFmpeg OpenDivX + DIV2 Microsoft MPEG-4 v1/v2 + DIV3 DivX ;-) MPEG-4 v3.x Low-Motion + DIV4 DivX ;-) MPEG-4 v3.x Fast-Motion + DIV5 DivX MPEG-4 v5.x + DIV6 DivX ;-) (MS MPEG-4 v3.x) + DIVX DivX MPEG-4 v4 (OpenDivX / Project Mayo) + divx DivX MPEG-4 + DMB1 Matrox Rainbow Runner hardware MJPEG + DMB2 Paradigm MJPEG + DSVD ?DSVD? + DUCK Duck TrueMotion 1.0 + DPS0 DPS/Leitch Reality Motion JPEG + DPSC DPS/Leitch PAR Motion JPEG + DV25 Matrox DVCPRO codec + DV50 Matrox DVCPRO50 codec + DVC IEC 61834 and SMPTE 314M (DVC/DV Video) + DVCP IEC 61834 and SMPTE 314M (DVC/DV Video) + DVHD IEC Standard DV 1125 lines @ 30fps / 1250 lines @ 25fps + DVMA Darim Vision DVMPEG (dummy for MPEG compressor) (www.darvision.com) + DVSL IEC Standard DV compressed in SD (SDL) + DVAN ?DVAN? + DVE2 InSoft DVE-2 Videoconferencing + dvsd IEC 61834 and SMPTE 314M DVC/DV Video + DVSD IEC 61834 and SMPTE 314M DVC/DV Video + DVX1 Lucent DVX1000SP Video Decoder + DVX2 Lucent DVX2000S Video Decoder + DVX3 Lucent DVX3000S Video Decoder + DX50 DivX v5 + DXT1 Microsoft DirectX Compressed Texture (DXT1) + DXT2 Microsoft DirectX Compressed Texture (DXT2) + DXT3 Microsoft DirectX Compressed Texture (DXT3) + DXT4 Microsoft DirectX Compressed Texture (DXT4) + DXT5 Microsoft DirectX Compressed Texture (DXT5) + DXTC Microsoft DirectX Compressed Texture (DXTC) + DXTn Microsoft DirectX Compressed Texture (DXTn) + EM2V Etymonix MPEG-2 I-frame (www.etymonix.com) + EKQ0 Elsa ?EKQ0? + ELK0 Elsa ?ELK0? + ESCP Eidos Escape + ETV1 eTreppid Video ETV1 + ETV2 eTreppid Video ETV2 + ETVC eTreppid Video ETVC + FLIC Autodesk FLI/FLC Animation + FRWT Darim Vision Forward Motion JPEG (www.darvision.com) + FRWU Darim Vision Forward Uncompressed (www.darvision.com) + FLJP D-Vision Field Encoded Motion JPEG + FRWA SoftLab-Nsk Forward Motion JPEG w/ alpha channel + FRWD SoftLab-Nsk Forward Motion JPEG + FVF1 Iterated Systems Fractal Video Frame + GLZW Motion LZW (gabest@freemail.hu) + GPEG Motion JPEG (gabest@freemail.hu) + GWLT Microsoft Greyscale WLT DIB + H260 Intel ITU H.260 Videoconferencing + H261 Intel ITU H.261 Videoconferencing + H262 Intel ITU H.262 Videoconferencing + H263 Intel ITU H.263 Videoconferencing + H264 Intel ITU H.264 Videoconferencing + H265 Intel ITU H.265 Videoconferencing + H266 Intel ITU H.266 Videoconferencing + H267 Intel ITU H.267 Videoconferencing + H268 Intel ITU H.268 Videoconferencing + H269 Intel ITU H.269 Videoconferencing + HFYU Huffman Lossless Codec + HMCR Rendition Motion Compensation Format (HMCR) + HMRR Rendition Motion Compensation Format (HMRR) + I263 FFmpeg I263 decoder + IF09 Indeo YVU9 ("YVU9 with additional delta-frame info after the U plane") + IUYV Interlaced version of UYVY (www.leadtools.com) + IY41 Interlaced version of Y41P (www.leadtools.com) + IYU1 12 bit format used in mode 2 of the IEEE 1394 Digital Camera 1.04 spec IEEE standard + IYU2 24 bit format used in mode 2 of the IEEE 1394 Digital Camera 1.04 spec IEEE standard + IYUV Planar YUV format (8-bpp Y plane, followed by 8-bpp 2×2 U and V planes) + i263 Intel ITU H.263 Videoconferencing (i263) + I420 Intel Indeo 4 + IAN Intel Indeo 4 (RDX) + ICLB InSoft CellB Videoconferencing + IGOR Power DVD + IJPG Intergraph JPEG + ILVC Intel Layered Video + ILVR ITU-T H.263+ + IPDV I-O Data Device Giga AVI DV Codec + IR21 Intel Indeo 2.1 + IRAW Intel YUV Uncompressed + IV30 Intel Indeo 3.0 + IV31 Intel Indeo 3.1 + IV32 Ligos Indeo 3.2 + IV33 Ligos Indeo 3.3 + IV34 Ligos Indeo 3.4 + IV35 Ligos Indeo 3.5 + IV36 Ligos Indeo 3.6 + IV37 Ligos Indeo 3.7 + IV38 Ligos Indeo 3.8 + IV39 Ligos Indeo 3.9 + IV40 Ligos Indeo Interactive 4.0 + IV41 Ligos Indeo Interactive 4.1 + IV42 Ligos Indeo Interactive 4.2 + IV43 Ligos Indeo Interactive 4.3 + IV44 Ligos Indeo Interactive 4.4 + IV45 Ligos Indeo Interactive 4.5 + IV46 Ligos Indeo Interactive 4.6 + IV47 Ligos Indeo Interactive 4.7 + IV48 Ligos Indeo Interactive 4.8 + IV49 Ligos Indeo Interactive 4.9 + IV50 Ligos Indeo Interactive 5.0 + JBYR Kensington ?JBYR? + JPEG Still Image JPEG DIB + JPGL Pegasus Lossless Motion JPEG + KMVC Team17 Software Karl Morton\'s Video Codec + LSVM Vianet Lighting Strike Vmail (Streaming) (www.vianet.com) + LEAD LEAD Video Codec + Ljpg LEAD MJPEG Codec + MDVD Alex MicroDVD Video (hacked MS MPEG-4) (www.tiasoft.de) + MJPA Morgan Motion JPEG (MJPA) (www.morgan-multimedia.com) + MJPB Morgan Motion JPEG (MJPB) (www.morgan-multimedia.com) + MMES Matrox MPEG-2 I-frame + MP2v Microsoft S-Mpeg 4 version 1 (MP2v) + MP42 Microsoft S-Mpeg 4 version 2 (MP42) + MP43 Microsoft S-Mpeg 4 version 3 (MP43) + MP4S Microsoft S-Mpeg 4 version 3 (MP4S) + MP4V FFmpeg MPEG-4 + MPG1 FFmpeg MPEG 1/2 + MPG2 FFmpeg MPEG 1/2 + MPG3 FFmpeg DivX ;-) (MS MPEG-4 v3) + MPG4 Microsoft MPEG-4 + MPGI Sigma Designs MPEG + MPNG PNG images decoder + MSS1 Microsoft Windows Screen Video + MSZH LCL (Lossless Codec Library) (www.geocities.co.jp/Playtown-Denei/2837/LRC.htm) + M261 Microsoft H.261 + M263 Microsoft H.263 + M4S2 Microsoft Fully Compliant MPEG-4 v2 simple profile (M4S2) + m4s2 Microsoft Fully Compliant MPEG-4 v2 simple profile (m4s2) + MC12 ATI Motion Compensation Format (MC12) + MCAM ATI Motion Compensation Format (MCAM) + MJ2C Morgan Multimedia Motion JPEG2000 + mJPG IBM Motion JPEG w/ Huffman Tables + MJPG Microsoft Motion JPEG DIB + MP42 Microsoft MPEG-4 (low-motion) + MP43 Microsoft MPEG-4 (fast-motion) + MP4S Microsoft MPEG-4 (MP4S) + mp4s Microsoft MPEG-4 (mp4s) + MPEG Chromatic Research MPEG-1 Video I-Frame + MPG4 Microsoft MPEG-4 Video High Speed Compressor + MPGI Sigma Designs MPEG + MRCA FAST Multimedia Martin Regen Codec + MRLE Microsoft Run Length Encoding + MSVC Microsoft Video 1 + MTX1 Matrox ?MTX1? + MTX2 Matrox ?MTX2? + MTX3 Matrox ?MTX3? + MTX4 Matrox ?MTX4? + MTX5 Matrox ?MTX5? + MTX6 Matrox ?MTX6? + MTX7 Matrox ?MTX7? + MTX8 Matrox ?MTX8? + MTX9 Matrox ?MTX9? + MV12 Motion Pixels Codec (old) + MWV1 Aware Motion Wavelets + nAVI SMR Codec (hack of Microsoft MPEG-4) (IRC #shadowrealm) + NT00 NewTek LightWave HDTV YUV w/ Alpha (www.newtek.com) + NUV1 NuppelVideo + NTN1 Nogatech Video Compression 1 + NVS0 nVidia GeForce Texture (NVS0) + NVS1 nVidia GeForce Texture (NVS1) + NVS2 nVidia GeForce Texture (NVS2) + NVS3 nVidia GeForce Texture (NVS3) + NVS4 nVidia GeForce Texture (NVS4) + NVS5 nVidia GeForce Texture (NVS5) + NVT0 nVidia GeForce Texture (NVT0) + NVT1 nVidia GeForce Texture (NVT1) + NVT2 nVidia GeForce Texture (NVT2) + NVT3 nVidia GeForce Texture (NVT3) + NVT4 nVidia GeForce Texture (NVT4) + NVT5 nVidia GeForce Texture (NVT5) + PIXL MiroXL, Pinnacle PCTV + PDVC I-O Data Device Digital Video Capture DV codec + PGVV Radius Video Vision + PHMO IBM Photomotion + PIM1 MPEG Realtime (Pinnacle Cards) + PIM2 Pegasus Imaging ?PIM2? + PIMJ Pegasus Imaging Lossless JPEG + PVEZ Horizons Technology PowerEZ + PVMM PacketVideo Corporation MPEG-4 + PVW2 Pegasus Imaging Wavelet Compression + Q1.0 Q-Team\'s QPEG 1.0 (www.q-team.de) + Q1.1 Q-Team\'s QPEG 1.1 (www.q-team.de) + QPEG Q-Team QPEG 1.0 + qpeq Q-Team QPEG 1.1 + RGB Raw BGR32 + RGBA Raw RGB w/ Alpha + RMP4 REALmagic MPEG-4 (unauthorized XVID copy) (www.sigmadesigns.com) + ROQV Id RoQ File Video Decoder + RPZA Quicktime Apple Video (RPZA) + RUD0 Rududu video codec (http://rududu.ifrance.com/rududu/) + RV10 RealVideo 1.0 (aka RealVideo 5.0) + RV13 RealVideo 1.0 (RV13) + RV20 RealVideo G2 + RV30 RealVideo 8 + RV40 RealVideo 9 + RGBT Raw RGB w/ Transparency + RLE Microsoft Run Length Encoder + RLE4 Run Length Encoded (4bpp, 16-color) + RLE8 Run Length Encoded (8bpp, 256-color) + RT21 Intel Indeo RealTime Video 2.1 + rv20 RealVideo G2 + rv30 RealVideo 8 + RVX Intel RDX (RVX ) + SMC Apple Graphics (SMC ) + SP54 Logitech Sunplus Sp54 Codec for Mustek GSmart Mini 2 + SPIG Radius Spigot + SVQ3 Sorenson Video 3 (Apple Quicktime 5) + s422 Tekram VideoCap C210 YUV 4:2:2 + SDCC Sun Communication Digital Camera Codec + SFMC CrystalNet Surface Fitting Method + SMSC Radius SMSC + SMSD Radius SMSD + smsv WorldConnect Wavelet Video + SPIG Radius Spigot + SPLC Splash Studios ACM Audio Codec (www.splashstudios.net) + SQZ2 Microsoft VXTreme Video Codec V2 + STVA ST Microelectronics CMOS Imager Data (Bayer) + STVB ST Microelectronics CMOS Imager Data (Nudged Bayer) + STVC ST Microelectronics CMOS Imager Data (Bunched) + STVX ST Microelectronics CMOS Imager Data (Extended CODEC Data Format) + STVY ST Microelectronics CMOS Imager Data (Extended CODEC Data Format with Correction Data) + SV10 Sorenson Video R1 + SVQ1 Sorenson Video + T420 Toshiba YUV 4:2:0 + TM2A Duck TrueMotion Archiver 2.0 (www.duck.com) + TVJP Pinnacle/Truevision Targa 2000 board (TVJP) + TVMJ Pinnacle/Truevision Targa 2000 board (TVMJ) + TY0N Tecomac Low-Bit Rate Codec (www.tecomac.com) + TY2C Trident Decompression Driver + TLMS TeraLogic Motion Intraframe Codec (TLMS) + TLST TeraLogic Motion Intraframe Codec (TLST) + TM20 Duck TrueMotion 2.0 + TM2X Duck TrueMotion 2X + TMIC TeraLogic Motion Intraframe Codec (TMIC) + TMOT Horizons Technology TrueMotion S + tmot Horizons TrueMotion Video Compression + TR20 Duck TrueMotion RealTime 2.0 + TSCC TechSmith Screen Capture Codec + TV10 Tecomac Low-Bit Rate Codec + TY2N Trident ?TY2N? + U263 UB Video H.263/H.263+/H.263++ Decoder + UMP4 UB Video MPEG 4 (www.ubvideo.com) + UYNV Nvidia UYVY packed 4:2:2 + UYVP Evans & Sutherland YCbCr 4:2:2 extended precision + UCOD eMajix.com ClearVideo + ULTI IBM Ultimotion + UYVY UYVY packed 4:2:2 + V261 Lucent VX2000S + VIFP VFAPI Reader Codec (www.yks.ne.jp/~hori/) + VIV1 FFmpeg H263+ decoder + VIV2 Vivo H.263 + VQC2 Vector-quantised codec 2 (research) http://eprints.ecs.soton.ac.uk/archive/00001310/01/VTC97-js.pdf) + VTLP Alaris VideoGramPiX + VYU9 ATI YUV (VYU9) + VYUY ATI YUV (VYUY) + V261 Lucent VX2000S + V422 Vitec Multimedia 24-bit YUV 4:2:2 Format + V655 Vitec Multimedia 16-bit YUV 4:2:2 Format + VCR1 ATI Video Codec 1 + VCR2 ATI Video Codec 2 + VCR3 ATI VCR 3.0 + VCR4 ATI VCR 4.0 + VCR5 ATI VCR 5.0 + VCR6 ATI VCR 6.0 + VCR7 ATI VCR 7.0 + VCR8 ATI VCR 8.0 + VCR9 ATI VCR 9.0 + VDCT Vitec Multimedia Video Maker Pro DIB + VDOM VDOnet VDOWave + VDOW VDOnet VDOLive (H.263) + VDTZ Darim Vison VideoTizer YUV + VGPX Alaris VideoGramPiX + VIDS Vitec Multimedia YUV 4:2:2 CCIR 601 for V422 + VIVO Vivo H.263 v2.00 + vivo Vivo H.263 + VIXL Miro/Pinnacle Video XL + VLV1 VideoLogic/PURE Digital Videologic Capture + VP30 On2 VP3.0 + VP31 On2 VP3.1 + VX1K Lucent VX1000S Video Codec + VX2K Lucent VX2000S Video Codec + VXSP Lucent VX1000SP Video Codec + WBVC Winbond W9960 + WHAM Microsoft Video 1 (WHAM) + WINX Winnov Software Compression + WJPG AverMedia Winbond JPEG + WMV1 Windows Media Video V7 + WMV2 Windows Media Video V8 + WMV3 Windows Media Video V9 + WNV1 Winnov Hardware Compression + XYZP Extended PAL format XYZ palette (www.riff.org) + x263 Xirlink H.263 + XLV0 NetXL Video Decoder + XMPG Xing MPEG (I-Frame only) + XVID XviD MPEG-4 (www.xvid.org) + XXAN ?XXAN? + Y422 ADS Technologies Copy of UYVY used in Pyro WebCam firewire camera + Y800 Simple, single Y plane for monochrome images + YU92 Intel YUV (YU92) + YUNV Nvidia Uncompressed YUV 4:2:2 + YUVP Extended PAL format YUV palette (www.riff.org) + Y211 YUV 2:1:1 Packed + Y411 YUV 4:1:1 Packed + Y41B Weitek YUV 4:1:1 Planar + Y41P Brooktree PC1 YUV 4:1:1 Packed + Y41T Brooktree PC1 YUV 4:1:1 with transparency + Y42B Weitek YUV 4:2:2 Planar + Y42T Brooktree UYUV 4:2:2 with transparency + Y8 Grayscale video + YC12 Intel YUV 12 codec + YUV8 Winnov Caviar YUV8 + YUV9 Intel YUV9 + YUY2 Uncompressed YUV 4:2:2 + YUYV Canopus YUV + YV12 YVU12 Planar + YVU9 Intel YVU9 Planar (8-bpp Y plane, followed by 8-bpp 4x4 U and V planes) + YVYU YVYU 4:2:2 Packed + ZLIB Lossless Codec Library zlib compression (www.geocities.co.jp/Playtown-Denei/2837/LRC.htm) + ZPEG Metheus Video Zipper + + */ + + return getid3_lib::EmbeddedLookup($fourcc, $begin, __LINE__, __FILE__, 'riff-fourcc'); + } + + + function EitherEndian2Int(&$ThisFileInfo, $byteword, $signed=false) { + if ($ThisFileInfo['fileformat'] == 'riff') { + return getid3_lib::LittleEndian2Int($byteword, $signed); + } + return getid3_lib::BigEndian2Int($byteword, false, $signed); + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio-video.swf.php b/modules/id3/getid3/module.audio-video.swf.php new file mode 100644 index 00000000..a03806ea --- /dev/null +++ b/modules/id3/getid3/module.audio-video.swf.php @@ -0,0 +1,153 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio-video.swf.php // +// module for analyzing Shockwave Flash files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_swf +{ + + function getid3_swf(&$fd, &$ThisFileInfo, $ReturnAllTagData=false) { + $ThisFileInfo['fileformat'] = 'swf'; + $ThisFileInfo['video']['dataformat'] = 'swf'; + + // http://www.openswf.org/spec/SWFfileformat.html + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + +//echo 'reading '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' bytes<br>'; + $SWFfileData = fread($fd, $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']); // 8 + 2 + 2 + max(9) bytes NOT including Frame_Size RECT data + + $ThisFileInfo['swf']['header']['signature'] = substr($SWFfileData, 0, 3); + switch ($ThisFileInfo['swf']['header']['signature']) { + case 'FWS': + $ThisFileInfo['swf']['header']['compressed'] = false; + break; + + case 'CWS': + $ThisFileInfo['swf']['header']['compressed'] = true; + break; + + default: + $ThisFileInfo['error'][] = 'Expecting "FWS" or "CWS" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$ThisFileInfo['swf']['header']['signature'].'"'; + unset($ThisFileInfo['swf']); + unset($ThisFileInfo['fileformat']); + return false; + break; + } + $ThisFileInfo['swf']['header']['version'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 3, 1)); + $ThisFileInfo['swf']['header']['length'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 4, 4)); + +//echo '1<br>'; + if ($ThisFileInfo['swf']['header']['compressed']) { + +//echo '2<br>'; +// $foo = substr($SWFfileData, 8, 4096); +// echo '['.strlen($foo).']<br>'; +// $fee = gzuncompress($foo); +// echo '('.strlen($fee).')<br>'; +//return false; +//echo '<br>time: '.time().'<br>'; +//return false; + if ($UncompressedFileData = gzuncompress(substr($SWFfileData, 8))) { + +//echo '3<br>'; + $SWFfileData = substr($SWFfileData, 0, 8).$UncompressedFileData; + + } else { + +//echo '4<br>'; + $ThisFileInfo['error'][] = 'Error decompressing compressed SWF data'; + return false; + + } + + } + + $FrameSizeBitsPerValue = (ord(substr($SWFfileData, 8, 1)) & 0xF8) >> 3; + $FrameSizeDataLength = ceil((5 + (4 * $FrameSizeBitsPerValue)) / 8); + $FrameSizeDataString = str_pad(decbin(ord(substr($SWFfileData, 8, 1)) & 0x07), 3, '0', STR_PAD_LEFT); + for ($i = 1; $i < $FrameSizeDataLength; $i++) { + $FrameSizeDataString .= str_pad(decbin(ord(substr($SWFfileData, 8 + $i, 1))), 8, '0', STR_PAD_LEFT); + } + list($X1, $X2, $Y1, $Y2) = explode("\n", wordwrap($FrameSizeDataString, $FrameSizeBitsPerValue, "\n", 1)); + $ThisFileInfo['swf']['header']['frame_width'] = getid3_lib::Bin2Dec($X2); + $ThisFileInfo['swf']['header']['frame_height'] = getid3_lib::Bin2Dec($Y2); + + // http://www-lehre.informatik.uni-osnabrueck.de/~fbstark/diplom/docs/swf/Flash_Uncovered.htm + // Next in the header is the frame rate, which is kind of weird. + // It is supposed to be stored as a 16bit integer, but the first byte + // (or last depending on how you look at it) is completely ignored. + // Example: 0x000C -> 0x0C -> 12 So the frame rate is 12 fps. + + // Byte at (8 + $FrameSizeDataLength) is always zero and ignored + $ThisFileInfo['swf']['header']['frame_rate'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 9 + $FrameSizeDataLength, 1)); + $ThisFileInfo['swf']['header']['frame_count'] = getid3_lib::LittleEndian2Int(substr($SWFfileData, 10 + $FrameSizeDataLength, 2)); + + $ThisFileInfo['video']['frame_rate'] = $ThisFileInfo['swf']['header']['frame_rate']; + $ThisFileInfo['video']['resolution_x'] = intval(round($ThisFileInfo['swf']['header']['frame_width'] / 20)); + $ThisFileInfo['video']['resolution_y'] = intval(round($ThisFileInfo['swf']['header']['frame_height'] / 20)); + $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1; + + if (($ThisFileInfo['swf']['header']['frame_count'] > 0) && ($ThisFileInfo['swf']['header']['frame_rate'] > 0)) { + $ThisFileInfo['playtime_seconds'] = $ThisFileInfo['swf']['header']['frame_count'] / $ThisFileInfo['swf']['header']['frame_rate']; + } + + + // SWF tags + + $CurrentOffset = 12 + $FrameSizeDataLength; + $SWFdataLength = strlen($SWFfileData); + + while ($CurrentOffset < $SWFdataLength) { + + $TagIDTagLength = getid3_lib::LittleEndian2Int(substr($SWFfileData, $CurrentOffset, 2)); + $TagID = ($TagIDTagLength & 0xFFFC) >> 6; + $TagLength = ($TagIDTagLength & 0x003F); + $CurrentOffset += 2; + if ($TagLength == 0x3F) { + $TagLength = getid3_lib::LittleEndian2Int(substr($SWFfileData, $CurrentOffset, 4)); + $CurrentOffset += 4; + } + + unset($TagData); + $TagData['offset'] = $CurrentOffset; + $TagData['size'] = $TagLength; + $TagData['id'] = $TagID; + $TagData['data'] = substr($SWFfileData, $CurrentOffset, $TagLength); + switch ($TagID) { + case 0: // end of movie + break 2; + + case 9: // Set background color + //$ThisFileInfo['swf']['tags'][] = $TagData; + $ThisFileInfo['swf']['bgcolor'] = strtoupper(str_pad(dechex(getid3_lib::BigEndian2Int($TagData['data'])), 6, '0', STR_PAD_LEFT)); + break; + + default: + if ($ReturnAllTagData) { + $ThisFileInfo['swf']['tags'][] = $TagData; + } + break; + } + + $CurrentOffset += $TagLength; + } + + return true; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.aac.php b/modules/id3/getid3/module.audio.aac.php new file mode 100644 index 00000000..9e7ce47c --- /dev/null +++ b/modules/id3/getid3/module.audio.aac.php @@ -0,0 +1,538 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.aac.php // +// module for analyzing AAC Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_aac +{ + + // new combined constructor + function getid3_aac(&$fd, &$ThisFileInfo, $option) { + + if ($option === 'adif') { + $this->getAACADIFheaderFilepointer($fd, $ThisFileInfo); + } + elseif ($option === 'adts') { + $this->getAACADTSheaderFilepointer($fd, $ThisFileInfo); + } + } + + + + function getAACADIFheaderFilepointer(&$fd, &$ThisFileInfo) { + $ThisFileInfo['fileformat'] = 'aac'; + $ThisFileInfo['audio']['dataformat'] = 'aac'; + $ThisFileInfo['audio']['lossless'] = false; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $AACheader = fread($fd, 1024); + $offset = 0; + + if (substr($AACheader, 0, 4) == 'ADIF') { + + // http://faac.sourceforge.net/wiki/index.php?page=ADIF + + // http://libmpeg.org/mpeg4/doc/w2203tfs.pdf + // adif_header() { + // adif_id 32 + // copyright_id_present 1 + // if( copyright_id_present ) + // copyright_id 72 + // original_copy 1 + // home 1 + // bitstream_type 1 + // bitrate 23 + // num_program_config_elements 4 + // for (i = 0; i < num_program_config_elements + 1; i++ ) { + // if( bitstream_type == '0' ) + // adif_buffer_fullness 20 + // program_config_element() + // } + // } + + $AACheaderBitstream = getid3_lib::BigEndian2Bin($AACheader); + $bitoffset = 0; + + $ThisFileInfo['aac']['header_type'] = 'ADIF'; + $bitoffset += 32; + $ThisFileInfo['aac']['header']['mpeg_version'] = 4; + + $ThisFileInfo['aac']['header']['copyright'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1'); + $bitoffset += 1; + if ($ThisFileInfo['aac']['header']['copyright']) { + $ThisFileInfo['aac']['header']['copyright_id'] = getid3_lib::Bin2String(substr($AACheaderBitstream, $bitoffset, 72)); + $bitoffset += 72; + } + $ThisFileInfo['aac']['header']['original_copy'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1'); + $bitoffset += 1; + $ThisFileInfo['aac']['header']['home'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1'); + $bitoffset += 1; + $ThisFileInfo['aac']['header']['is_vbr'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1'); + $bitoffset += 1; + if ($ThisFileInfo['aac']['header']['is_vbr']) { + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + $ThisFileInfo['aac']['header']['bitrate_max'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23)); + $bitoffset += 23; + } else { + $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + $ThisFileInfo['aac']['header']['bitrate'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23)); + $bitoffset += 23; + $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['aac']['header']['bitrate']; + } + if ($ThisFileInfo['audio']['bitrate'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt AAC file: bitrate_audio == zero'; + return false; + } + $ThisFileInfo['aac']['header']['num_program_configs'] = 1 + getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + + for ($i = 0; $i < $ThisFileInfo['aac']['header']['num_program_configs']; $i++) { + // http://www.audiocoding.com/wiki/index.php?page=program_config_element + + // buffer_fullness 20 + + // element_instance_tag 4 + // object_type 2 + // sampling_frequency_index 4 + // num_front_channel_elements 4 + // num_side_channel_elements 4 + // num_back_channel_elements 4 + // num_lfe_channel_elements 2 + // num_assoc_data_elements 3 + // num_valid_cc_elements 4 + // mono_mixdown_present 1 + // mono_mixdown_element_number 4 if mono_mixdown_present == 1 + // stereo_mixdown_present 1 + // stereo_mixdown_element_number 4 if stereo_mixdown_present == 1 + // matrix_mixdown_idx_present 1 + // matrix_mixdown_idx 2 if matrix_mixdown_idx_present == 1 + // pseudo_surround_enable 1 if matrix_mixdown_idx_present == 1 + // for (i = 0; i < num_front_channel_elements; i++) { + // front_element_is_cpe[i] 1 + // front_element_tag_select[i] 4 + // } + // for (i = 0; i < num_side_channel_elements; i++) { + // side_element_is_cpe[i] 1 + // side_element_tag_select[i] 4 + // } + // for (i = 0; i < num_back_channel_elements; i++) { + // back_element_is_cpe[i] 1 + // back_element_tag_select[i] 4 + // } + // for (i = 0; i < num_lfe_channel_elements; i++) { + // lfe_element_tag_select[i] 4 + // } + // for (i = 0; i < num_assoc_data_elements; i++) { + // assoc_data_element_tag_select[i] 4 + // } + // for (i = 0; i < num_valid_cc_elements; i++) { + // cc_element_is_ind_sw[i] 1 + // valid_cc_element_tag_select[i] 4 + // } + // byte_alignment() VAR + // comment_field_bytes 8 + // for (i = 0; i < comment_field_bytes; i++) { + // comment_field_data[i] 8 + // } + + if (!$ThisFileInfo['aac']['header']['is_vbr']) { + $ThisFileInfo['aac']['program_configs'][$i]['buffer_fullness'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 20)); + $bitoffset += 20; + } + $ThisFileInfo['aac']['program_configs'][$i]['element_instance_tag'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + $ThisFileInfo['aac']['program_configs'][$i]['object_type'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2)); + $bitoffset += 2; + $ThisFileInfo['aac']['program_configs'][$i]['sampling_frequency_index'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + $ThisFileInfo['aac']['program_configs'][$i]['num_front_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + $ThisFileInfo['aac']['program_configs'][$i]['num_side_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + $ThisFileInfo['aac']['program_configs'][$i]['num_back_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + $ThisFileInfo['aac']['program_configs'][$i]['num_lfe_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2)); + $bitoffset += 2; + $ThisFileInfo['aac']['program_configs'][$i]['num_assoc_data_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 3)); + $bitoffset += 3; + $ThisFileInfo['aac']['program_configs'][$i]['num_valid_cc_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + $ThisFileInfo['aac']['program_configs'][$i]['mono_mixdown_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + if ($ThisFileInfo['aac']['program_configs'][$i]['mono_mixdown_present']) { + $ThisFileInfo['aac']['program_configs'][$i]['mono_mixdown_element_number'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + $ThisFileInfo['aac']['program_configs'][$i]['stereo_mixdown_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + if ($ThisFileInfo['aac']['program_configs'][$i]['stereo_mixdown_present']) { + $ThisFileInfo['aac']['program_configs'][$i]['stereo_mixdown_element_number'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + $ThisFileInfo['aac']['program_configs'][$i]['matrix_mixdown_idx_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + if ($ThisFileInfo['aac']['program_configs'][$i]['matrix_mixdown_idx_present']) { + $ThisFileInfo['aac']['program_configs'][$i]['matrix_mixdown_idx'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2)); + $bitoffset += 2; + $ThisFileInfo['aac']['program_configs'][$i]['pseudo_surround_enable'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + } + for ($j = 0; $j < $ThisFileInfo['aac']['program_configs'][$i]['num_front_channel_elements']; $j++) { + $ThisFileInfo['aac']['program_configs'][$i]['front_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + $ThisFileInfo['aac']['program_configs'][$i]['front_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + for ($j = 0; $j < $ThisFileInfo['aac']['program_configs'][$i]['num_side_channel_elements']; $j++) { + $ThisFileInfo['aac']['program_configs'][$i]['side_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + $ThisFileInfo['aac']['program_configs'][$i]['side_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + for ($j = 0; $j < $ThisFileInfo['aac']['program_configs'][$i]['num_back_channel_elements']; $j++) { + $ThisFileInfo['aac']['program_configs'][$i]['back_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + $ThisFileInfo['aac']['program_configs'][$i]['back_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + for ($j = 0; $j < $ThisFileInfo['aac']['program_configs'][$i]['num_lfe_channel_elements']; $j++) { + $ThisFileInfo['aac']['program_configs'][$i]['lfe_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + for ($j = 0; $j < $ThisFileInfo['aac']['program_configs'][$i]['num_assoc_data_elements']; $j++) { + $ThisFileInfo['aac']['program_configs'][$i]['assoc_data_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + for ($j = 0; $j < $ThisFileInfo['aac']['program_configs'][$i]['num_valid_cc_elements']; $j++) { + $ThisFileInfo['aac']['program_configs'][$i]['cc_element_is_ind_sw'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + $ThisFileInfo['aac']['program_configs'][$i]['valid_cc_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + } + + $bitoffset = ceil($bitoffset / 8) * 8; + + $ThisFileInfo['aac']['program_configs'][$i]['comment_field_bytes'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 8)); + $bitoffset += 8; + $ThisFileInfo['aac']['program_configs'][$i]['comment_field'] = getid3_lib::Bin2String(substr($AACheaderBitstream, $bitoffset, 8 * $ThisFileInfo['aac']['program_configs'][$i]['comment_field_bytes'])); + $bitoffset += 8 * $ThisFileInfo['aac']['program_configs'][$i]['comment_field_bytes']; + + + $ThisFileInfo['aac']['header']['profile_text'] = $this->AACprofileLookup($ThisFileInfo['aac']['program_configs'][$i]['object_type'], $ThisFileInfo['aac']['header']['mpeg_version']); + $ThisFileInfo['aac']['program_configs'][$i]['sampling_frequency'] = $this->AACsampleRateLookup($ThisFileInfo['aac']['program_configs'][$i]['sampling_frequency_index']); + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['aac']['program_configs'][$i]['sampling_frequency']; + $ThisFileInfo['audio']['channels'] = $this->AACchannelCountCalculate($ThisFileInfo['aac']['program_configs'][$i]); + if ($ThisFileInfo['aac']['program_configs'][$i]['comment_field']) { + $ThisFileInfo['aac']['comments'][] = $ThisFileInfo['aac']['program_configs'][$i]['comment_field']; + } + } + $ThisFileInfo['playtime_seconds'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['audio']['bitrate']; + + $ThisFileInfo['audio']['encoder_options'] = $ThisFileInfo['aac']['header_type'].' '.$ThisFileInfo['aac']['header']['profile_text']; + + + + return true; + + } else { + + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['aac']); + $ThisFileInfo['error'][] = 'AAC-ADIF synch not found at offset '.$ThisFileInfo['avdataoffset'].' (expected "ADIF", found "'.substr($AACheader, 0, 4).'" instead)'; + return false; + + } + + } + + + function getAACADTSheaderFilepointer(&$fd, &$ThisFileInfo, $MaxFramesToScan=1000000, $ReturnExtendedInfo=false) { + // based loosely on code from AACfile by Jurgen Faul <jfaulØgmx.de> + // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html + + + // http://faac.sourceforge.net/wiki/index.php?page=ADTS + + // * ADTS Fixed Header: these don't change from frame to frame + // syncword 12 always: '111111111111' + // ID 1 0: MPEG-4, 1: MPEG-2 + // layer 2 always: '00' + // protection_absent 1 + // profile 2 + // sampling_frequency_index 4 + // private_bit 1 + // channel_configuration 3 + // original/copy 1 + // home 1 + // emphasis 2 only if ID == 0 (ie MPEG-4) + + // * ADTS Variable Header: these can change from frame to frame + // copyright_identification_bit 1 + // copyright_identification_start 1 + // aac_frame_length 13 length of the frame including header (in bytes) + // adts_buffer_fullness 11 0x7FF indicates VBR + // no_raw_data_blocks_in_frame 2 + + // * ADTS Error check + // crc_check 16 only if protection_absent == 0 + + $byteoffset = 0; + $framenumber = 0; + + // Init bit pattern array + static $decbin = array(); + + // Populate $bindec + for ($i = 0; $i < 256; $i++) { + $decbin[chr($i)] = str_pad(decbin($i), 8, '0', STR_PAD_LEFT); + } + + // used to calculate bitrate below + static $BitrateCache = array(); + + + while (true) { + // breaks out when end-of-file encountered, or invalid data found, + // or MaxFramesToScan frames have been scanned + + fseek($fd, $byteoffset, SEEK_SET); + + // First get substring + $substring = fread($fd, 10); + $substringlength = strlen($substring); + if ($substringlength != 10) { + $ThisFileInfo['error'][] = 'Failed to read 10 bytes at offset '.(ftell($fd) - $substringlength).' (only read '.$substringlength.' bytes)'; + return false; + } + + // Initialise $AACheaderBitstream + $AACheaderBitstream = ''; + + // Loop thru substring chars + for ($i = 0; $i < 10; $i++) { + $AACheaderBitstream .= $decbin[$substring{$i}]; + } + + $bitoffset = 0; + + $synctest = bindec(substr($AACheaderBitstream, $bitoffset, 12)); + + $bitoffset += 12; + if ($synctest != 0x0FFF) { + $ThisFileInfo['error'][] = 'Synch pattern (0x0FFF) not found at offset '.(ftell($fd) - 10).' (found 0x0'.strtoupper(dechex($synctest)).' instead)'; + if ($ThisFileInfo['fileformat'] == 'aac') { + return true; + } + return false; + } + + // Gather info for first frame only - this takes time to do 1000 times! + if ($framenumber > 0) { + + if (!$AACheaderBitstream[$bitoffset]) { + + // MPEG-4 + $bitoffset += 20; + + } else { + + // MPEG-2 + $bitoffset += 18; + + } + + } else { + + $ThisFileInfo['aac']['header_type'] = 'ADTS'; + $ThisFileInfo['aac']['header']['synch'] = $synctest; + $ThisFileInfo['fileformat'] = 'aac'; + $ThisFileInfo['audio']['dataformat'] = 'aac'; + + $ThisFileInfo['aac']['header']['mpeg_version'] = ((substr($AACheaderBitstream, $bitoffset, 1) == '0') ? 4 : 2); + $bitoffset += 1; + $ThisFileInfo['aac']['header']['layer'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2)); + $bitoffset += 2; + if ($ThisFileInfo['aac']['header']['layer'] != 0) { + $ThisFileInfo['error'][] = 'Layer error - expected 0x00, found 0x'.dechex($ThisFileInfo['aac']['header']['layer']).' instead'; + return false; + } + $ThisFileInfo['aac']['header']['crc_present'] = ((substr($AACheaderBitstream, $bitoffset, 1) == '0') ? true : false); + $bitoffset += 1; + $ThisFileInfo['aac']['header']['profile_id'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2)); + $bitoffset += 2; + $ThisFileInfo['aac']['header']['profile_text'] = $this->AACprofileLookup($ThisFileInfo['aac']['header']['profile_id'], $ThisFileInfo['aac']['header']['mpeg_version']); + + $ThisFileInfo['aac']['header']['sample_frequency_index'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4)); + $bitoffset += 4; + $ThisFileInfo['aac']['header']['sample_frequency'] = $this->AACsampleRateLookup($ThisFileInfo['aac']['header']['sample_frequency_index']); + if ($ThisFileInfo['aac']['header']['sample_frequency'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt AAC file: sample_frequency == zero'; + return false; + } + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['aac']['header']['sample_frequency']; + + $ThisFileInfo['aac']['header']['private'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + $ThisFileInfo['aac']['header']['channel_configuration'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 3)); + $bitoffset += 3; + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['aac']['header']['channel_configuration']; + $ThisFileInfo['aac']['header']['original'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + $ThisFileInfo['aac']['header']['home'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + + if ($ThisFileInfo['aac']['header']['mpeg_version'] == 4) { + $ThisFileInfo['aac']['header']['emphasis'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2)); + $bitoffset += 2; + } + + if ($ReturnExtendedInfo) { + + $ThisFileInfo['aac'][$framenumber]['copyright_id_bit'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + $ThisFileInfo['aac'][$framenumber]['copyright_id_start'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1)); + $bitoffset += 1; + + } else { + + $bitoffset += 2; + + } + + } + + $FrameLength = bindec(substr($AACheaderBitstream, $bitoffset, 13)); + + if (!isset($BitrateCache[$FrameLength])) { + $BitrateCache[$FrameLength] = ($ThisFileInfo['aac']['header']['sample_frequency'] / 1024) * $FrameLength * 8; + } + @$ThisFileInfo['aac']['bitrate_distribution'][$BitrateCache[$FrameLength]]++; + + $ThisFileInfo['aac'][$framenumber]['aac_frame_length'] = $FrameLength; + $bitoffset += 13; + $ThisFileInfo['aac'][$framenumber]['adts_buffer_fullness'] = bindec(substr($AACheaderBitstream, $bitoffset, 11)); + $bitoffset += 11; + if ($ThisFileInfo['aac'][$framenumber]['adts_buffer_fullness'] == 0x07FF) { + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + } else { + $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + } + $ThisFileInfo['aac'][$framenumber]['num_raw_data_blocks'] = bindec(substr($AACheaderBitstream, $bitoffset, 2)); + $bitoffset += 2; + + if ($ThisFileInfo['aac']['header']['crc_present']) { + //$ThisFileInfo['aac'][$framenumber]['crc'] = bindec(substr($AACheaderBitstream, $bitoffset, 16)); + $bitoffset += 16; + } + + if (!$ReturnExtendedInfo) { + unset($ThisFileInfo['aac'][$framenumber]); + } + + $byteoffset += $FrameLength; + if ((++$framenumber < $MaxFramesToScan) && (($byteoffset + 10) < $ThisFileInfo['avdataend'])) { + + // keep scanning + + } else { + + $ThisFileInfo['aac']['frames'] = $framenumber; + $ThisFileInfo['playtime_seconds'] = ($ThisFileInfo['avdataend'] / $byteoffset) * (($framenumber * 1024) / $ThisFileInfo['aac']['header']['sample_frequency']); // (1 / % of file scanned) * (samples / (samples/sec)) = seconds + if ($ThisFileInfo['playtime_seconds'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt AAC file: playtime_seconds == zero'; + return false; + } + $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + ksort($ThisFileInfo['aac']['bitrate_distribution']); + + $ThisFileInfo['audio']['encoder_options'] = $ThisFileInfo['aac']['header_type'].' '.$ThisFileInfo['aac']['header']['profile_text']; + + return true; + + } + } + // should never get here. + } + + function AACsampleRateLookup($samplerateid) { + static $AACsampleRateLookup = array(); + if (empty($AACsampleRateLookup)) { + $AACsampleRateLookup[0] = 96000; + $AACsampleRateLookup[1] = 88200; + $AACsampleRateLookup[2] = 64000; + $AACsampleRateLookup[3] = 48000; + $AACsampleRateLookup[4] = 44100; + $AACsampleRateLookup[5] = 32000; + $AACsampleRateLookup[6] = 24000; + $AACsampleRateLookup[7] = 22050; + $AACsampleRateLookup[8] = 16000; + $AACsampleRateLookup[9] = 12000; + $AACsampleRateLookup[10] = 11025; + $AACsampleRateLookup[11] = 8000; + $AACsampleRateLookup[12] = 0; + $AACsampleRateLookup[13] = 0; + $AACsampleRateLookup[14] = 0; + $AACsampleRateLookup[15] = 0; + } + return (isset($AACsampleRateLookup[$samplerateid]) ? $AACsampleRateLookup[$samplerateid] : 'invalid'); + } + + function AACprofileLookup($profileid, $mpegversion) { + static $AACprofileLookup = array(); + if (empty($AACprofileLookup)) { + $AACprofileLookup[2][0] = 'Main profile'; + $AACprofileLookup[2][1] = 'Low Complexity profile (LC)'; + $AACprofileLookup[2][2] = 'Scalable Sample Rate profile (SSR)'; + $AACprofileLookup[2][3] = '(reserved)'; + $AACprofileLookup[4][0] = 'AAC_MAIN'; + $AACprofileLookup[4][1] = 'AAC_LC'; + $AACprofileLookup[4][2] = 'AAC_SSR'; + $AACprofileLookup[4][3] = 'AAC_LTP'; + } + return (isset($AACprofileLookup[$mpegversion][$profileid]) ? $AACprofileLookup[$mpegversion][$profileid] : 'invalid'); + } + + function AACchannelCountCalculate($program_configs) { + $channels = 0; + for ($i = 0; $i < $program_configs['num_front_channel_elements']; $i++) { + $channels++; + if ($program_configs['front_element_is_cpe'][$i]) { + // each front element is channel pair (CPE = Channel Pair Element) + $channels++; + } + } + for ($i = 0; $i < $program_configs['num_side_channel_elements']; $i++) { + $channels++; + if ($program_configs['side_element_is_cpe'][$i]) { + // each side element is channel pair (CPE = Channel Pair Element) + $channels++; + } + } + for ($i = 0; $i < $program_configs['num_back_channel_elements']; $i++) { + $channels++; + if ($program_configs['back_element_is_cpe'][$i]) { + // each back element is channel pair (CPE = Channel Pair Element) + $channels++; + } + } + for ($i = 0; $i < $program_configs['num_lfe_channel_elements']; $i++) { + $channels++; + } + return $channels; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.ac3.php b/modules/id3/getid3/module.audio.ac3.php new file mode 100644 index 00000000..9809a47c --- /dev/null +++ b/modules/id3/getid3/module.audio.ac3.php @@ -0,0 +1,497 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.ac3.php // +// module for analyzing AC-3 (aka Dolby Digital) audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_ac3 +{ + + function getid3_ac3(&$fd, &$ThisFileInfo) { + + ///AH + $ThisFileInfo['ac3']['raw']['bsi'] = array(); + $thisfile_ac3 = &$ThisFileInfo['ac3']; + $thisfile_ac3_raw = &$thisfile_ac3['raw']; + $thisfile_ac3_raw_bsi = &$thisfile_ac3_raw['bsi']; + + + // http://www.atsc.org/standards/a_52a.pdf + + $ThisFileInfo['fileformat'] = 'ac3'; + $ThisFileInfo['audio']['dataformat'] = 'ac3'; + $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + $ThisFileInfo['audio']['lossless'] = false; + + // An AC-3 serial coded audio bit stream is made up of a sequence of synchronization frames + // Each synchronization frame contains 6 coded audio blocks (AB), each of which represent 256 + // new audio samples per channel. A synchronization information (SI) header at the beginning + // of each frame contains information needed to acquire and maintain synchronization. A + // bit stream information (BSI) header follows SI, and contains parameters describing the coded + // audio service. The coded audio blocks may be followed by an auxiliary data (Aux) field. At the + // end of each frame is an error check field that includes a CRC word for error detection. An + // additional CRC word is located in the SI header, the use of which, by a decoder, is optional. + // + // syncinfo() | bsi() | AB0 | AB1 | AB2 | AB3 | AB4 | AB5 | Aux | CRC + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $AC3header['syncinfo'] = fread($fd, 5); + $thisfile_ac3_raw['synchinfo']['synchword'] = substr($AC3header['syncinfo'], 0, 2); + + if ($thisfile_ac3_raw['synchinfo']['synchword'] != "\x0B\x77") { + + $ThisFileInfo['error'][] = 'Expecting "\x0B\x77" at offset '.$ThisFileInfo['avdataoffset'].', found \x'.strtoupper(dechex($AC3header['syncinfo']{0})).'\x'.strtoupper(dechex($AC3header['syncinfo']{1})).' instead'; + unset($thisfile_ac3); + return false; + + } else { + + // syncinfo() { + // syncword 16 + // crc1 16 + // fscod 2 + // frmsizecod 6 + // } /* end of syncinfo */ + + $thisfile_ac3_raw['synchinfo']['crc1'] = getid3_lib::LittleEndian2Int(substr($AC3header['syncinfo'], 2, 2)); + $ac3_synchinfo_fscod_frmsizecod = getid3_lib::LittleEndian2Int(substr($AC3header['syncinfo'], 4, 1)); + $thisfile_ac3_raw['synchinfo']['fscod'] = ($ac3_synchinfo_fscod_frmsizecod & 0xC0) >> 6; + $thisfile_ac3_raw['synchinfo']['frmsizecod'] = ($ac3_synchinfo_fscod_frmsizecod & 0x3F); + + $thisfile_ac3['sample_rate'] = $this->AC3sampleRateCodeLookup($thisfile_ac3_raw['synchinfo']['fscod']); + if ($thisfile_ac3_raw['synchinfo']['fscod'] <= 3) { + $ThisFileInfo['audio']['sample_rate'] = $thisfile_ac3['sample_rate']; + } + + $thisfile_ac3['frame_length'] = $this->AC3frameSizeLookup($thisfile_ac3_raw['synchinfo']['frmsizecod'], $thisfile_ac3_raw['synchinfo']['fscod']); + $thisfile_ac3['bitrate'] = $this->AC3bitrateLookup($thisfile_ac3_raw['synchinfo']['frmsizecod']); + $ThisFileInfo['audio']['bitrate'] = $thisfile_ac3['bitrate']; + + $AC3header['bsi'] = getid3_lib::BigEndian2Bin(fread($fd, 15)); + $ac3_bsi_offset = 0; + + $thisfile_ac3_raw_bsi['bsid'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 5)); + $ac3_bsi_offset += 5; + if ($thisfile_ac3_raw_bsi['bsid'] > 8) { + // Decoders which can decode version 8 will thus be able to decode version numbers less than 8. + // If this standard is extended by the addition of additional elements or features, a value of bsid greater than 8 will be used. + // Decoders built to this version of the standard will not be able to decode versions with bsid greater than 8. + $ThisFileInfo['error'][] = 'Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 8'; + unset($thisfile_ac3); + return false; + } + + $thisfile_ac3_raw_bsi['bsmod'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 3)); + $ac3_bsi_offset += 3; + $thisfile_ac3_raw_bsi['acmod'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 3)); + $ac3_bsi_offset += 3; + + $thisfile_ac3['service_type'] = $this->AC3serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']); + $ac3_coding_mode = $this->AC3audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']); + foreach($ac3_coding_mode as $key => $value) { + $thisfile_ac3[$key] = $value; + } + switch ($thisfile_ac3_raw_bsi['acmod']) { + case 0: + case 1: + $ThisFileInfo['audio']['channelmode'] = 'mono'; + break; + case 3: + case 4: + $ThisFileInfo['audio']['channelmode'] = 'stereo'; + break; + default: + $ThisFileInfo['audio']['channelmode'] = 'surround'; + break; + } + $ThisFileInfo['audio']['channels'] = $thisfile_ac3['num_channels']; + + if ($thisfile_ac3_raw_bsi['acmod'] & 0x01) { + // If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream. + $thisfile_ac3_raw_bsi['cmixlev'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 2)); + $ac3_bsi_offset += 2; + $thisfile_ac3['center_mix_level'] = $this->AC3centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']); + } + + if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) { + // If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream. + $thisfile_ac3_raw_bsi['surmixlev'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 2)); + $ac3_bsi_offset += 2; + $thisfile_ac3['surround_mix_level'] = $this->AC3surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']); + } + + if ($thisfile_ac3_raw_bsi['acmod'] == 0x02) { + // When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround. + $thisfile_ac3_raw_bsi['dsurmod'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 2)); + $ac3_bsi_offset += 2; + $thisfile_ac3['dolby_surround_mode'] = $this->AC3dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']); + } + + $thisfile_ac3_raw_bsi['lfeon'] = (bool) bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 1)); + $ac3_bsi_offset += 1; + $thisfile_ac3['lfe_enabled'] = $thisfile_ac3_raw_bsi['lfeon']; + if ($thisfile_ac3_raw_bsi['lfeon']) { + //$ThisFileInfo['audio']['channels']++; + $ThisFileInfo['audio']['channels'] .= '.1'; + } + + $thisfile_ac3['channels_enabled'] = $this->AC3channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['lfeon']); + + // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1–31. + // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent. + $thisfile_ac3_raw_bsi['dialnorm'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 5)); + $ac3_bsi_offset += 5; + $thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB'; + + $thisfile_ac3_raw_bsi['compre_flag'] = (bool) bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 1)); + $ac3_bsi_offset += 1; + if ($thisfile_ac3_raw_bsi['compre_flag']) { + $thisfile_ac3_raw_bsi['compr'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 8)); + $ac3_bsi_offset += 8; + $thisfile_ac3['heavy_compression'] = $this->AC3heavyCompression($thisfile_ac3_raw_bsi['compr']); + } + + $thisfile_ac3_raw_bsi['langcode_flag'] = (bool) bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 1)); + $ac3_bsi_offset += 1; + if ($thisfile_ac3_raw_bsi['langcode_flag']) { + $thisfile_ac3_raw_bsi['langcod'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 8)); + $ac3_bsi_offset += 8; + } + + $thisfile_ac3_raw_bsi['audprodie'] = (bool) bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 1)); + $ac3_bsi_offset += 1; + if ($thisfile_ac3_raw_bsi['audprodie']) { + $thisfile_ac3_raw_bsi['mixlevel'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 5)); + $ac3_bsi_offset += 5; + $thisfile_ac3_raw_bsi['roomtyp'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 2)); + $ac3_bsi_offset += 2; + + $thisfile_ac3['mixing_level'] = (80 + $thisfile_ac3_raw_bsi['mixlevel']).'dB'; + $thisfile_ac3['room_type'] = $this->AC3roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']); + } + + if ($thisfile_ac3_raw_bsi['acmod'] == 0x00) { + // If acmod is 0, then two completely independent program channels (dual mono) + // are encoded into the bit stream, and are referenced as Ch1, Ch2. In this case, + // a number of additional items are present in BSI or audblk to fully describe Ch2. + + + // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1–31. + // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent. + $thisfile_ac3_raw_bsi['dialnorm2'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 5)); + $ac3_bsi_offset += 5; + $thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB'; + + $thisfile_ac3_raw_bsi['compre_flag2'] = (bool) bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 1)); + $ac3_bsi_offset += 1; + if ($thisfile_ac3_raw_bsi['compre_flag2']) { + $thisfile_ac3_raw_bsi['compr2'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 8)); + $ac3_bsi_offset += 8; + $thisfile_ac3['heavy_compression2'] = $this->AC3heavyCompression($thisfile_ac3_raw_bsi['compr2']); + } + + $thisfile_ac3_raw_bsi['langcode_flag2'] = (bool) bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 1)); + $ac3_bsi_offset += 1; + if ($thisfile_ac3_raw_bsi['langcode_flag2']) { + $thisfile_ac3_raw_bsi['langcod2'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 8)); + $ac3_bsi_offset += 8; + } + + $thisfile_ac3_raw_bsi['audprodie2'] = (bool) bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 1)); + $ac3_bsi_offset += 1; + if ($thisfile_ac3_raw_bsi['audprodie2']) { + $thisfile_ac3_raw_bsi['mixlevel2'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 5)); + $ac3_bsi_offset += 5; + $thisfile_ac3_raw_bsi['roomtyp2'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 2)); + $ac3_bsi_offset += 2; + + $thisfile_ac3['mixing_level2'] = (80 + $thisfile_ac3_raw_bsi['mixlevel2']).'dB'; + $thisfile_ac3['room_type2'] = $this->AC3roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']); + } + + } + + $thisfile_ac3_raw_bsi['copyright'] = (bool) bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 1)); + $ac3_bsi_offset += 1; + + $thisfile_ac3_raw_bsi['original'] = (bool) bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 1)); + $ac3_bsi_offset += 1; + + $thisfile_ac3_raw_bsi['timecode1_flag'] = (bool) bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 1)); + $ac3_bsi_offset += 1; + if ($thisfile_ac3_raw_bsi['timecode1_flag']) { + $thisfile_ac3_raw_bsi['timecode1'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 14)); + $ac3_bsi_offset += 14; + } + + $thisfile_ac3_raw_bsi['timecode2_flag'] = (bool) bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 1)); + $ac3_bsi_offset += 1; + if ($thisfile_ac3_raw_bsi['timecode2_flag']) { + $thisfile_ac3_raw_bsi['timecode2'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 14)); + $ac3_bsi_offset += 14; + } + + $thisfile_ac3_raw_bsi['addbsi_flag'] = (bool) bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 1)); + $ac3_bsi_offset += 1; + if ($thisfile_ac3_raw_bsi['addbsi_flag']) { + $thisfile_ac3_raw_bsi['addbsi_length'] = bindec(substr($AC3header['bsi'], $ac3_bsi_offset, 6)); + $ac3_bsi_offset += 6; + + $AC3header['bsi'] .= getid3_lib::BigEndian2Bin(fread($fd, $thisfile_ac3_raw_bsi['addbsi_length'])); + + $thisfile_ac3_raw_bsi['addbsi_data'] = substr($AC3header['bsi'], $ac3_bsi_offset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8); + $ac3_bsi_offset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8; + } + + } + + return true; + } + + + function AC3sampleRateCodeLookup($fscod) { + static $AC3sampleRateCodeLookup = array( + 0 => 48000, + 1 => 44100, + 2 => 32000, + 3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute. + ); + return (isset($AC3sampleRateCodeLookup[$fscod]) ? $AC3sampleRateCodeLookup[$fscod] : false); + } + + function AC3serviceTypeLookup($bsmod, $acmod) { + static $AC3serviceTypeLookup = array(); + if (empty($AC3serviceTypeLookup)) { + for ($i = 0; $i <= 7; $i++) { + $AC3serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)'; + $AC3serviceTypeLookup[1][$i] = 'main audio service: music and effects (ME)'; + $AC3serviceTypeLookup[2][$i] = 'associated service: visually impaired (VI)'; + $AC3serviceTypeLookup[3][$i] = 'associated service: hearing impaired (HI)'; + $AC3serviceTypeLookup[4][$i] = 'associated service: dialogue (D)'; + $AC3serviceTypeLookup[5][$i] = 'associated service: commentary (C)'; + $AC3serviceTypeLookup[6][$i] = 'associated service: emergency (E)'; + } + + $AC3serviceTypeLookup[7][1] = 'associated service: voice over (VO)'; + for ($i = 2; $i <= 7; $i++) { + $AC3serviceTypeLookup[7][$i] = 'main audio service: karaoke'; + } + } + return (isset($AC3serviceTypeLookup[$bsmod][$acmod]) ? $AC3serviceTypeLookup[$bsmod][$acmod] : false); + } + + function AC3audioCodingModeLookup($acmod) { + static $AC3audioCodingModeLookup = array(); + if (empty($AC3audioCodingModeLookup)) { + // array(channel configuration, # channels (not incl LFE), channel order) + $AC3audioCodingModeLookup = array ( + 0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'), + 1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'), + 2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'), + 3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'), + 4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'), + 5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'), + 6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'), + 7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR') + ); + } + return (isset($AC3audioCodingModeLookup[$acmod]) ? $AC3audioCodingModeLookup[$acmod] : false); + } + + function AC3centerMixLevelLookup($cmixlev) { + static $AC3centerMixLevelLookup; + if (empty($AC3centerMixLevelLookup)) { + $AC3centerMixLevelLookup = array( + 0 => pow(2, -3.0 / 6), // 0.707 (–3.0 dB) + 1 => pow(2, -4.5 / 6), // 0.595 (–4.5 dB) + 2 => pow(2, -6.0 / 6), // 0.500 (–6.0 dB) + 3 => 'reserved' + ); + } + return (isset($AC3centerMixLevelLookup[$cmixlev]) ? $AC3centerMixLevelLookup[$cmixlev] : false); + } + + function AC3surroundMixLevelLookup($surmixlev) { + static $AC3surroundMixLevelLookup; + if (empty($AC3surroundMixLevelLookup)) { + $AC3surroundMixLevelLookup = array( + 0 => pow(2, -3.0 / 6), + 1 => pow(2, -6.0 / 6), + 2 => 0, + 3 => 'reserved' + ); + } + return (isset($AC3surroundMixLevelLookup[$surmixlev]) ? $AC3surroundMixLevelLookup[$surmixlev] : false); + } + + function AC3dolbySurroundModeLookup($dsurmod) { + static $AC3dolbySurroundModeLookup = array( + 0 => 'not indicated', + 1 => 'Not Dolby Surround encoded', + 2 => 'Dolby Surround encoded', + 3 => 'reserved' + ); + return (isset($AC3dolbySurroundModeLookup[$dsurmod]) ? $AC3dolbySurroundModeLookup[$dsurmod] : false); + } + + function AC3channelsEnabledLookup($acmod, $lfeon) { + $AC3channelsEnabledLookup = array( + 'ch1'=>(bool) ($acmod == 0), + 'ch2'=>(bool) ($acmod == 0), + 'left'=>(bool) ($acmod > 1), + 'right'=>(bool) ($acmod > 1), + 'center'=>(bool) ($acmod & 0x01), + 'surround_mono'=>false, + 'surround_left'=>false, + 'surround_right'=>false, + 'lfe'=>$lfeon); + switch ($acmod) { + case 4: + case 5: + $AC3channelsEnabledLookup['surround_mono'] = true; + break; + case 6: + case 7: + $AC3channelsEnabledLookup['surround_left'] = true; + $AC3channelsEnabledLookup['surround_right'] = true; + break; + } + return $AC3channelsEnabledLookup; + } + + function AC3heavyCompression($compre) { + // The first four bits indicate gain changes in 6.02dB increments which can be + // implemented with an arithmetic shift operation. The following four bits + // indicate linear gain changes, and require a 5-bit multiply. + // We will represent the two 4-bit fields of compr as follows: + // X0 X1 X2 X3 . Y4 Y5 Y6 Y7 + // The meaning of the X values is most simply described by considering X to represent a 4-bit + // signed integer with values from –8 to +7. The gain indicated by X is then (X + 1) * 6.02 dB. The + // following table shows this in detail. + + // Meaning of 4 msb of compr + // 7 +48.16 dB + // 6 +42.14 dB + // 5 +36.12 dB + // 4 +30.10 dB + // 3 +24.08 dB + // 2 +18.06 dB + // 1 +12.04 dB + // 0 +6.02 dB + // -1 0 dB + // -2 –6.02 dB + // -3 –12.04 dB + // -4 –18.06 dB + // -5 –24.08 dB + // -6 –30.10 dB + // -7 –36.12 dB + // -8 –42.14 dB + + $fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT); + if ($fourbit{0} == '1') { + $log_gain = -8 + bindec(substr($fourbit, 1)); + } else { + $log_gain = bindec(substr($fourbit, 1)); + } + $log_gain = ($log_gain + 1) * getid3_lib::RGADamplitude2dB(2); + + // The value of Y is a linear representation of a gain change of up to –6 dB. Y is considered to + // be an unsigned fractional integer, with a leading value of 1, or: 0.1 Y4 Y5 Y6 Y7 (base 2). Y can + // represent values between 0.111112 (or 31/32) and 0.100002 (or 1/2). Thus, Y can represent gain + // changes from –0.28 dB to –6.02 dB. + + $lin_gain = (16 + ($compre & 0x0F)) / 32; + + // The combination of X and Y values allows compr to indicate gain changes from + // 48.16 – 0.28 = +47.89 dB, to + // –42.14 – 6.02 = –48.16 dB. + + return $log_gain - $lin_gain; + } + + function AC3roomTypeLookup($roomtyp) { + static $AC3roomTypeLookup = array( + 0 => 'not indicated', + 1 => 'large room, X curve monitor', + 2 => 'small room, flat monitor', + 3 => 'reserved' + ); + return (isset($AC3roomTypeLookup[$roomtyp]) ? $AC3roomTypeLookup[$roomtyp] : false); + } + + function AC3frameSizeLookup($frmsizecod, $fscod) { + $padding = (bool) ($frmsizecod % 2); + $framesizeid = floor($frmsizecod / 2); + + static $AC3frameSizeLookup = array(); + if (empty($AC3frameSizeLookup)) { + $AC3frameSizeLookup = array ( + 0 => array(128, 138, 192), + 1 => array(40, 160, 174, 240), + 2 => array(48, 192, 208, 288), + 3 => array(56, 224, 242, 336), + 4 => array(64, 256, 278, 384), + 5 => array(80, 320, 348, 480), + 6 => array(96, 384, 416, 576), + 7 => array(112, 448, 486, 672), + 8 => array(128, 512, 556, 768), + 9 => array(160, 640, 696, 960), + 10 => array(192, 768, 834, 1152), + 11 => array(224, 896, 974, 1344), + 12 => array(256, 1024, 1114, 1536), + 13 => array(320, 1280, 1392, 1920), + 14 => array(384, 1536, 1670, 2304), + 15 => array(448, 1792, 1950, 2688), + 16 => array(512, 2048, 2228, 3072), + 17 => array(576, 2304, 2506, 3456), + 18 => array(640, 2560, 2786, 3840) + ); + } + if (($fscod == 1) && $padding) { + // frame lengths are padded by 1 word (16 bits) at 44100 + $AC3frameSizeLookup[$frmsizecod] += 2; + } + return (isset($AC3frameSizeLookup[$framesizeid][$fscod]) ? $AC3frameSizeLookup[$framesizeid][$fscod] : false); + } + + function AC3bitrateLookup($frmsizecod) { + $framesizeid = floor($frmsizecod / 2); + + static $AC3bitrateLookup = array( + 0 => 32000, + 1 => 40000, + 2 => 48000, + 3 => 56000, + 4 => 64000, + 5 => 80000, + 6 => 96000, + 7 => 112000, + 8 => 128000, + 9 => 160000, + 10 => 192000, + 11 => 224000, + 12 => 256000, + 13 => 320000, + 14 => 384000, + 15 => 448000, + 16 => 512000, + 17 => 576000, + 18 => 640000 + ); + return (isset($AC3bitrateLookup[$framesizeid]) ? $AC3bitrateLookup[$framesizeid] : false); + } + + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.au.php b/modules/id3/getid3/module.audio.au.php new file mode 100644 index 00000000..afbc75d6 --- /dev/null +++ b/modules/id3/getid3/module.audio.au.php @@ -0,0 +1,163 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.au.php // +// module for analyzing AU files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_au +{ + + function getid3_au(&$fd, &$ThisFileInfo) { + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $AUheader = fread($fd, 8); + + if (substr($AUheader, 0, 4) != '.snd') { + $ThisFileInfo['error'][] = 'Expecting ".snd" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($AUheader, 0, 4).'"'; + return false; + } + + // shortcut + $ThisFileInfo['au'] = array(); + $thisfile_au = &$ThisFileInfo['au']; + + $ThisFileInfo['fileformat'] = 'au'; + $ThisFileInfo['audio']['dataformat'] = 'au'; + $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + $thisfile_au['encoding'] = 'ISO-8859-1'; + + $thisfile_au['header_length'] = getid3_lib::BigEndian2Int(substr($AUheader, 4, 4)); + $AUheader .= fread($fd, $thisfile_au['header_length'] - 8); + $ThisFileInfo['avdataoffset'] += $thisfile_au['header_length']; + + $thisfile_au['data_size'] = getid3_lib::BigEndian2Int(substr($AUheader, 8, 4)); + $thisfile_au['data_format_id'] = getid3_lib::BigEndian2Int(substr($AUheader, 12, 4)); + $thisfile_au['sample_rate'] = getid3_lib::BigEndian2Int(substr($AUheader, 16, 4)); + $thisfile_au['channels'] = getid3_lib::BigEndian2Int(substr($AUheader, 20, 4)); + $thisfile_au['comments']['comment'][] = trim(substr($AUheader, 24)); + + $thisfile_au['data_format'] = $this->AUdataFormatNameLookup($thisfile_au['data_format_id']); + $thisfile_au['used_bits_per_sample'] = $this->AUdataFormatUsedBitsPerSampleLookup($thisfile_au['data_format_id']); + if ($thisfile_au['bits_per_sample'] = $this->AUdataFormatBitsPerSampleLookup($thisfile_au['data_format_id'])) { + $ThisFileInfo['audio']['bits_per_sample'] = $thisfile_au['bits_per_sample']; + } else { + unset($thisfile_au['bits_per_sample']); + } + + $ThisFileInfo['audio']['sample_rate'] = $thisfile_au['sample_rate']; + $ThisFileInfo['audio']['channels'] = $thisfile_au['channels']; + + if (($ThisFileInfo['avdataoffset'] + $thisfile_au['data_size']) > $ThisFileInfo['avdataend']) { + $ThisFileInfo['warning'][] = 'Possible truncated file - expecting "'.$thisfile_au['data_size'].'" bytes of audio data, only found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' bytes"'; + } + + $ThisFileInfo['playtime_seconds'] = $thisfile_au['data_size'] / ($thisfile_au['sample_rate'] * $thisfile_au['channels'] * ($thisfile_au['used_bits_per_sample'] / 8)); + $ThisFileInfo['audio']['bitrate'] = ($thisfile_au['data_size'] * 8) / $ThisFileInfo['playtime_seconds']; + + return true; + } + + function AUdataFormatNameLookup($id) { + static $AUdataFormatNameLookup = array( + 0 => 'unspecified format', + 1 => '8-bit mu-law', + 2 => '8-bit linear', + 3 => '16-bit linear', + 4 => '24-bit linear', + 5 => '32-bit linear', + 6 => 'floating-point', + 7 => 'double-precision float', + 8 => 'fragmented sampled data', + 9 => 'SUN_FORMAT_NESTED', + 10 => 'DSP program', + 11 => '8-bit fixed-point', + 12 => '16-bit fixed-point', + 13 => '24-bit fixed-point', + 14 => '32-bit fixed-point', + + 16 => 'non-audio display data', + 17 => 'SND_FORMAT_MULAW_SQUELCH', + 18 => '16-bit linear with emphasis', + 19 => '16-bit linear with compression', + 20 => '16-bit linear with emphasis + compression', + 21 => 'Music Kit DSP commands', + 22 => 'SND_FORMAT_DSP_COMMANDS_SAMPLES', + 23 => 'CCITT g.721 4-bit ADPCM', + 24 => 'CCITT g.722 ADPCM', + 25 => 'CCITT g.723 3-bit ADPCM', + 26 => 'CCITT g.723 5-bit ADPCM', + 27 => 'A-Law 8-bit' + ); + return (isset($AUdataFormatNameLookup[$id]) ? $AUdataFormatNameLookup[$id] : false); + } + + function AUdataFormatBitsPerSampleLookup($id) { + static $AUdataFormatBitsPerSampleLookup = array( + 1 => 8, + 2 => 8, + 3 => 16, + 4 => 24, + 5 => 32, + 6 => 32, + 7 => 64, + + 11 => 8, + 12 => 16, + 13 => 24, + 14 => 32, + + 18 => 16, + 19 => 16, + 20 => 16, + + 23 => 16, + + 25 => 16, + 26 => 16, + 27 => 8 + ); + return (isset($AUdataFormatBitsPerSampleLookup[$id]) ? $AUdataFormatBitsPerSampleLookup[$id] : false); + } + + function AUdataFormatUsedBitsPerSampleLookup($id) { + static $AUdataFormatUsedBitsPerSampleLookup = array( + 1 => 8, + 2 => 8, + 3 => 16, + 4 => 24, + 5 => 32, + 6 => 32, + 7 => 64, + + 11 => 8, + 12 => 16, + 13 => 24, + 14 => 32, + + 18 => 16, + 19 => 16, + 20 => 16, + + 23 => 4, + + 25 => 3, + 26 => 5, + 27 => 8, + ); + return (isset($AUdataFormatUsedBitsPerSampleLookup[$id]) ? $AUdataFormatUsedBitsPerSampleLookup[$id] : false); + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.avr.php b/modules/id3/getid3/module.audio.avr.php new file mode 100644 index 00000000..60994397 --- /dev/null +++ b/modules/id3/getid3/module.audio.avr.php @@ -0,0 +1,125 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.avr.php // +// module for analyzing AVR Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_avr +{ + + function getid3_avr(&$fd, &$ThisFileInfo) { + + // http://cui.unige.ch/OSG/info/AudioFormats/ap11.html + // http://www.btinternet.com/~AnthonyJ/Atari/programming/avr_format.html + // offset type length name comments + // --------------------------------------------------------------------- + // 0 char 4 ID format ID == "2BIT" + // 4 char 8 name sample name (unused space filled with 0) + // 12 short 1 mono/stereo 0=mono, -1 (0xFFFF)=stereo + // With stereo, samples are alternated, + // the first voice is the left : + // (LRLRLRLRLRLRLRLRLR...) + // 14 short 1 resolution 8, 12 or 16 (bits) + // 16 short 1 signed or not 0=unsigned, -1 (0xFFFF)=signed + // 18 short 1 loop or not 0=no loop, -1 (0xFFFF)=loop on + // 20 short 1 MIDI note 0xFFnn, where 0 <= nn <= 127 + // 0xFFFF means "no MIDI note defined" + // 22 byte 1 Replay speed Frequence in the Replay software + // 0=5.485 Khz, 1=8.084 Khz, 2=10.971 Khz, + // 3=16.168 Khz, 4=21.942 Khz, 5=32.336 Khz + // 6=43.885 Khz, 7=47.261 Khz + // -1 (0xFF)=no defined Frequence + // 23 byte 3 sample rate in Hertz + // 26 long 1 size in bytes (2 * bytes in stereo) + // 30 long 1 loop begin 0 for no loop + // 34 long 1 loop size equal to 'size' for no loop + // 38 short 2 Reserved, MIDI keyboard split */ + // 40 short 2 Reserved, sample compression */ + // 42 short 2 Reserved */ + // 44 char 20; Additional filename space, used if (name[7] != 0) + // 64 byte 64 user data + // 128 bytes ? sample data (12 bits samples are coded on 16 bits: + // 0000 xxxx xxxx xxxx) + // --------------------------------------------------------------------- + + // Note that all values are in motorola (big-endian) format, and that long is + // assumed to be 4 bytes, and short 2 bytes. + // When reading the samples, you should handle both signed and unsigned data, + // and be prepared to convert 16->8 bit, or mono->stereo if needed. To convert + // 8-bit data between signed/unsigned just add 127 to the sample values. + // Simularly for 16-bit data you should add 32769 + + $ThisFileInfo['fileformat'] = 'avr'; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $AVRheader = fread($fd, 128); + + $ThisFileInfo['avr']['raw']['magic'] = substr($AVRheader, 0, 4); + if ($ThisFileInfo['avr']['raw']['magic'] != '2BIT') { + $ThisFileInfo['error'][] = 'Expecting "2BIT" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$ThisFileInfo['avr']['raw']['magic'].'"'; + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['avr']); + return false; + } + $ThisFileInfo['avdataoffset'] += 128; + + $ThisFileInfo['avr']['sample_name'] = rtrim(substr($AVRheader, 4, 8)); + $ThisFileInfo['avr']['raw']['mono'] = getid3_lib::BigEndian2Int(substr($AVRheader, 12, 2)); + $ThisFileInfo['avr']['bits_per_sample'] = getid3_lib::BigEndian2Int(substr($AVRheader, 14, 2)); + $ThisFileInfo['avr']['raw']['signed'] = getid3_lib::BigEndian2Int(substr($AVRheader, 16, 2)); + $ThisFileInfo['avr']['raw']['loop'] = getid3_lib::BigEndian2Int(substr($AVRheader, 18, 2)); + $ThisFileInfo['avr']['raw']['midi'] = getid3_lib::BigEndian2Int(substr($AVRheader, 20, 2)); + $ThisFileInfo['avr']['raw']['replay_freq'] = getid3_lib::BigEndian2Int(substr($AVRheader, 22, 1)); + $ThisFileInfo['avr']['sample_rate'] = getid3_lib::BigEndian2Int(substr($AVRheader, 23, 3)); + $ThisFileInfo['avr']['sample_length'] = getid3_lib::BigEndian2Int(substr($AVRheader, 26, 4)); + $ThisFileInfo['avr']['loop_start'] = getid3_lib::BigEndian2Int(substr($AVRheader, 30, 4)); + $ThisFileInfo['avr']['loop_end'] = getid3_lib::BigEndian2Int(substr($AVRheader, 34, 4)); + $ThisFileInfo['avr']['midi_split'] = getid3_lib::BigEndian2Int(substr($AVRheader, 38, 2)); + $ThisFileInfo['avr']['sample_compression'] = getid3_lib::BigEndian2Int(substr($AVRheader, 40, 2)); + $ThisFileInfo['avr']['reserved'] = getid3_lib::BigEndian2Int(substr($AVRheader, 42, 2)); + $ThisFileInfo['avr']['sample_name_extra'] = rtrim(substr($AVRheader, 44, 20)); + $ThisFileInfo['avr']['comment'] = rtrim(substr($AVRheader, 64, 64)); + + $ThisFileInfo['avr']['flags']['stereo'] = (($ThisFileInfo['avr']['raw']['mono'] == 0) ? false : true); + $ThisFileInfo['avr']['flags']['signed'] = (($ThisFileInfo['avr']['raw']['signed'] == 0) ? false : true); + $ThisFileInfo['avr']['flags']['loop'] = (($ThisFileInfo['avr']['raw']['loop'] == 0) ? false : true); + + $ThisFileInfo['avr']['midi_notes'] = array(); + if (($ThisFileInfo['avr']['raw']['midi'] & 0xFF00) != 0xFF00) { + $ThisFileInfo['avr']['midi_notes'][] = ($ThisFileInfo['avr']['raw']['midi'] & 0xFF00) >> 8; + } + if (($ThisFileInfo['avr']['raw']['midi'] & 0x00FF) != 0x00FF) { + $ThisFileInfo['avr']['midi_notes'][] = ($ThisFileInfo['avr']['raw']['midi'] & 0x00FF); + } + + if (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) != ($ThisFileInfo['avr']['sample_length'] * (($ThisFileInfo['avr']['bits_per_sample'] == 8) ? 1 : 2))) { + $ThisFileInfo['warning'][] = 'Probable truncated file: expecting '.($ThisFileInfo['avr']['sample_length'] * (($ThisFileInfo['avr']['bits_per_sample'] == 8) ? 1 : 2)).' bytes of audio data, found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']); + } + + $ThisFileInfo['audio']['dataformat'] = 'avr'; + $ThisFileInfo['audio']['lossless'] = true; + $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['avr']['bits_per_sample']; + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['avr']['sample_rate']; + $ThisFileInfo['audio']['channels'] = ($ThisFileInfo['avr']['flags']['stereo'] ? 2 : 1); + $ThisFileInfo['playtime_seconds'] = ($ThisFileInfo['avr']['sample_length'] / $ThisFileInfo['audio']['channels']) / $ThisFileInfo['avr']['sample_rate']; + $ThisFileInfo['audio']['bitrate'] = ($ThisFileInfo['avr']['sample_length'] * (($ThisFileInfo['avr']['bits_per_sample'] == 8) ? 8 : 16)) / $ThisFileInfo['playtime_seconds']; + + + return true; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.bonk.php b/modules/id3/getid3/module.audio.bonk.php new file mode 100644 index 00000000..218aa328 --- /dev/null +++ b/modules/id3/getid3/module.audio.bonk.php @@ -0,0 +1,213 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.la.php // +// module for analyzing BONK audio files // +// dependencies: module.tag.id3v2.php (optional) // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_bonk +{ + function getid3_bonk(&$fd, &$ThisFileInfo) { + + // shortcut + $ThisFileInfo['bonk'] = array(); + $thisfile_bonk = &$ThisFileInfo['bonk']; + + $thisfile_bonk['dataoffset'] = $ThisFileInfo['avdataoffset']; + $thisfile_bonk['dataend'] = $ThisFileInfo['avdataend']; + + // scan-from-end method, for v0.6 and higher + fseek($fd, $thisfile_bonk['dataend'] - 8, SEEK_SET); + $PossibleBonkTag = fread($fd, 8); + while ($this->BonkIsValidTagName(substr($PossibleBonkTag, 4, 4), true)) { + $BonkTagSize = getid3_lib::LittleEndian2Int(substr($PossibleBonkTag, 0, 4)); + fseek($fd, 0 - $BonkTagSize, SEEK_CUR); + $BonkTagOffset = ftell($fd); + $TagHeaderTest = fread($fd, 5); + if (($TagHeaderTest{0} != "\x00") || (substr($PossibleBonkTag, 4, 4) != strtolower(substr($PossibleBonkTag, 4, 4)))) { + $ThisFileInfo['error'][] = 'Expecting "Ø'.strtoupper(substr($PossibleBonkTag, 4, 4)).'" at offset '.$BonkTagOffset.', found "'.$TagHeaderTest.'"'; + return false; + } + $BonkTagName = substr($TagHeaderTest, 1, 4); + + $thisfile_bonk[$BonkTagName]['size'] = $BonkTagSize; + $thisfile_bonk[$BonkTagName]['offset'] = $BonkTagOffset; + $this->HandleBonkTags($fd, $BonkTagName, $ThisFileInfo); + $NextTagEndOffset = $BonkTagOffset - 8; + if ($NextTagEndOffset < $thisfile_bonk['dataoffset']) { + if (empty($ThisFileInfo['audio']['encoder'])) { + $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.9+'; + } + return true; + } + fseek($fd, $NextTagEndOffset, SEEK_SET); + $PossibleBonkTag = fread($fd, 8); + } + + // seek-from-beginning method for v0.4 and v0.5 + if (empty($thisfile_bonk['BONK'])) { + fseek($fd, $thisfile_bonk['dataoffset'], SEEK_SET); + do { + $TagHeaderTest = fread($fd, 5); + switch ($TagHeaderTest) { + case "\x00".'BONK': + if (empty($ThisFileInfo['audio']['encoder'])) { + $ThisFileInfo['audio']['encoder'] = 'BONK v0.4'; + } + break; + + case "\x00".'INFO': + $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.5'; + break; + + default: + break 2; + } + $BonkTagName = substr($TagHeaderTest, 1, 4); + $thisfile_bonk[$BonkTagName]['size'] = $thisfile_bonk['dataend'] - $thisfile_bonk['dataoffset']; + $thisfile_bonk[$BonkTagName]['offset'] = $thisfile_bonk['dataoffset']; + $this->HandleBonkTags($fd, $BonkTagName, $ThisFileInfo); + + } while (true); + } + + // parse META block for v0.6 - v0.8 + if (empty($thisfile_bonk['INFO']) && isset($thisfile_bonk['META']['tags']['info'])) { + fseek($fd, $thisfile_bonk['META']['tags']['info'], SEEK_SET); + $TagHeaderTest = fread($fd, 5); + if ($TagHeaderTest == "\x00".'INFO') { + $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.6 - v0.8'; + + $BonkTagName = substr($TagHeaderTest, 1, 4); + $thisfile_bonk[$BonkTagName]['size'] = $thisfile_bonk['dataend'] - $thisfile_bonk['dataoffset']; + $thisfile_bonk[$BonkTagName]['offset'] = $thisfile_bonk['dataoffset']; + $this->HandleBonkTags($fd, $BonkTagName, $ThisFileInfo); + } + } + + if (empty($ThisFileInfo['audio']['encoder'])) { + $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.9+'; + } + if (empty($thisfile_bonk['BONK'])) { + unset($ThisFileInfo['bonk']); + } + return true; + + } + + function HandleBonkTags(&$fd, &$BonkTagName, &$ThisFileInfo) { + + switch ($BonkTagName) { + case 'BONK': + // shortcut + $thisfile_bonk_BONK = &$ThisFileInfo['bonk']['BONK']; + + $BonkData = "\x00".'BONK'.fread($fd, 17); + $thisfile_bonk_BONK['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1)); + $thisfile_bonk_BONK['number_samples'] = getid3_lib::LittleEndian2Int(substr($BonkData, 6, 4)); + $thisfile_bonk_BONK['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BonkData, 10, 4)); + + $thisfile_bonk_BONK['channels'] = getid3_lib::LittleEndian2Int(substr($BonkData, 14, 1)); + $thisfile_bonk_BONK['lossless'] = (bool) getid3_lib::LittleEndian2Int(substr($BonkData, 15, 1)); + $thisfile_bonk_BONK['joint_stereo'] = (bool) getid3_lib::LittleEndian2Int(substr($BonkData, 16, 1)); + $thisfile_bonk_BONK['number_taps'] = getid3_lib::LittleEndian2Int(substr($BonkData, 17, 2)); + $thisfile_bonk_BONK['downsampling_ratio'] = getid3_lib::LittleEndian2Int(substr($BonkData, 19, 1)); + $thisfile_bonk_BONK['samples_per_packet'] = getid3_lib::LittleEndian2Int(substr($BonkData, 20, 2)); + + $ThisFileInfo['avdataoffset'] = $thisfile_bonk_BONK['offset'] + 5 + 17; + $ThisFileInfo['avdataend'] = $thisfile_bonk_BONK['offset'] + $thisfile_bonk_BONK['size']; + + $ThisFileInfo['fileformat'] = 'bonk'; + $ThisFileInfo['audio']['dataformat'] = 'bonk'; + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; // assumed + $ThisFileInfo['audio']['channels'] = $thisfile_bonk_BONK['channels']; + $ThisFileInfo['audio']['sample_rate'] = $thisfile_bonk_BONK['sample_rate']; + $ThisFileInfo['audio']['channelmode'] = ($thisfile_bonk_BONK['joint_stereo'] ? 'joint stereo' : 'stereo'); + $ThisFileInfo['audio']['lossless'] = $thisfile_bonk_BONK['lossless']; + $ThisFileInfo['audio']['codec'] = 'bonk'; + + $ThisFileInfo['playtime_seconds'] = $thisfile_bonk_BONK['number_samples'] / ($thisfile_bonk_BONK['sample_rate'] * $thisfile_bonk_BONK['channels']); + if ($ThisFileInfo['playtime_seconds'] > 0) { + $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['bonk']['dataend'] - $ThisFileInfo['bonk']['dataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + } + break; + + case 'INFO': + // shortcut + $thisfile_bonk_INFO = &$ThisFileInfo['bonk']['INFO']; + + $thisfile_bonk_INFO['version'] = getid3_lib::LittleEndian2Int(fread($fd, 1)); + $thisfile_bonk_INFO['entries_count'] = 0; + $NextInfoDataPair = fread($fd, 5); + if (!$this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) { + while (!feof($fd)) { + //$CurrentSeekInfo['offset'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 0, 4)); + //$CurrentSeekInfo['nextbit'] = getid3_lib::LittleEndian2Int(substr($NextInfoDataPair, 4, 1)); + //$thisfile_bonk_INFO[] = $CurrentSeekInfo; + + $NextInfoDataPair = fread($fd, 5); + if ($this->BonkIsValidTagName(substr($NextInfoDataPair, 1, 4))) { + fseek($fd, -5, SEEK_CUR); + break; + } + $thisfile_bonk_INFO['entries_count']++; + } + } + break; + + case 'META': + $BonkData = "\x00".'META'.fread($fd, $ThisFileInfo['bonk']['META']['size'] - 5); + $ThisFileInfo['bonk']['META']['version'] = getid3_lib::LittleEndian2Int(substr($BonkData, 5, 1)); + + $MetaTagEntries = floor(((strlen($BonkData) - 8) - 6) / 8); // BonkData - xxxxmeta - ØMETA + $offset = 6; + for ($i = 0; $i < $MetaTagEntries; $i++) { + $MetaEntryTagName = substr($BonkData, $offset, 4); + $offset += 4; + $MetaEntryTagOffset = getid3_lib::LittleEndian2Int(substr($BonkData, $offset, 4)); + $offset += 4; + $ThisFileInfo['bonk']['META']['tags'][$MetaEntryTagName] = $MetaEntryTagOffset; + } + break; + + case ' ID3': + $ThisFileInfo['audio']['encoder'] = 'Extended BONK v0.9+'; + + // ID3v2 checking is optional + if (class_exists('getid3_id3v2')) { + $ThisFileInfo['bonk'][' ID3']['valid'] = new getid3_id3v2($fd, $ThisFileInfo, $ThisFileInfo['bonk'][' ID3']['offset'] + 2); + } + break; + + default: + $ThisFileInfo['warning'][] = 'Unexpected Bonk tag "'.$BonkTagName.'" at offset '.$ThisFileInfo['bonk'][$BonkTagName]['offset']; + break; + + } + } + + function BonkIsValidTagName($PossibleBonkTag, $ignorecase=false) { + static $BonkIsValidTagName = array('BONK', 'INFO', ' ID3', 'META'); + foreach ($BonkIsValidTagName as $validtagname) { + if ($validtagname == $PossibleBonkTag) { + return true; + } elseif ($ignorecase && (strtolower($validtagname) == strtolower($PossibleBonkTag))) { + return true; + } + } + return false; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.flac.php b/modules/id3/getid3/module.audio.flac.php new file mode 100644 index 00000000..18a55b0d --- /dev/null +++ b/modules/id3/getid3/module.audio.flac.php @@ -0,0 +1,309 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.flac.php // +// module for analyzing FLAC and OggFLAC audio files // +// dependencies: module.audio.ogg.php // +// /// +///////////////////////////////////////////////////////////////// + + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true); + +class getid3_flac +{ + + function getid3_flac(&$fd, &$ThisFileInfo) { + // http://flac.sourceforge.net/format.html + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $StreamMarker = fread($fd, 4); + if ($StreamMarker != 'fLaC') { + $ThisFileInfo['error'][] = 'Expecting "fLaC" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$StreamMarker.'"'; + return false; + } + $ThisFileInfo['fileformat'] = 'flac'; + $ThisFileInfo['audio']['dataformat'] = 'flac'; + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + $ThisFileInfo['audio']['lossless'] = true; + + return getid3_flac::FLACparseMETAdata($fd, $ThisFileInfo); + } + + + function FLACparseMETAdata(&$fd, &$ThisFileInfo) { + + do { + $METAdataBlockOffset = ftell($fd); + $METAdataBlockHeader = fread($fd, 4); + $METAdataLastBlockFlag = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x80); + $METAdataBlockType = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 0, 1)) & 0x7F; + $METAdataBlockLength = getid3_lib::BigEndian2Int(substr($METAdataBlockHeader, 1, 3)); + $METAdataBlockTypeText = getid3_flac::FLACmetaBlockTypeLookup($METAdataBlockType); + + if ($METAdataBlockLength <= 0) { + $ThisFileInfo['error'][] = 'corrupt or invalid METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset; + break; + } + + $ThisFileInfo['flac'][$METAdataBlockTypeText]['raw'] = array(); + $ThisFileInfo_flac_METAdataBlockTypeText_raw = &$ThisFileInfo['flac'][$METAdataBlockTypeText]['raw']; + + $ThisFileInfo_flac_METAdataBlockTypeText_raw['offset'] = $METAdataBlockOffset; + $ThisFileInfo_flac_METAdataBlockTypeText_raw['last_meta_block'] = $METAdataLastBlockFlag; + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type'] = $METAdataBlockType; + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_type_text'] = $METAdataBlockTypeText; + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_length'] = $METAdataBlockLength; + $ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'] = fread($fd, $METAdataBlockLength); + $ThisFileInfo['avdataoffset'] = ftell($fd); + + switch ($METAdataBlockTypeText) { + + case 'STREAMINFO': + if (!getid3_flac::FLACparseSTREAMINFO($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'], $ThisFileInfo)) { + return false; + } + break; + + case 'PADDING': + // ignore + break; + + case 'APPLICATION': + if (!getid3_flac::FLACparseAPPLICATION($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'], $ThisFileInfo)) { + return false; + } + break; + + case 'SEEKTABLE': + if (!getid3_flac::FLACparseSEEKTABLE($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'], $ThisFileInfo)) { + return false; + } + break; + + case 'VORBIS_COMMENT': + $OldOffset = ftell($fd); + fseek($fd, 0 - $METAdataBlockLength, SEEK_CUR); + getid3_ogg::ParseVorbisCommentsFilepointer($fd, $ThisFileInfo); + fseek($fd, $OldOffset, SEEK_SET); + break; + + case 'CUESHEET': + if (!getid3_flac::FLACparseCUESHEET($ThisFileInfo_flac_METAdataBlockTypeText_raw['block_data'], $ThisFileInfo)) { + return false; + } + break; + + default: + $ThisFileInfo['warning'][] = 'Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$METAdataBlockType.') at offset '.$METAdataBlockOffset; + break; + } + + } while ($METAdataLastBlockFlag === false); + + + if (isset($ThisFileInfo['flac']['STREAMINFO'])) { + $ThisFileInfo['flac']['compressed_audio_bytes'] = $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']; + $ThisFileInfo['flac']['uncompressed_audio_bytes'] = $ThisFileInfo['flac']['STREAMINFO']['samples_stream'] * $ThisFileInfo['flac']['STREAMINFO']['channels'] * ($ThisFileInfo['flac']['STREAMINFO']['bits_per_sample'] / 8); + if ($ThisFileInfo['flac']['uncompressed_audio_bytes'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt FLAC file: uncompressed_audio_bytes == zero'; + return false; + } + $ThisFileInfo['flac']['compression_ratio'] = $ThisFileInfo['flac']['compressed_audio_bytes'] / $ThisFileInfo['flac']['uncompressed_audio_bytes']; + } + + // set md5_data_source - built into flac 0.5+ + if (isset($ThisFileInfo['flac']['STREAMINFO']['audio_signature'])) { + + if ($ThisFileInfo['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) { + + $ThisFileInfo['warning'][] = 'FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)'; + + } else { + + $ThisFileInfo['md5_data_source'] = ''; + $md5 = $ThisFileInfo['flac']['STREAMINFO']['audio_signature']; + for ($i = 0; $i < strlen($md5); $i++) { + $ThisFileInfo['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT); + } + if (!preg_match('/^[0-9a-f]{32}$/', $ThisFileInfo['md5_data_source'])) { + unset($ThisFileInfo['md5_data_source']); + } + + } + + } + + $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['flac']['STREAMINFO']['bits_per_sample']; + if ($ThisFileInfo['audio']['bits_per_sample'] == 8) { + // special case + // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value + // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed + $ThisFileInfo['warning'][] = 'FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file'; + } + if (!empty($ThisFileInfo['ogg']['vendor'])) { + $ThisFileInfo['audio']['encoder'] = $ThisFileInfo['ogg']['vendor']; + } + + return true; + } + + function FLACmetaBlockTypeLookup($blocktype) { + static $FLACmetaBlockTypeLookup = array(); + if (empty($FLACmetaBlockTypeLookup)) { + $FLACmetaBlockTypeLookup[0] = 'STREAMINFO'; + $FLACmetaBlockTypeLookup[1] = 'PADDING'; + $FLACmetaBlockTypeLookup[2] = 'APPLICATION'; + $FLACmetaBlockTypeLookup[3] = 'SEEKTABLE'; + $FLACmetaBlockTypeLookup[4] = 'VORBIS_COMMENT'; + $FLACmetaBlockTypeLookup[5] = 'CUESHEET'; + } + return (isset($FLACmetaBlockTypeLookup[$blocktype]) ? $FLACmetaBlockTypeLookup[$blocktype] : 'reserved'); + } + + function FLACapplicationIDLookup($applicationid) { + static $FLACapplicationIDLookup = array(); + if (empty($FLACapplicationIDLookup)) { + // http://flac.sourceforge.net/id.html + $FLACapplicationIDLookup[0x46746F6C] = 'flac-tools'; // 'Ftol' + $FLACapplicationIDLookup[0x46746F6C] = 'Sound Font FLAC'; // 'SFFL' + } + return (isset($FLACapplicationIDLookup[$applicationid]) ? $FLACapplicationIDLookup[$applicationid] : 'reserved'); + } + + function FLACparseSTREAMINFO($METAdataBlockData, &$ThisFileInfo) { + $offset = 0; + $ThisFileInfo['flac']['STREAMINFO']['min_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); + $offset += 2; + $ThisFileInfo['flac']['STREAMINFO']['max_block_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); + $offset += 2; + $ThisFileInfo['flac']['STREAMINFO']['min_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3)); + $offset += 3; + $ThisFileInfo['flac']['STREAMINFO']['max_frame_size'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 3)); + $offset += 3; + + $SampleRateChannelsSampleBitsStreamSamples = getid3_lib::BigEndian2Bin(substr($METAdataBlockData, $offset, 8)); + $ThisFileInfo['flac']['STREAMINFO']['sample_rate'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 0, 20)); + $ThisFileInfo['flac']['STREAMINFO']['channels'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 20, 3)) + 1; + $ThisFileInfo['flac']['STREAMINFO']['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 23, 5)) + 1; + $ThisFileInfo['flac']['STREAMINFO']['samples_stream'] = getid3_lib::Bin2Dec(substr($SampleRateChannelsSampleBitsStreamSamples, 28, 36)); + $offset += 8; + + $ThisFileInfo['flac']['STREAMINFO']['audio_signature'] = substr($METAdataBlockData, $offset, 16); + $offset += 16; + + if (!empty($ThisFileInfo['flac']['STREAMINFO']['sample_rate'])) { + + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['flac']['STREAMINFO']['sample_rate']; + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['flac']['STREAMINFO']['channels']; + $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['flac']['STREAMINFO']['bits_per_sample']; + $ThisFileInfo['playtime_seconds'] = $ThisFileInfo['flac']['STREAMINFO']['samples_stream'] / $ThisFileInfo['flac']['STREAMINFO']['sample_rate']; + $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + + } else { + + $ThisFileInfo['error'][] = 'Corrupt METAdata block: STREAMINFO'; + return false; + + } + return true; + } + + + function FLACparseAPPLICATION($METAdataBlockData, &$ThisFileInfo) { + $offset = 0; + $ApplicationID = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 4)); + $offset += 4; + $ThisFileInfo['flac']['APPLICATION'][$ApplicationID]['name'] = getid3_flac::FLACapplicationIDLookup($ApplicationID); + $ThisFileInfo['flac']['APPLICATION'][$ApplicationID]['data'] = substr($METAdataBlockData, $offset); + $offset = $METAdataBlockLength; + + return true; + } + + + function FLACparseSEEKTABLE($METAdataBlockData, &$ThisFileInfo) { + $offset = 0; + $METAdataBlockLength = strlen($METAdataBlockData); + $placeholderpattern = str_repeat("\xFF", 8); + while ($offset < $METAdataBlockLength) { + $SampleNumberString = substr($METAdataBlockData, $offset, 8); + $offset += 8; + if ($SampleNumberString == $placeholderpattern) { + + // placeholder point + @$ThisFileInfo['flac']['SEEKTABLE']['placeholders']++; + $offset += 10; + + } else { + + $SampleNumber = getid3_lib::BigEndian2Int($SampleNumberString); + $ThisFileInfo['flac']['SEEKTABLE'][$SampleNumber]['offset'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); + $offset += 8; + $ThisFileInfo['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 2)); + $offset += 2; + + } + } + return true; + } + + function FLACparseCUESHEET($METAdataBlockData, &$ThisFileInfo) { + $offset = 0; + $ThisFileInfo['flac']['CUESHEET']['media_catalog_number'] = trim(substr($METAdataBlockData, $offset, 128), "\0"); + $offset += 128; + $ThisFileInfo['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); + $offset += 8; + $ThisFileInfo['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)) & 0x80); + $offset += 1; + + $offset += 258; // reserved + + $ThisFileInfo['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); + $offset += 1; + + for ($track = 0; $track < $ThisFileInfo['flac']['CUESHEET']['number_tracks']; $track++) { + $TrackSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); + $offset += 8; + $TrackNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); + $offset += 1; + + $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset; + + $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($METAdataBlockData, $offset, 12); + $offset += 12; + + $TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); + $offset += 1; + $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80); + $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40); + + $offset += 13; // reserved + + $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); + $offset += 1; + + for ($index = 0; $index < $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) { + $IndexSampleOffset = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 8)); + $offset += 8; + $IndexNumber = getid3_lib::BigEndian2Int(substr($METAdataBlockData, $offset, 1)); + $offset += 1; + + $offset += 3; // reserved + + $ThisFileInfo['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset; + } + } + return true; + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.la.php b/modules/id3/getid3/module.audio.la.php new file mode 100644 index 00000000..9f82aca2 --- /dev/null +++ b/modules/id3/getid3/module.audio.la.php @@ -0,0 +1,227 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.la.php // +// module for analyzing LA audio files // +// dependencies: module.audio.riff.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + +class getid3_la +{ + + function getid3_la(&$fd, &$ThisFileInfo) { + $offset = 0; + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $rawdata = fread($fd, GETID3_FREAD_BUFFER_SIZE); + + switch (substr($rawdata, $offset, 4)) { + case 'LA02': + case 'LA03': + case 'LA04': + $ThisFileInfo['fileformat'] = 'la'; + $ThisFileInfo['audio']['dataformat'] = 'la'; + $ThisFileInfo['audio']['lossless'] = true; + + $ThisFileInfo['la']['version_major'] = (int) substr($rawdata, $offset + 2, 1); + $ThisFileInfo['la']['version_minor'] = (int) substr($rawdata, $offset + 3, 1); + $ThisFileInfo['la']['version'] = (float) $ThisFileInfo['la']['version_major'] + ($ThisFileInfo['la']['version_minor'] / 10); + $offset += 4; + + $ThisFileInfo['la']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + if ($ThisFileInfo['la']['uncompressed_size'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt LA file: uncompressed_size == zero'; + return false; + } + + $WAVEchunk = substr($rawdata, $offset, 4); + if ($WAVEchunk !== 'WAVE') { + $ThisFileInfo['error'][] = 'Expected "WAVE" ('.getid3_lib::PrintHexBytes('WAVE').') at offset '.$offset.', found "'.$WAVEchunk.'" ('.getid3_lib::PrintHexBytes($WAVEchunk).') instead.'; + return false; + } + $offset += 4; + + $ThisFileInfo['la']['fmt_size'] = 24; + if ($ThisFileInfo['la']['version'] >= 0.3) { + + $ThisFileInfo['la']['fmt_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $ThisFileInfo['la']['header_size'] = 49 + $ThisFileInfo['la']['fmt_size'] - 24; + $offset += 4; + + } else { + + // version 0.2 didn't support additional data blocks + $ThisFileInfo['la']['header_size'] = 41; + + } + + $fmt_chunk = substr($rawdata, $offset, 4); + if ($fmt_chunk !== 'fmt ') { + $ThisFileInfo['error'][] = 'Expected "fmt " ('.getid3_lib::PrintHexBytes('fmt ').') at offset '.$offset.', found "'.$fmt_chunk.'" ('.getid3_lib::PrintHexBytes($fmt_chunk).') instead.'; + return false; + } + $offset += 4; + $fmt_size = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + + $ThisFileInfo['la']['raw']['format'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); + $offset += 2; + + $ThisFileInfo['la']['channels'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); + $offset += 2; + if ($ThisFileInfo['la']['channels'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt LA file: channels == zero'; + return false; + } + + $ThisFileInfo['la']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + if ($ThisFileInfo['la']['sample_rate'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt LA file: sample_rate == zero'; + return false; + } + + $ThisFileInfo['la']['bytes_per_second'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + $ThisFileInfo['la']['bytes_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); + $offset += 2; + $ThisFileInfo['la']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); + $offset += 2; + + $ThisFileInfo['la']['samples'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + + $ThisFileInfo['la']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 1)); + $offset += 1; + $ThisFileInfo['la']['flags']['seekable'] = (bool) ($ThisFileInfo['la']['raw']['flags'] & 0x01); + if ($ThisFileInfo['la']['version'] >= 0.4) { + $ThisFileInfo['la']['flags']['high_compression'] = (bool) ($ThisFileInfo['la']['raw']['flags'] & 0x02); + } + + $ThisFileInfo['la']['original_crc'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + + // mikeØbevin*de + // Basically, the blocksize/seekevery are 61440/19 in La0.4 and 73728/16 + // in earlier versions. A seekpoint is added every blocksize * seekevery + // samples, so 4 * int(totalSamples / (blockSize * seekEvery)) should + // give the number of bytes used for the seekpoints. Of course, if seeking + // is disabled, there are no seekpoints stored. + if ($ThisFileInfo['la']['version'] >= 0.4) { + $ThisFileInfo['la']['blocksize'] = 61440; + $ThisFileInfo['la']['seekevery'] = 19; + } else { + $ThisFileInfo['la']['blocksize'] = 73728; + $ThisFileInfo['la']['seekevery'] = 16; + } + + $ThisFileInfo['la']['seekpoint_count'] = 0; + if ($ThisFileInfo['la']['flags']['seekable']) { + $ThisFileInfo['la']['seekpoint_count'] = floor($ThisFileInfo['la']['samples'] / ($ThisFileInfo['la']['blocksize'] * $ThisFileInfo['la']['seekevery'])); + + for ($i = 0; $i < $ThisFileInfo['la']['seekpoint_count']; $i++) { + $ThisFileInfo['la']['seekpoints'][] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + } + } + + if ($ThisFileInfo['la']['version'] >= 0.3) { + + // Following the main header information, the program outputs all of the + // seekpoints. Following these is what I called the 'footer start', + // i.e. the position immediately after the La audio data is finished. + $ThisFileInfo['la']['footerstart'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); + $offset += 4; + + if ($ThisFileInfo['la']['footerstart'] > $ThisFileInfo['filesize']) { + $ThisFileInfo['warning'][] = 'FooterStart value points to offset '.$ThisFileInfo['la']['footerstart'].' which is beyond end-of-file ('.$ThisFileInfo['filesize'].')'; + $ThisFileInfo['la']['footerstart'] = $ThisFileInfo['filesize']; + } + + } else { + + // La v0.2 didn't have FooterStart value + $ThisFileInfo['la']['footerstart'] = $ThisFileInfo['avdataend']; + + } + + if ($ThisFileInfo['la']['footerstart'] < $ThisFileInfo['avdataend']) { + if ($RIFFtempfilename = tempnam('*', 'id3')) { + if ($RIFF_fp = fopen($RIFFtempfilename, 'w+b')) { + $RIFFdata = 'WAVE'; + if ($ThisFileInfo['la']['version'] == 0.2) { + $RIFFdata .= substr($rawdata, 12, 24); + } else { + $RIFFdata .= substr($rawdata, 16, 24); + } + if ($ThisFileInfo['la']['footerstart'] < $ThisFileInfo['avdataend']) { + fseek($fd, $ThisFileInfo['la']['footerstart'], SEEK_SET); + $RIFFdata .= fread($fd, $ThisFileInfo['avdataend'] - $ThisFileInfo['la']['footerstart']); + } + $RIFFdata = 'RIFF'.getid3_lib::LittleEndian2String(strlen($RIFFdata), 4, false).$RIFFdata; + fwrite($RIFF_fp, $RIFFdata, strlen($RIFFdata)); + $dummy = $ThisFileInfo; + $dummy['filesize'] = strlen($RIFFdata); + $dummy['avdataoffset'] = 0; + $dummy['avdataend'] = $dummy['filesize']; + + $riff = new getid3_riff($RIFF_fp, $dummy); + if (empty($dummy['error'])) { + $ThisFileInfo['riff'] = $dummy['riff']; + } else { + $ThisFileInfo['warning'][] = 'Error parsing RIFF portion of La file: '.implode($dummy['error']); + } + unset($dummy); + fclose($RIFF_fp); + } + unlink($RIFFtempfilename); + } + } + + // $ThisFileInfo['avdataoffset'] should be zero to begin with, but just in case it's not, include the addition anyway + $ThisFileInfo['avdataend'] = $ThisFileInfo['avdataoffset'] + $ThisFileInfo['la']['footerstart']; + $ThisFileInfo['avdataoffset'] = $ThisFileInfo['avdataoffset'] + $offset; + + //$ThisFileInfo['la']['codec'] = RIFFwFormatTagLookup($ThisFileInfo['la']['raw']['format']); + $ThisFileInfo['la']['compression_ratio'] = (float) (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) / $ThisFileInfo['la']['uncompressed_size']); + $ThisFileInfo['playtime_seconds'] = (float) ($ThisFileInfo['la']['samples'] / $ThisFileInfo['la']['sample_rate']) / $ThisFileInfo['la']['channels']; + if ($ThisFileInfo['playtime_seconds'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt LA file: playtime_seconds == zero'; + return false; + } + + $ThisFileInfo['audio']['bitrate'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8 / $ThisFileInfo['playtime_seconds']; + //$ThisFileInfo['audio']['codec'] = $ThisFileInfo['la']['codec']; + $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['la']['bits_per_sample']; + break; + + default: + if (substr($rawdata, $offset, 2) == 'LA') { + $ThisFileInfo['error'][] = 'This version of getID3() (v'.GETID3_VERSION.') doesn\'t support LA version '.substr($rawdata, $offset + 2, 1).'.'.substr($rawdata, $offset + 3, 1).' which this appears to be - check http://getid3.sourceforge.net for updates.'; + } else { + $ThisFileInfo['error'][] = 'Not a LA (Lossless-Audio) file'; + } + return false; + break; + } + + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['la']['channels']; + $ThisFileInfo['audio']['sample_rate'] = (int) $ThisFileInfo['la']['sample_rate']; + $ThisFileInfo['audio']['encoder'] = 'LA v'.$ThisFileInfo['la']['version']; + + return true; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.lpac.php b/modules/id3/getid3/module.audio.lpac.php new file mode 100644 index 00000000..64a87d05 --- /dev/null +++ b/modules/id3/getid3/module.audio.lpac.php @@ -0,0 +1,125 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.lpac.php // +// module for analyzing LPAC Audio files // +// dependencies: module.audio-video.riff.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + +class getid3_lpac +{ + + function getid3_lpac(&$fd, &$ThisFileInfo) { + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $LPACheader = fread($fd, 14); + if (substr($LPACheader, 0, 4) != 'LPAC') { + $ThisFileInfo['error'][] = 'Expected "LPAC" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$StreamMarker.'"'; + return false; + } + $ThisFileInfo['avdataoffset'] += 14; + + $ThisFileInfo['fileformat'] = 'lpac'; + $ThisFileInfo['audio']['dataformat'] = 'lpac'; + $ThisFileInfo['audio']['lossless'] = true; + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + + $ThisFileInfo['lpac']['file_version'] = getid3_lib::BigEndian2Int(substr($LPACheader, 4, 1)); + $flags['audio_type'] = getid3_lib::BigEndian2Int(substr($LPACheader, 5, 1)); + $ThisFileInfo['lpac']['total_samples']= getid3_lib::BigEndian2Int(substr($LPACheader, 6, 4)); + $flags['parameters'] = getid3_lib::BigEndian2Int(substr($LPACheader, 10, 4)); + + $ThisFileInfo['lpac']['flags']['is_wave'] = (bool) ($flags['audio_type'] & 0x40); + $ThisFileInfo['lpac']['flags']['stereo'] = (bool) ($flags['audio_type'] & 0x04); + $ThisFileInfo['lpac']['flags']['24_bit'] = (bool) ($flags['audio_type'] & 0x02); + $ThisFileInfo['lpac']['flags']['16_bit'] = (bool) ($flags['audio_type'] & 0x01); + + if ($ThisFileInfo['lpac']['flags']['24_bit'] && $ThisFileInfo['lpac']['flags']['16_bit']) { + $ThisFileInfo['warning'][] = '24-bit and 16-bit flags cannot both be set'; + } + + $ThisFileInfo['lpac']['flags']['fast_compress'] = (bool) ($flags['parameters'] & 0x40000000); + $ThisFileInfo['lpac']['flags']['random_access'] = (bool) ($flags['parameters'] & 0x08000000); + $ThisFileInfo['lpac']['block_length'] = pow(2, (($flags['parameters'] & 0x07000000) >> 24)) * 256; + $ThisFileInfo['lpac']['flags']['adaptive_prediction_order'] = (bool) ($flags['parameters'] & 0x00800000); + $ThisFileInfo['lpac']['flags']['adaptive_quantization'] = (bool) ($flags['parameters'] & 0x00400000); + $ThisFileInfo['lpac']['flags']['joint_stereo'] = (bool) ($flags['parameters'] & 0x00040000); + $ThisFileInfo['lpac']['quantization'] = ($flags['parameters'] & 0x00001F00) >> 8; + $ThisFileInfo['lpac']['max_prediction_order'] = ($flags['parameters'] & 0x0000003F); + + if ($ThisFileInfo['lpac']['flags']['fast_compress'] && ($ThisFileInfo['lpac']['max_prediction_order'] != 3)) { + $ThisFileInfo['warning'][] = 'max_prediction_order expected to be "3" if fast_compress is true, actual value is "'.$ThisFileInfo['lpac']['max_prediction_order'].'"'; + } + switch ($ThisFileInfo['lpac']['file_version']) { + case 6: + if ($ThisFileInfo['lpac']['flags']['adaptive_quantization']) { + $ThisFileInfo['warning'][] = 'adaptive_quantization expected to be false in LPAC file stucture v6, actually true'; + } + if ($ThisFileInfo['lpac']['quantization'] != 20) { + $ThisFileInfo['warning'][] = 'Quantization expected to be 20 in LPAC file stucture v6, actually '.$ThisFileInfo['lpac']['flags']['Q']; + } + break; + + default: + //$ThisFileInfo['warning'][] = 'This version of getID3() only supports LPAC file format version 6, this file is version '.$ThisFileInfo['lpac']['file_version'].' - please report to info@getid3.org'; + break; + } + + $dummy = $ThisFileInfo; + $riff = new getid3_riff($fd, $dummy); + $ThisFileInfo['avdataoffset'] = $dummy['avdataoffset']; + $ThisFileInfo['riff'] = $dummy['riff']; + $ThisFileInfo['error'] = $dummy['error']; + $ThisFileInfo['warning'] = $dummy['warning']; + $ThisFileInfo['lpac']['comments']['comment'] = $dummy['comments']; + $ThisFileInfo['audio']['sample_rate'] = $dummy['audio']['sample_rate']; + + $ThisFileInfo['audio']['channels'] = ($ThisFileInfo['lpac']['flags']['stereo'] ? 2 : 1); + + if ($ThisFileInfo['lpac']['flags']['24_bit']) { + $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['riff']['audio'][0]['bits_per_sample']; + } elseif ($ThisFileInfo['lpac']['flags']['16_bit']) { + $ThisFileInfo['audio']['bits_per_sample'] = 16; + } else { + $ThisFileInfo['audio']['bits_per_sample'] = 8; + } + + if ($ThisFileInfo['lpac']['flags']['fast_compress']) { + // fast + $ThisFileInfo['audio']['encoder_options'] = '-1'; + } else { + switch ($ThisFileInfo['lpac']['max_prediction_order']) { + case 20: // simple + $ThisFileInfo['audio']['encoder_options'] = '-2'; + break; + case 30: // medium + $ThisFileInfo['audio']['encoder_options'] = '-3'; + break; + case 40: // high + $ThisFileInfo['audio']['encoder_options'] = '-4'; + break; + case 60: // extrahigh + $ThisFileInfo['audio']['encoder_options'] = '-5'; + break; + } + } + + $ThisFileInfo['playtime_seconds'] = $ThisFileInfo['lpac']['total_samples'] / $ThisFileInfo['audio']['sample_rate']; + $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + + return true; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.midi.php b/modules/id3/getid3/module.audio.midi.php new file mode 100644 index 00000000..f72760d1 --- /dev/null +++ b/modules/id3/getid3/module.audio.midi.php @@ -0,0 +1,520 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.midi.php // +// module for Midi Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_midi +{ + + function getid3_midi(&$fd, &$ThisFileInfo, $scanwholefile=true) { + + // shortcut + $ThisFileInfo['midi']['raw'] = array(); + $thisfile_midi = &$ThisFileInfo['midi']; + $thisfile_midi_raw = &$thisfile_midi['raw']; + + $ThisFileInfo['fileformat'] = 'midi'; + $ThisFileInfo['audio']['dataformat'] = 'midi'; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $MIDIdata = fread($fd, GETID3_FREAD_BUFFER_SIZE); + $offset = 0; + $MIDIheaderID = substr($MIDIdata, $offset, 4); // 'MThd' + if ($MIDIheaderID != 'MThd') { + $ThisFileInfo['error'][] = 'Expecting "MThd" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$MIDIheaderID.'"'; + unset($ThisFileInfo['fileformat']); + return false; + } + $offset += 4; + $thisfile_midi_raw['headersize'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4)); + $offset += 4; + $thisfile_midi_raw['fileformat'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2)); + $offset += 2; + $thisfile_midi_raw['tracks'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2)); + $offset += 2; + $thisfile_midi_raw['ticksperqnote'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2)); + $offset += 2; + + for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++) { + if ((strlen($MIDIdata) - $offset) < 8) { + $MIDIdata .= fread($fd, GETID3_FREAD_BUFFER_SIZE); + } + $trackID = substr($MIDIdata, $offset, 4); + $offset += 4; + if ($trackID == 'MTrk') { + $tracksize = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4)); + $offset += 4; + // $thisfile_midi['tracks'][$i]['size'] = $tracksize; + $trackdataarray[$i] = substr($MIDIdata, $offset, $tracksize); + $offset += $tracksize; + } else { + $ThisFileInfo['error'][] = 'Expecting "MTrk" at '.$offset.', found '.$trackID.' instead'; + return false; + } + } + + if (!isset($trackdataarray) || !is_array($trackdataarray)) { + $ThisFileInfo['error'][] = 'Cannot find MIDI track information'; + unset($thisfile_midi); + unset($ThisFileInfo['fileformat']); + return false; + } + + if ($scanwholefile) { // this can take quite a long time, so have the option to bypass it if speed is very important + $thisfile_midi['totalticks'] = 0; + $ThisFileInfo['playtime_seconds'] = 0; + $CurrentMicroSecondsPerBeat = 500000; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat + $CurrentBeatsPerMinute = 120; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat + + foreach ($trackdataarray as $tracknumber => $trackdata) { + + $eventsoffset = 0; + $LastIssuedMIDIcommand = 0; + $LastIssuedMIDIchannel = 0; + $CumulativeDeltaTime = 0; + $TicksAtCurrentBPM = 0; + while ($eventsoffset < strlen($trackdata)) { + $eventid = 0; + if (isset($MIDIevents[$tracknumber]) && is_array($MIDIevents[$tracknumber])) { + $eventid = count($MIDIevents[$tracknumber]); + } + $deltatime = 0; + for ($i = 0; $i < 4; $i++) { + $deltatimebyte = ord(substr($trackdata, $eventsoffset++, 1)); + $deltatime = ($deltatime << 7) + ($deltatimebyte & 0x7F); + if ($deltatimebyte & 0x80) { + // another byte follows + } else { + break; + } + } + $CumulativeDeltaTime += $deltatime; + $TicksAtCurrentBPM += $deltatime; + $MIDIevents[$tracknumber][$eventid]['deltatime'] = $deltatime; + $MIDI_event_channel = ord(substr($trackdata, $eventsoffset++, 1)); + if ($MIDI_event_channel & 0x80) { + // OK, normal event - MIDI command has MSB set + $LastIssuedMIDIcommand = $MIDI_event_channel >> 4; + $LastIssuedMIDIchannel = $MIDI_event_channel & 0x0F; + } else { + // running event - assume last command + $eventsoffset--; + } + $MIDIevents[$tracknumber][$eventid]['eventid'] = $LastIssuedMIDIcommand; + $MIDIevents[$tracknumber][$eventid]['channel'] = $LastIssuedMIDIchannel; + if ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x08) { // Note off (key is released) + + $notenumber = ord(substr($trackdata, $eventsoffset++, 1)); + $velocity = ord(substr($trackdata, $eventsoffset++, 1)); + + } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x09) { // Note on (key is pressed) + + $notenumber = ord(substr($trackdata, $eventsoffset++, 1)); + $velocity = ord(substr($trackdata, $eventsoffset++, 1)); + + } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0A) { // Key after-touch + + $notenumber = ord(substr($trackdata, $eventsoffset++, 1)); + $velocity = ord(substr($trackdata, $eventsoffset++, 1)); + + } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0B) { // Control Change + + $controllernum = ord(substr($trackdata, $eventsoffset++, 1)); + $newvalue = ord(substr($trackdata, $eventsoffset++, 1)); + + } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0C) { // Program (patch) change + + $newprogramnum = ord(substr($trackdata, $eventsoffset++, 1)); + + $thisfile_midi_raw['track'][$tracknumber]['instrumentid'] = $newprogramnum; + if ($tracknumber == 10) { + $thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIpercussionLookup($newprogramnum); + } else { + $thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIinstrumentLookup($newprogramnum); + } + + } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0D) { // Channel after-touch + + $channelnumber = ord(substr($trackdata, $eventsoffset++, 1)); + + } elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0E) { // Pitch wheel change (2000H is normal or no change) + + $changeLSB = ord(substr($trackdata, $eventsoffset++, 1)); + $changeMSB = ord(substr($trackdata, $eventsoffset++, 1)); + $pitchwheelchange = (($changeMSB & 0x7F) << 7) & ($changeLSB & 0x7F); + + } elseif (($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0F) && ($MIDIevents[$tracknumber][$eventid]['channel'] == 0x0F)) { + + $METAeventCommand = ord(substr($trackdata, $eventsoffset++, 1)); + $METAeventLength = ord(substr($trackdata, $eventsoffset++, 1)); + $METAeventData = substr($trackdata, $eventsoffset, $METAeventLength); + $eventsoffset += $METAeventLength; + switch ($METAeventCommand) { + case 0x00: // Set track sequence number + $track_sequence_number = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength)); + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['seqno'] = $track_sequence_number; + break; + + case 0x01: // Text: generic + $text_generic = substr($METAeventData, 0, $METAeventLength); + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['text'] = $text_generic; + $thisfile_midi['comments']['comment'][] = $text_generic; + break; + + case 0x02: // Text: copyright + $text_copyright = substr($METAeventData, 0, $METAeventLength); + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['copyright'] = $text_copyright; + $thisfile_midi['comments']['copyright'][] = $text_copyright; + break; + + case 0x03: // Text: track name + $text_trackname = substr($METAeventData, 0, $METAeventLength); + $thisfile_midi_raw['track'][$tracknumber]['name'] = $text_trackname; + break; + + case 0x04: // Text: track instrument name + $text_instrument = substr($METAeventData, 0, $METAeventLength); + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['instrument'] = $text_instrument; + break; + + case 0x05: // Text: lyrics + $text_lyrics = substr($METAeventData, 0, $METAeventLength); + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['lyrics'] = $text_lyrics; + if (!isset($thisfile_midi['lyrics'])) { + $thisfile_midi['lyrics'] = ''; + } + $thisfile_midi['lyrics'] .= $text_lyrics."\n"; + break; + + case 0x06: // Text: marker + $text_marker = substr($METAeventData, 0, $METAeventLength); + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['marker'] = $text_marker; + break; + + case 0x07: // Text: cue point + $text_cuepoint = substr($METAeventData, 0, $METAeventLength); + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['cuepoint'] = $text_cuepoint; + break; + + case 0x2F: // End Of Track + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['EOT'] = $CumulativeDeltaTime; + break; + + case 0x51: // Tempo: microseconds / quarter note + $CurrentMicroSecondsPerBeat = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength)); + if ($CurrentMicroSecondsPerBeat == 0) { + $ThisFileInfo['error'][] = 'Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero'; + return false; + } + $thisfile_midi_raw['events'][$tracknumber][$CumulativeDeltaTime]['us_qnote'] = $CurrentMicroSecondsPerBeat; + $CurrentBeatsPerMinute = (1000000 / $CurrentMicroSecondsPerBeat) * 60; + $MicroSecondsPerQuarterNoteAfter[$CumulativeDeltaTime] = $CurrentMicroSecondsPerBeat; + $TicksAtCurrentBPM = 0; + break; + + case 0x58: // Time signature + $timesig_numerator = getid3_lib::BigEndian2Int($METAeventData{0}); + $timesig_denominator = pow(2, getid3_lib::BigEndian2Int($METAeventData{1})); // $02 -> x/4, $03 -> x/8, etc + $timesig_32inqnote = getid3_lib::BigEndian2Int($METAeventData{2}); // number of 32nd notes to the quarter note + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_32inqnote'] = $timesig_32inqnote; + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_numerator'] = $timesig_numerator; + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_denominator'] = $timesig_denominator; + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_text'] = $timesig_numerator.'/'.$timesig_denominator; + $thisfile_midi['timesignature'][] = $timesig_numerator.'/'.$timesig_denominator; + break; + + case 0x59: // Keysignature + $keysig_sharpsflats = getid3_lib::BigEndian2Int($METAeventData{0}); + if ($keysig_sharpsflats & 0x80) { + // (-7 -> 7 flats, 0 ->key of C, 7 -> 7 sharps) + $keysig_sharpsflats -= 256; + } + + $keysig_majorminor = getid3_lib::BigEndian2Int($METAeventData{1}); // 0 -> major, 1 -> minor + $keysigs = array(-7=>'Cb', -6=>'Gb', -5=>'Db', -4=>'Ab', -3=>'Eb', -2=>'Bb', -1=>'F', 0=>'C', 1=>'G', 2=>'D', 3=>'A', 4=>'E', 5=>'B', 6=>'F#', 7=>'C#'); + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_sharps'] = (($keysig_sharpsflats > 0) ? abs($keysig_sharpsflats) : 0); + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_flats'] = (($keysig_sharpsflats < 0) ? abs($keysig_sharpsflats) : 0); + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] = (bool) $keysig_majorminor; + //$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_text'] = $keysigs[$keysig_sharpsflats].' '.($thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] ? 'minor' : 'major'); + + // $keysigs[$keysig_sharpsflats] gets an int key (correct) - $keysigs["$keysig_sharpsflats"] gets a string key (incorrect) + $thisfile_midi['keysignature'][] = $keysigs[$keysig_sharpsflats].' '.((bool) $keysig_majorminor ? 'minor' : 'major'); + break; + + case 0x7F: // Sequencer specific information + $custom_data = substr($METAeventData, 0, $METAeventLength); + break; + + default: + $ThisFileInfo['warning'][] = 'Unhandled META Event Command: '.$METAeventCommand; + break; + } + + } else { + + $ThisFileInfo['warning'][] = 'Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid]['eventid'].' + Channel ID: '.$MIDIevents[$tracknumber][$eventid]['channel']; + + } + } + if (($tracknumber > 0) || (count($trackdataarray) == 1)) { + $thisfile_midi['totalticks'] = max($thisfile_midi['totalticks'], $CumulativeDeltaTime); + } + } + $previoustickoffset = null; + + ksort($MicroSecondsPerQuarterNoteAfter); + foreach ($MicroSecondsPerQuarterNoteAfter as $tickoffset => $microsecondsperbeat) { + if (is_null($previoustickoffset)) { + $prevmicrosecondsperbeat = $microsecondsperbeat; + $previoustickoffset = $tickoffset; + continue; + } + if ($thisfile_midi['totalticks'] > $tickoffset) { + + if ($thisfile_midi_raw['ticksperqnote'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt MIDI file: ticksperqnote == zero'; + return false; + } + + $ThisFileInfo['playtime_seconds'] += (($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000); + + $prevmicrosecondsperbeat = $microsecondsperbeat; + $previoustickoffset = $tickoffset; + } + } + if ($thisfile_midi['totalticks'] > $previoustickoffset) { + + if ($thisfile_midi_raw['ticksperqnote'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt MIDI file: ticksperqnote == zero'; + return false; + } + + $ThisFileInfo['playtime_seconds'] += (($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($microsecondsperbeat / 1000000); + + } + } + + if ($ThisFileInfo['playtime_seconds'] > 0) { + $ThisFileInfo['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + } + + if (!empty($thisfile_midi['lyrics'])) { + $thisfile_midi['comments']['lyrics'][] = $thisfile_midi['lyrics']; + } + + return true; + } + + function GeneralMIDIinstrumentLookup($instrumentid) { + + $begin = __LINE__; + + /** This is not a comment! + + 0 Acoustic Grand + 1 Bright Acoustic + 2 Electric Grand + 3 Honky-Tonk + 4 Electric Piano 1 + 5 Electric Piano 2 + 6 Harpsichord + 7 Clavier + 8 Celesta + 9 Glockenspiel + 10 Music Box + 11 Vibraphone + 12 Marimba + 13 Xylophone + 14 Tubular Bells + 15 Dulcimer + 16 Drawbar Organ + 17 Percussive Organ + 18 Rock Organ + 19 Church Organ + 20 Reed Organ + 21 Accordian + 22 Harmonica + 23 Tango Accordian + 24 Acoustic Guitar (nylon) + 25 Acoustic Guitar (steel) + 26 Electric Guitar (jazz) + 27 Electric Guitar (clean) + 28 Electric Guitar (muted) + 29 Overdriven Guitar + 30 Distortion Guitar + 31 Guitar Harmonics + 32 Acoustic Bass + 33 Electric Bass (finger) + 34 Electric Bass (pick) + 35 Fretless Bass + 36 Slap Bass 1 + 37 Slap Bass 2 + 38 Synth Bass 1 + 39 Synth Bass 2 + 40 Violin + 41 Viola + 42 Cello + 43 Contrabass + 44 Tremolo Strings + 45 Pizzicato Strings + 46 Orchestral Strings + 47 Timpani + 48 String Ensemble 1 + 49 String Ensemble 2 + 50 SynthStrings 1 + 51 SynthStrings 2 + 52 Choir Aahs + 53 Voice Oohs + 54 Synth Voice + 55 Orchestra Hit + 56 Trumpet + 57 Trombone + 58 Tuba + 59 Muted Trumpet + 60 French Horn + 61 Brass Section + 62 SynthBrass 1 + 63 SynthBrass 2 + 64 Soprano Sax + 65 Alto Sax + 66 Tenor Sax + 67 Baritone Sax + 68 Oboe + 69 English Horn + 70 Bassoon + 71 Clarinet + 72 Piccolo + 73 Flute + 74 Recorder + 75 Pan Flute + 76 Blown Bottle + 77 Shakuhachi + 78 Whistle + 79 Ocarina + 80 Lead 1 (square) + 81 Lead 2 (sawtooth) + 82 Lead 3 (calliope) + 83 Lead 4 (chiff) + 84 Lead 5 (charang) + 85 Lead 6 (voice) + 86 Lead 7 (fifths) + 87 Lead 8 (bass + lead) + 88 Pad 1 (new age) + 89 Pad 2 (warm) + 90 Pad 3 (polysynth) + 91 Pad 4 (choir) + 92 Pad 5 (bowed) + 93 Pad 6 (metallic) + 94 Pad 7 (halo) + 95 Pad 8 (sweep) + 96 FX 1 (rain) + 97 FX 2 (soundtrack) + 98 FX 3 (crystal) + 99 FX 4 (atmosphere) + 100 FX 5 (brightness) + 101 FX 6 (goblins) + 102 FX 7 (echoes) + 103 FX 8 (sci-fi) + 104 Sitar + 105 Banjo + 106 Shamisen + 107 Koto + 108 Kalimba + 109 Bagpipe + 110 Fiddle + 111 Shanai + 112 Tinkle Bell + 113 Agogo + 114 Steel Drums + 115 Woodblock + 116 Taiko Drum + 117 Melodic Tom + 118 Synth Drum + 119 Reverse Cymbal + 120 Guitar Fret Noise + 121 Breath Noise + 122 Seashore + 123 Bird Tweet + 124 Telephone Ring + 125 Helicopter + 126 Applause + 127 Gunshot + + */ + + return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIinstrument'); + } + + function GeneralMIDIpercussionLookup($instrumentid) { + + $begin = __LINE__; + + /** This is not a comment! + + 35 Acoustic Bass Drum + 36 Bass Drum 1 + 37 Side Stick + 38 Acoustic Snare + 39 Hand Clap + 40 Electric Snare + 41 Low Floor Tom + 42 Closed Hi-Hat + 43 High Floor Tom + 44 Pedal Hi-Hat + 45 Low Tom + 46 Open Hi-Hat + 47 Low-Mid Tom + 48 Hi-Mid Tom + 49 Crash Cymbal 1 + 50 High Tom + 51 Ride Cymbal 1 + 52 Chinese Cymbal + 53 Ride Bell + 54 Tambourine + 55 Splash Cymbal + 56 Cowbell + 57 Crash Cymbal 2 + 59 Ride Cymbal 2 + 60 Hi Bongo + 61 Low Bongo + 62 Mute Hi Conga + 63 Open Hi Conga + 64 Low Conga + 65 High Timbale + 66 Low Timbale + 67 High Agogo + 68 Low Agogo + 69 Cabasa + 70 Maracas + 71 Short Whistle + 72 Long Whistle + 73 Short Guiro + 74 Long Guiro + 75 Claves + 76 Hi Wood Block + 77 Low Wood Block + 78 Mute Cuica + 79 Open Cuica + 80 Mute Triangle + 81 Open Triangle + + */ + + return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIpercussion'); + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.mod.php b/modules/id3/getid3/module.audio.mod.php new file mode 100644 index 00000000..7f81ff36 --- /dev/null +++ b/modules/id3/getid3/module.audio.mod.php @@ -0,0 +1,101 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.mod.php // +// module for analyzing MOD Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_mod +{ + + // new combined constructor + function getid3_mod(&$fd, &$ThisFileInfo, $option) { + + if ($option === 'mod') { + $this->getMODheaderFilepointer($fd, $ThisFileInfo); + } + elseif ($option === 'xm') { + $this->getXMheaderFilepointer($fd, $ThisFileInfo); + } + elseif ($option === 'it') { + $this->getITheaderFilepointer($fd, $ThisFileInfo); + } + elseif ($option === 's3m') { + $this->getS3MheaderFilepointer($fd, $ThisFileInfo); + } + } + + + function getMODheaderFilepointer(&$fd, &$ThisFileInfo) { + + fseek($fd, $ThisFileInfo['avdataoffset'] + 1080); + $FormatID = fread($fd, 4); + if (!ereg('^(M.K.|[5-9]CHN|[1-3][0-9]CH)$', $FormatID)) { + $ThisFileInfo['error'][] = 'This is not a known type of MOD file'; + return false; + } + + $ThisFileInfo['fileformat'] = 'mod'; + + $ThisFileInfo['error'][] = 'MOD parsing not enabled in this version of getID3()'; + return false; + } + + function getXMheaderFilepointer(&$fd, &$ThisFileInfo) { + + fseek($fd, $ThisFileInfo['avdataoffset']); + $FormatID = fread($fd, 15); + if (!ereg('^Extended Module$', $FormatID)) { + $ThisFileInfo['error'][] = 'This is not a known type of XM-MOD file'; + return false; + } + + $ThisFileInfo['fileformat'] = 'xm'; + + $ThisFileInfo['error'][] = 'XM-MOD parsing not enabled in this version of getID3()'; + return false; + } + + function getS3MheaderFilepointer(&$fd, &$ThisFileInfo) { + + fseek($fd, $ThisFileInfo['avdataoffset'] + 44); + $FormatID = fread($fd, 4); + if (!ereg('^SCRM$', $FormatID)) { + $ThisFileInfo['error'][] = 'This is not a ScreamTracker MOD file'; + return false; + } + + $ThisFileInfo['fileformat'] = 's3m'; + + $ThisFileInfo['error'][] = 'ScreamTracker parsing not enabled in this version of getID3()'; + return false; + } + + function getITheaderFilepointer(&$fd, &$ThisFileInfo) { + + fseek($fd, $ThisFileInfo['avdataoffset']); + $FormatID = fread($fd, 4); + if (!ereg('^IMPM$', $FormatID)) { + $ThisFileInfo['error'][] = 'This is not an ImpulseTracker MOD file'; + return false; + } + + $ThisFileInfo['fileformat'] = 'it'; + + $ThisFileInfo['error'][] = 'ImpulseTracker parsing not enabled in this version of getID3()'; + return false; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.monkey.php b/modules/id3/getid3/module.audio.monkey.php new file mode 100644 index 00000000..42382ad1 --- /dev/null +++ b/modules/id3/getid3/module.audio.monkey.php @@ -0,0 +1,202 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.monkey.php // +// module for analyzing Monkey's Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_monkey +{ + + function getid3_monkey(&$fd, &$ThisFileInfo) { + // based loosely on code from TMonkey by Jurgen Faul <jfaulØgmx*de> + // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html + + $ThisFileInfo['fileformat'] = 'mac'; + $ThisFileInfo['audio']['dataformat'] = 'mac'; + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + $ThisFileInfo['audio']['lossless'] = true; + + $ThisFileInfo['monkeys_audio']['raw'] = array(); + $thisfile_monkeysaudio = &$ThisFileInfo['monkeys_audio']; + $thisfile_monkeysaudio_raw = &$thisfile_monkeysaudio['raw']; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $MACheaderData = fread($fd, 74); + + $thisfile_monkeysaudio_raw['magic'] = substr($MACheaderData, 0, 4); + if ($thisfile_monkeysaudio_raw['magic'] != 'MAC ') { + $ThisFileInfo['error'][] = 'Expecting "MAC" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$thisfile_monkeysaudio_raw['magic'].'"'; + unset($ThisFileInfo['fileformat']); + return false; + } + $thisfile_monkeysaudio_raw['nVersion'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 4, 2)); // appears to be uint32 in 3.98+ + + if ($thisfile_monkeysaudio_raw['nVersion'] < 3980) { + $thisfile_monkeysaudio_raw['nCompressionLevel'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 6, 2)); + $thisfile_monkeysaudio_raw['nFormatFlags'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 8, 2)); + $thisfile_monkeysaudio_raw['nChannels'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 10, 2)); + $thisfile_monkeysaudio_raw['nSampleRate'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 12, 4)); + $thisfile_monkeysaudio_raw['nHeaderDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 16, 4)); + $thisfile_monkeysaudio_raw['nWAVTerminatingBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 20, 4)); + $thisfile_monkeysaudio_raw['nTotalFrames'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 24, 4)); + $thisfile_monkeysaudio_raw['nFinalFrameSamples'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 28, 4)); + $thisfile_monkeysaudio_raw['nPeakLevel'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 32, 4)); + $thisfile_monkeysaudio_raw['nSeekElements'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 38, 2)); + $offset = 8; + } else { + $offset = 8; + // APE_DESCRIPTOR + $thisfile_monkeysaudio_raw['nDescriptorBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4)); + $offset += 4; + $thisfile_monkeysaudio_raw['nHeaderBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4)); + $offset += 4; + $thisfile_monkeysaudio_raw['nSeekTableBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4)); + $offset += 4; + $thisfile_monkeysaudio_raw['nHeaderDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4)); + $offset += 4; + $thisfile_monkeysaudio_raw['nAPEFrameDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4)); + $offset += 4; + $thisfile_monkeysaudio_raw['nAPEFrameDataBytesHigh'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4)); + $offset += 4; + $thisfile_monkeysaudio_raw['nTerminatingDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4)); + $offset += 4; + $thisfile_monkeysaudio_raw['cFileMD5'] = substr($MACheaderData, $offset, 16); + $offset += 16; + + // APE_HEADER + $thisfile_monkeysaudio_raw['nCompressionLevel'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2)); + $offset += 2; + $thisfile_monkeysaudio_raw['nFormatFlags'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2)); + $offset += 2; + $thisfile_monkeysaudio_raw['nBlocksPerFrame'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4)); + $offset += 4; + $thisfile_monkeysaudio_raw['nFinalFrameBlocks'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4)); + $offset += 4; + $thisfile_monkeysaudio_raw['nTotalFrames'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4)); + $offset += 4; + $thisfile_monkeysaudio_raw['nBitsPerSample'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2)); + $offset += 2; + $thisfile_monkeysaudio_raw['nChannels'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2)); + $offset += 2; + $thisfile_monkeysaudio_raw['nSampleRate'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4)); + $offset += 4; + } + + $thisfile_monkeysaudio['flags']['8-bit'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0001); + $thisfile_monkeysaudio['flags']['crc-32'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0002); + $thisfile_monkeysaudio['flags']['peak_level'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0004); + $thisfile_monkeysaudio['flags']['24-bit'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0008); + $thisfile_monkeysaudio['flags']['seek_elements'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0010); + $thisfile_monkeysaudio['flags']['no_wav_header'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0020); + $thisfile_monkeysaudio['version'] = $thisfile_monkeysaudio_raw['nVersion'] / 1000; + $thisfile_monkeysaudio['compression'] = $this->MonkeyCompressionLevelNameLookup($thisfile_monkeysaudio_raw['nCompressionLevel']); + if ($thisfile_monkeysaudio_raw['nVersion'] < 3980) { + $thisfile_monkeysaudio['samples_per_frame'] = $this->MonkeySamplesPerFrame($thisfile_monkeysaudio_raw['nVersion'], $thisfile_monkeysaudio_raw['nCompressionLevel']); + } + $thisfile_monkeysaudio['bits_per_sample'] = ($thisfile_monkeysaudio['flags']['24-bit'] ? 24 : ($thisfile_monkeysaudio['flags']['8-bit'] ? 8 : 16)); + $thisfile_monkeysaudio['channels'] = $thisfile_monkeysaudio_raw['nChannels']; + $ThisFileInfo['audio']['channels'] = $thisfile_monkeysaudio['channels']; + $thisfile_monkeysaudio['sample_rate'] = $thisfile_monkeysaudio_raw['nSampleRate']; + if ($thisfile_monkeysaudio['sample_rate'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt MAC file: frequency == zero'; + return false; + } + $ThisFileInfo['audio']['sample_rate'] = $thisfile_monkeysaudio['sample_rate']; + if ($thisfile_monkeysaudio['flags']['peak_level']) { + $thisfile_monkeysaudio['peak_level'] = $thisfile_monkeysaudio_raw['nPeakLevel']; + $thisfile_monkeysaudio['peak_ratio'] = $thisfile_monkeysaudio['peak_level'] / pow(2, $thisfile_monkeysaudio['bits_per_sample'] - 1); + } + if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) { + $thisfile_monkeysaudio['samples'] = (($thisfile_monkeysaudio_raw['nTotalFrames'] - 1) * $thisfile_monkeysaudio_raw['nBlocksPerFrame']) + $thisfile_monkeysaudio_raw['nFinalFrameBlocks']; + } else { + $thisfile_monkeysaudio['samples'] = (($thisfile_monkeysaudio_raw['nTotalFrames'] - 1) * $thisfile_monkeysaudio['samples_per_frame']) + $thisfile_monkeysaudio_raw['nFinalFrameSamples']; + } + $thisfile_monkeysaudio['playtime'] = $thisfile_monkeysaudio['samples'] / $thisfile_monkeysaudio['sample_rate']; + if ($thisfile_monkeysaudio['playtime'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt MAC file: playtime == zero'; + return false; + } + $ThisFileInfo['playtime_seconds'] = $thisfile_monkeysaudio['playtime']; + $thisfile_monkeysaudio['compressed_size'] = $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']; + $thisfile_monkeysaudio['uncompressed_size'] = $thisfile_monkeysaudio['samples'] * $thisfile_monkeysaudio['channels'] * ($thisfile_monkeysaudio['bits_per_sample'] / 8); + if ($thisfile_monkeysaudio['uncompressed_size'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt MAC file: uncompressed_size == zero'; + return false; + } + $thisfile_monkeysaudio['compression_ratio'] = $thisfile_monkeysaudio['compressed_size'] / ($thisfile_monkeysaudio['uncompressed_size'] + $thisfile_monkeysaudio_raw['nHeaderDataBytes']); + $thisfile_monkeysaudio['bitrate'] = (($thisfile_monkeysaudio['samples'] * $thisfile_monkeysaudio['channels'] * $thisfile_monkeysaudio['bits_per_sample']) / $thisfile_monkeysaudio['playtime']) * $thisfile_monkeysaudio['compression_ratio']; + $ThisFileInfo['audio']['bitrate'] = $thisfile_monkeysaudio['bitrate']; + + // add size of MAC header to avdataoffset + if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) { + $ThisFileInfo['avdataoffset'] += $thisfile_monkeysaudio_raw['nDescriptorBytes']; + $ThisFileInfo['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderBytes']; + $ThisFileInfo['avdataoffset'] += $thisfile_monkeysaudio_raw['nSeekTableBytes']; + $ThisFileInfo['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderDataBytes']; + + $ThisFileInfo['avdataend'] -= $thisfile_monkeysaudio_raw['nTerminatingDataBytes']; + } else { + $ThisFileInfo['avdataoffset'] += $offset; + } + + if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) { + if ($thisfile_monkeysaudio_raw['cFileMD5'] === str_repeat("\x00", 16)) { + //$ThisFileInfo['warning'][] = 'cFileMD5 is null'; + } else { + $ThisFileInfo['md5_data_source'] = ''; + $md5 = $thisfile_monkeysaudio_raw['cFileMD5']; + for ($i = 0; $i < strlen($md5); $i++) { + $ThisFileInfo['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT); + } + if (!preg_match('/^[0-9a-f]{32}$/', $ThisFileInfo['md5_data_source'])) { + unset($ThisFileInfo['md5_data_source']); + } + } + } + + + + $ThisFileInfo['audio']['bits_per_sample'] = $thisfile_monkeysaudio['bits_per_sample']; + $ThisFileInfo['audio']['encoder'] = 'MAC v'.number_format($thisfile_monkeysaudio['version'], 2); + $ThisFileInfo['audio']['encoder_options'] = ucfirst($thisfile_monkeysaudio['compression']).' compression'; + + return true; + } + + function MonkeyCompressionLevelNameLookup($compressionlevel) { + static $MonkeyCompressionLevelNameLookup = array( + 0 => 'unknown', + 1000 => 'fast', + 2000 => 'normal', + 3000 => 'high', + 4000 => 'extra-high', + 5000 => 'insane' + ); + return (isset($MonkeyCompressionLevelNameLookup[$compressionlevel]) ? $MonkeyCompressionLevelNameLookup[$compressionlevel] : 'invalid'); + } + + function MonkeySamplesPerFrame($versionid, $compressionlevel) { + if ($versionid >= 3950) { + return 73728 * 4; + } elseif ($versionid >= 3900) { + return 73728; + } elseif (($versionid >= 3800) && ($compressionlevel == 4000)) { + return 73728; + } else { + return 9216; + } + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.mp3.php b/modules/id3/getid3/module.audio.mp3.php new file mode 100644 index 00000000..d7b0b77a --- /dev/null +++ b/modules/id3/getid3/module.audio.mp3.php @@ -0,0 +1,1944 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.mp3.php // +// module for analyzing MP3 files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +// number of frames to scan to determine if MPEG-audio sequence is valid +// Lower this number to 5-20 for faster scanning +// Increase this number to 50+ for most accurate detection of valid VBR/CBR +// mpeg-audio streams +define('GETID3_MP3_VALID_CHECK_FRAMES', 35); + + +class getid3_mp3 +{ + + var $allow_bruteforce = false; // forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow, unrecommended, but may provide data from otherwise-unusuable files + + function getid3_mp3(&$fd, &$ThisFileInfo) { + + if (!$this->getOnlyMPEGaudioInfo($fd, $ThisFileInfo, $ThisFileInfo['avdataoffset'])) { + if ($this->allow_bruteforce) { + $ThisFileInfo['error'][] = 'Rescanning file in BruteForce mode'; + $this->getOnlyMPEGaudioInfoBruteForce($fd, $ThisFileInfo); + } + } + + + if (isset($ThisFileInfo['mpeg']['audio']['bitrate_mode'])) { + $ThisFileInfo['audio']['bitrate_mode'] = strtolower($ThisFileInfo['mpeg']['audio']['bitrate_mode']); + } + + if (((isset($ThisFileInfo['id3v2']['headerlength']) && ($ThisFileInfo['avdataoffset'] > $ThisFileInfo['id3v2']['headerlength'])) || (!isset($ThisFileInfo['id3v2']) && ($ThisFileInfo['avdataoffset'] > 0)))) { + + $synchoffsetwarning = 'Unknown data before synch '; + if (isset($ThisFileInfo['id3v2']['headerlength'])) { + $synchoffsetwarning .= '(ID3v2 header ends at '.$ThisFileInfo['id3v2']['headerlength'].', then '.($ThisFileInfo['avdataoffset'] - $ThisFileInfo['id3v2']['headerlength']).' bytes garbage, '; + } else { + $synchoffsetwarning .= '(should be at beginning of file, '; + } + $synchoffsetwarning .= 'synch detected at '.$ThisFileInfo['avdataoffset'].')'; + if ($ThisFileInfo['audio']['bitrate_mode'] == 'cbr') { + + if (!empty($ThisFileInfo['id3v2']['headerlength']) && (($ThisFileInfo['avdataoffset'] - $ThisFileInfo['id3v2']['headerlength']) == $ThisFileInfo['mpeg']['audio']['framelength'])) { + + $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90-3.92) DLL in CBR mode.'; + $ThisFileInfo['audio']['codec'] = 'LAME'; + $CurrentDataLAMEversionString = 'LAME3.'; + + } elseif (empty($ThisFileInfo['id3v2']['headerlength']) && ($ThisFileInfo['avdataoffset'] == $ThisFileInfo['mpeg']['audio']['framelength'])) { + + $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90 - 3.92) DLL in CBR mode.'; + $ThisFileInfo['audio']['codec'] = 'LAME'; + $CurrentDataLAMEversionString = 'LAME3.'; + + } + + } + $ThisFileInfo['warning'][] = $synchoffsetwarning; + + } + + if (isset($ThisFileInfo['mpeg']['audio']['LAME'])) { + $ThisFileInfo['audio']['codec'] = 'LAME'; + if (!empty($ThisFileInfo['mpeg']['audio']['LAME']['long_version'])) { + $ThisFileInfo['audio']['encoder'] = rtrim($ThisFileInfo['mpeg']['audio']['LAME']['long_version'], "\x00"); + } elseif (!empty($ThisFileInfo['mpeg']['audio']['LAME']['short_version'])) { + $ThisFileInfo['audio']['encoder'] = rtrim($ThisFileInfo['mpeg']['audio']['LAME']['short_version'], "\x00"); + } + } + + $CurrentDataLAMEversionString = (!empty($CurrentDataLAMEversionString) ? $CurrentDataLAMEversionString : @$ThisFileInfo['audio']['encoder']); + if (!empty($CurrentDataLAMEversionString) && (substr($CurrentDataLAMEversionString, 0, 6) == 'LAME3.') && !preg_match('[0-9\)]', substr($CurrentDataLAMEversionString, -1))) { + // a version number of LAME that does not end with a number like "LAME3.92" + // or with a closing parenthesis like "LAME3.88 (alpha)" + // or a version of LAME with the LAMEtag-not-filled-in-DLL-mode bug (3.90-3.92) + + // not sure what the actual last frame length will be, but will be less than or equal to 1441 + $PossiblyLongerLAMEversion_FrameLength = 1441; + + // Not sure what version of LAME this is - look in padding of last frame for longer version string + $PossibleLAMEversionStringOffset = $ThisFileInfo['avdataend'] - $PossiblyLongerLAMEversion_FrameLength; + fseek($fd, $PossibleLAMEversionStringOffset); + $PossiblyLongerLAMEversion_Data = fread($fd, $PossiblyLongerLAMEversion_FrameLength); + switch (substr($CurrentDataLAMEversionString, -1)) { + case 'a': + case 'b': + // "LAME3.94a" will have a longer version string of "LAME3.94 (alpha)" for example + // need to trim off "a" to match longer string + $CurrentDataLAMEversionString = substr($CurrentDataLAMEversionString, 0, -1); + break; + } + if (($PossiblyLongerLAMEversion_String = strstr($PossiblyLongerLAMEversion_Data, $CurrentDataLAMEversionString)) !== false) { + if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) { + $PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3" "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)" + if (strlen($PossiblyLongerLAMEversion_NewString) > strlen(@$ThisFileInfo['audio']['encoder'])) { + $ThisFileInfo['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString; + } + } + } + } + if (!empty($ThisFileInfo['audio']['encoder'])) { + $ThisFileInfo['audio']['encoder'] = rtrim($ThisFileInfo['audio']['encoder'], "\x00 "); + } + + switch (@$ThisFileInfo['mpeg']['audio']['layer']) { + case 1: + case 2: + $ThisFileInfo['audio']['dataformat'] = 'mp'.$ThisFileInfo['mpeg']['audio']['layer']; + break; + } + if ($ThisFileInfo['fileformat'] == 'mp3') { + switch ($ThisFileInfo['audio']['dataformat']) { + case 'mp1': + case 'mp2': + case 'mp3': + $ThisFileInfo['fileformat'] = $ThisFileInfo['audio']['dataformat']; + break; + + default: + $ThisFileInfo['warning'][] = 'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$ThisFileInfo['audio']['dataformat'].'"'; + break; + } + } + + if (empty($ThisFileInfo['fileformat'])) { + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['audio']['bitrate_mode']); + unset($ThisFileInfo['avdataoffset']); + unset($ThisFileInfo['avdataend']); + return false; + } + + $ThisFileInfo['mime_type'] = 'audio/mpeg'; + $ThisFileInfo['audio']['lossless'] = false; + + // Calculate playtime + if (!isset($ThisFileInfo['playtime_seconds']) && isset($ThisFileInfo['audio']['bitrate']) && ($ThisFileInfo['audio']['bitrate'] > 0)) { + $ThisFileInfo['playtime_seconds'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8 / $ThisFileInfo['audio']['bitrate']; + } + + $ThisFileInfo['audio']['encoder_options'] = $this->GuessEncoderOptions($ThisFileInfo); + + return true; + } + + + function GuessEncoderOptions(&$ThisFileInfo) { + // shortcuts + if (!empty($ThisFileInfo['mpeg']['audio'])) { + $thisfile_mpeg_audio = &$ThisFileInfo['mpeg']['audio']; + if (!empty($thisfile_mpeg_audio['LAME'])) { + $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME']; + } + } + + $encoder_options = ''; + static $NamedPresetBitrates = array(16, 24, 40, 56, 112, 128, 160, 192, 256); + + if ((@$thisfile_mpeg_audio['VBR_method'] == 'Fraunhofer') && !empty($thisfile_mpeg_audio['VBR_quality'])) { + + $encoder_options = 'VBR q'.$thisfile_mpeg_audio['VBR_quality']; + + } elseif (!empty($thisfile_mpeg_audio_lame['preset_used']) && (!in_array($thisfile_mpeg_audio_lame['preset_used_id'], $NamedPresetBitrates))) { + + $encoder_options = $thisfile_mpeg_audio_lame['preset_used']; + + } elseif (!empty($thisfile_mpeg_audio_lame['vbr_quality'])) { + + static $KnownEncoderValues = array(); + if (empty($KnownEncoderValues)) { + + //$KnownEncoderValues[abrbitrate_minbitrate][vbr_quality][raw_vbr_method][raw_noise_shaping][raw_stereo_mode][ath_type][lowpass_frequency] = 'preset name'; + $KnownEncoderValues[0xFF][58][1][1][3][2][20500] = '--alt-preset insane'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues[0xFF][58][1][1][3][2][20600] = '--alt-preset insane'; // 3.90.2, 3.90.3, 3.91 + $KnownEncoderValues[0xFF][57][1][1][3][4][20500] = '--alt-preset insane'; // 3.94, 3.95 + $KnownEncoderValues['**'][78][3][2][3][2][19500] = '--alt-preset extreme'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues['**'][78][3][2][3][2][19600] = '--alt-preset extreme'; // 3.90.2, 3.91 + $KnownEncoderValues['**'][78][3][1][3][2][19600] = '--alt-preset extreme'; // 3.90.3 + $KnownEncoderValues['**'][78][4][2][3][2][19500] = '--alt-preset fast extreme'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues['**'][78][4][2][3][2][19600] = '--alt-preset fast extreme'; // 3.90.2, 3.90.3, 3.91 + $KnownEncoderValues['**'][78][3][2][3][4][19000] = '--alt-preset standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues['**'][78][3][1][3][4][19000] = '--alt-preset standard'; // 3.90.3 + $KnownEncoderValues['**'][78][4][2][3][4][19000] = '--alt-preset fast standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues['**'][78][4][1][3][4][19000] = '--alt-preset fast standard'; // 3.90.3 + $KnownEncoderValues['**'][88][4][1][3][3][19500] = '--r3mix'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues['**'][88][4][1][3][3][19600] = '--r3mix'; // 3.90.2, 3.90.3, 3.91 + $KnownEncoderValues['**'][67][4][1][3][4][18000] = '--r3mix'; // 3.94, 3.95 + $KnownEncoderValues['**'][68][3][2][3][4][18000] = '--alt-preset medium'; // 3.90.3 + $KnownEncoderValues['**'][68][4][2][3][4][18000] = '--alt-preset fast medium'; // 3.90.3 + + $KnownEncoderValues[0xFF][99][1][1][1][2][0] = '--preset studio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0xFF][58][2][1][3][2][20600] = '--preset studio'; // 3.90.3, 3.93.1 + $KnownEncoderValues[0xFF][58][2][1][3][2][20500] = '--preset studio'; // 3.93 + $KnownEncoderValues[0xFF][57][2][1][3][4][20500] = '--preset studio'; // 3.94, 3.95 + $KnownEncoderValues[0xC0][88][1][1][1][2][0] = '--preset cd'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0xC0][58][2][2][3][2][19600] = '--preset cd'; // 3.90.3, 3.93.1 + $KnownEncoderValues[0xC0][58][2][2][3][2][19500] = '--preset cd'; // 3.93 + $KnownEncoderValues[0xC0][57][2][1][3][4][19500] = '--preset cd'; // 3.94, 3.95 + $KnownEncoderValues[0xA0][78][1][1][3][2][18000] = '--preset hifi'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0xA0][58][2][2][3][2][18000] = '--preset hifi'; // 3.90.3, 3.93, 3.93.1 + $KnownEncoderValues[0xA0][57][2][1][3][4][18000] = '--preset hifi'; // 3.94, 3.95 + $KnownEncoderValues[0x80][67][1][1][3][2][18000] = '--preset tape'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0x80][67][1][1][3][2][15000] = '--preset radio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0x70][67][1][1][3][2][15000] = '--preset fm'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92 + $KnownEncoderValues[0x70][58][2][2][3][2][16000] = '--preset tape/radio/fm'; // 3.90.3, 3.93, 3.93.1 + $KnownEncoderValues[0x70][57][2][1][3][4][16000] = '--preset tape/radio/fm'; // 3.94, 3.95 + $KnownEncoderValues[0x38][58][2][2][0][2][10000] = '--preset voice'; // 3.90.3, 3.93, 3.93.1 + $KnownEncoderValues[0x38][57][2][1][0][4][15000] = '--preset voice'; // 3.94, 3.95 + $KnownEncoderValues[0x38][57][2][1][0][4][16000] = '--preset voice'; // 3.94a14 + $KnownEncoderValues[0x28][65][1][1][0][2][7500] = '--preset mw-us'; // 3.90, 3.90.1, 3.92 + $KnownEncoderValues[0x28][65][1][1][0][2][7600] = '--preset mw-us'; // 3.90.2, 3.91 + $KnownEncoderValues[0x28][58][2][2][0][2][7000] = '--preset mw-us'; // 3.90.3, 3.93, 3.93.1 + $KnownEncoderValues[0x28][57][2][1][0][4][10500] = '--preset mw-us'; // 3.94, 3.95 + $KnownEncoderValues[0x28][57][2][1][0][4][11200] = '--preset mw-us'; // 3.94a14 + $KnownEncoderValues[0x28][57][2][1][0][4][8800] = '--preset mw-us'; // 3.94a15 + $KnownEncoderValues[0x18][58][2][2][0][2][4000] = '--preset phon+/lw/mw-eu/sw'; // 3.90.3, 3.93.1 + $KnownEncoderValues[0x18][58][2][2][0][2][3900] = '--preset phon+/lw/mw-eu/sw'; // 3.93 + $KnownEncoderValues[0x18][57][2][1][0][4][5900] = '--preset phon+/lw/mw-eu/sw'; // 3.94, 3.95 + $KnownEncoderValues[0x18][57][2][1][0][4][6200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a14 + $KnownEncoderValues[0x18][57][2][1][0][4][3200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a15 + $KnownEncoderValues[0x10][58][2][2][0][2][3800] = '--preset phone'; // 3.90.3, 3.93.1 + $KnownEncoderValues[0x10][58][2][2][0][2][3700] = '--preset phone'; // 3.93 + $KnownEncoderValues[0x10][57][2][1][0][4][5600] = '--preset phone'; // 3.94, 3.95 + } + + if (isset($KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) { + + $encoder_options = $KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']]; + + } elseif (isset($KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) { + + $encoder_options = $KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']]; + + } elseif ($ThisFileInfo['audio']['bitrate_mode'] == 'vbr') { + + // http://gabriel.mp3-tech.org/mp3infotag.html + // int Quality = (100 - 10 * gfp->VBR_q - gfp->quality)h + + + $LAME_V_value = 10 - ceil($thisfile_mpeg_audio_lame['vbr_quality'] / 10); + $LAME_q_value = 100 - $thisfile_mpeg_audio_lame['vbr_quality'] - ($LAME_V_value * 10); + $encoder_options = '-V'.$LAME_V_value.' -q'.$LAME_q_value; + + } elseif ($ThisFileInfo['audio']['bitrate_mode'] == 'cbr') { + + $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']).ceil($ThisFileInfo['audio']['bitrate'] / 1000); + + } else { + + $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']); + + } + + } elseif (!empty($thisfile_mpeg_audio_lame['bitrate_abr'])) { + + $encoder_options = 'ABR'.$thisfile_mpeg_audio_lame['bitrate_abr']; + + } elseif (!empty($ThisFileInfo['audio']['bitrate'])) { + + if ($ThisFileInfo['audio']['bitrate_mode'] == 'cbr') { + $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']).ceil($ThisFileInfo['audio']['bitrate'] / 1000); + } else { + $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']); + } + + } + if (!empty($thisfile_mpeg_audio_lame['bitrate_min'])) { + $encoder_options .= ' -b'.$thisfile_mpeg_audio_lame['bitrate_min']; + } + + if (@$thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev'] || @$thisfile_mpeg_audio_lame['encoding_flags']['nogap_next']) { + $encoder_options .= ' --nogap'; + } + + if (!empty($thisfile_mpeg_audio_lame['lowpass_frequency'])) { + $ExplodedOptions = explode(' ', $encoder_options, 4); + if ($ExplodedOptions[0] == '--r3mix') { + $ExplodedOptions[1] = 'r3mix'; + } + switch ($ExplodedOptions[0]) { + case '--preset': + case '--alt-preset': + case '--r3mix': + if ($ExplodedOptions[1] == 'fast') { + $ExplodedOptions[1] .= ' '.$ExplodedOptions[2]; + } + switch ($ExplodedOptions[1]) { + case 'portable': + case 'medium': + case 'standard': + case 'extreme': + case 'insane': + case 'fast portable': + case 'fast medium': + case 'fast standard': + case 'fast extreme': + case 'fast insane': + case 'r3mix': + static $ExpectedLowpass = array( + 'insane|20500' => 20500, + 'insane|20600' => 20600, // 3.90.2, 3.90.3, 3.91 + 'medium|18000' => 18000, + 'fast medium|18000' => 18000, + 'extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95 + 'extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1 + 'fast extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95 + 'fast extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1 + 'standard|19000' => 19000, + 'fast standard|19000' => 19000, + 'r3mix|19500' => 19500, // 3.90, 3.90.1, 3.92 + 'r3mix|19600' => 19600, // 3.90.2, 3.90.3, 3.91 + 'r3mix|18000' => 18000); // 3.94, 3.95 + if (!isset($ExpectedLowpass[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio_lame['lowpass_frequency']])) { + $encoder_options .= ' --lowpass '.$thisfile_mpeg_audio_lame['lowpass_frequency']; + } + break; + + default: + break; + } + break; + } + } + + if (isset($thisfile_mpeg_audio_lame['raw']['source_sample_freq'])) { + if (($thisfile_mpeg_audio['sample_rate'] == 44100) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 1)) { + $encoder_options .= ' --resample 44100'; + } elseif (($thisfile_mpeg_audio['sample_rate'] == 48000) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 2)) { + $encoder_options .= ' --resample 48000'; + } elseif ($thisfile_mpeg_audio['sample_rate'] < 44100) { + switch ($thisfile_mpeg_audio_lame['raw']['source_sample_freq']) { + case 0: // <= 32000 + // may or may not be same as source frequency - ignore + break; + case 1: // 44100 + case 2: // 48000 + case 3: // 48000+ + $ExplodedOptions = explode(' ', $encoder_options, 4); + switch ($ExplodedOptions[0]) { + case '--preset': + case '--alt-preset': + switch ($ExplodedOptions[1]) { + case 'fast': + case 'portable': + case 'medium': + case 'standard': + case 'extreme': + case 'insane': + $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate']; + break; + + default: + static $ExpectedResampledRate = array( + 'phon+/lw/mw-eu/sw|16000' => 16000, + 'mw-us|24000' => 24000, // 3.95 + 'mw-us|32000' => 32000, // 3.93 + 'mw-us|16000' => 16000, // 3.92 + 'phone|16000' => 16000, + 'phone|11025' => 11025, // 3.94a15 + 'radio|32000' => 32000, // 3.94a15 + 'fm/radio|32000' => 32000, // 3.92 + 'fm|32000' => 32000, // 3.90 + 'voice|32000' => 32000); + if (!isset($ExpectedResampledRate[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio['sample_rate']])) { + $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate']; + } + break; + } + break; + + case '--r3mix': + default: + $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate']; + break; + } + break; + } + } + } + if (empty($encoder_options) && !empty($ThisFileInfo['audio']['bitrate']) && !empty($ThisFileInfo['audio']['bitrate_mode'])) { + //$encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']).ceil($ThisFileInfo['audio']['bitrate'] / 1000); + $encoder_options = strtoupper($ThisFileInfo['audio']['bitrate_mode']); + } + + return $encoder_options; + } + + + function decodeMPEGaudioHeader($fd, $offset, &$ThisFileInfo, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) { + + static $MPEGaudioVersionLookup; + static $MPEGaudioLayerLookup; + static $MPEGaudioBitrateLookup; + static $MPEGaudioFrequencyLookup; + static $MPEGaudioChannelModeLookup; + static $MPEGaudioModeExtensionLookup; + static $MPEGaudioEmphasisLookup; + if (empty($MPEGaudioVersionLookup)) { + $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray(); + $MPEGaudioFrequencyLookup = getid3_mp3::MPEGaudioFrequencyArray(); + $MPEGaudioChannelModeLookup = getid3_mp3::MPEGaudioChannelModeArray(); + $MPEGaudioModeExtensionLookup = getid3_mp3::MPEGaudioModeExtensionArray(); + $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray(); + } + + if ($offset >= $ThisFileInfo['avdataend']) { + $ThisFileInfo['error'][] = 'end of file encounter looking for MPEG synch'; + return false; + } + fseek($fd, $offset, SEEK_SET); + //$headerstring = fread($fd, 1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame + $headerstring = fread($fd, 226); // LAME header at offset 36 + 190 bytes of Xing/LAME data + + // MP3 audio frame structure: + // $aa $aa $aa $aa [$bb $bb] $cc... + // where $aa..$aa is the four-byte mpeg-audio header (below) + // $bb $bb is the optional 2-byte CRC + // and $cc... is the audio data + + $head4 = substr($headerstring, 0, 4); + + static $MPEGaudioHeaderDecodeCache = array(); + if (isset($MPEGaudioHeaderDecodeCache[$head4])) { + $MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4]; + } else { + $MPEGheaderRawArray = getid3_mp3::MPEGaudioHeaderDecode($head4); + $MPEGaudioHeaderDecodeCache[$head4] = $MPEGheaderRawArray; + } + + static $MPEGaudioHeaderValidCache = array(); + + // Not in cache + if (!isset($MPEGaudioHeaderValidCache[$head4])) { + //$MPEGaudioHeaderValidCache[$head4] = getid3_mp3::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true); // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1) + $MPEGaudioHeaderValidCache[$head4] = getid3_mp3::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false); + } + + // shortcut + if (!isset($ThisFileInfo['mpeg']['audio'])) { + $ThisFileInfo['mpeg']['audio'] = array(); + } + $thisfile_mpeg_audio = &$ThisFileInfo['mpeg']['audio']; + + + if ($MPEGaudioHeaderValidCache[$head4]) { + $thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray; + } else { + $ThisFileInfo['error'][] = 'Invalid MPEG audio header at offset '.$offset; + return false; + } + + if (!$FastMPEGheaderScan) { + + $thisfile_mpeg_audio['version'] = $MPEGaudioVersionLookup[$thisfile_mpeg_audio['raw']['version']]; + $thisfile_mpeg_audio['layer'] = $MPEGaudioLayerLookup[$thisfile_mpeg_audio['raw']['layer']]; + + $thisfile_mpeg_audio['channelmode'] = $MPEGaudioChannelModeLookup[$thisfile_mpeg_audio['raw']['channelmode']]; + $thisfile_mpeg_audio['channels'] = (($thisfile_mpeg_audio['channelmode'] == 'mono') ? 1 : 2); + $thisfile_mpeg_audio['sample_rate'] = $MPEGaudioFrequencyLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['raw']['sample_rate']]; + $thisfile_mpeg_audio['protection'] = !$thisfile_mpeg_audio['raw']['protection']; + $thisfile_mpeg_audio['private'] = (bool) $thisfile_mpeg_audio['raw']['private']; + $thisfile_mpeg_audio['modeextension'] = $MPEGaudioModeExtensionLookup[$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['modeextension']]; + $thisfile_mpeg_audio['copyright'] = (bool) $thisfile_mpeg_audio['raw']['copyright']; + $thisfile_mpeg_audio['original'] = (bool) $thisfile_mpeg_audio['raw']['original']; + $thisfile_mpeg_audio['emphasis'] = $MPEGaudioEmphasisLookup[$thisfile_mpeg_audio['raw']['emphasis']]; + + $ThisFileInfo['audio']['channels'] = $thisfile_mpeg_audio['channels']; + $ThisFileInfo['audio']['sample_rate'] = $thisfile_mpeg_audio['sample_rate']; + + if ($thisfile_mpeg_audio['protection']) { + $thisfile_mpeg_audio['crc'] = getid3_lib::BigEndian2Int(substr($headerstring, 4, 2)); + } + + } + + if ($thisfile_mpeg_audio['raw']['bitrate'] == 15) { + // http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0 + $ThisFileInfo['warning'][] = 'Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1'; + $thisfile_mpeg_audio['raw']['bitrate'] = 0; + } + $thisfile_mpeg_audio['padding'] = (bool) $thisfile_mpeg_audio['raw']['padding']; + $thisfile_mpeg_audio['bitrate'] = $MPEGaudioBitrateLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['bitrate']]; + + if (($thisfile_mpeg_audio['bitrate'] == 'free') && ($offset == $ThisFileInfo['avdataoffset'])) { + // only skip multiple frame check if free-format bitstream found at beginning of file + // otherwise is quite possibly simply corrupted data + $recursivesearch = false; + } + + // For Layer 2 there are some combinations of bitrate and mode which are not allowed. + if (!$FastMPEGheaderScan && ($thisfile_mpeg_audio['layer'] == '2')) { + + $ThisFileInfo['audio']['dataformat'] = 'mp2'; + switch ($thisfile_mpeg_audio['channelmode']) { + + case 'mono': + if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] <= 192000)) { + // these are ok + } else { + $ThisFileInfo['error'][] = $thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; + return false; + } + break; + + case 'stereo': + case 'joint stereo': + case 'dual channel': + if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] == 64000) || ($thisfile_mpeg_audio['bitrate'] >= 96000)) { + // these are ok + } else { + $ThisFileInfo['error'][] = intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.'; + return false; + } + break; + + } + + } + + + if ($ThisFileInfo['audio']['sample_rate'] > 0) { + $thisfile_mpeg_audio['framelength'] = getid3_mp3::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $ThisFileInfo['audio']['sample_rate']); + } + + $nextframetestoffset = $offset + 1; + if ($thisfile_mpeg_audio['bitrate'] != 'free') { + + $ThisFileInfo['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; + + if (isset($thisfile_mpeg_audio['framelength'])) { + $nextframetestoffset = $offset + $thisfile_mpeg_audio['framelength']; + } else { + $ThisFileInfo['error'][] = 'Frame at offset('.$offset.') is has an invalid frame length.'; + return false; + } + + } + + $ExpectedNumberOfAudioBytes = 0; + + //////////////////////////////////////////////////////////////////////////////////// + // Variable-bitrate headers + + if (substr($headerstring, 4 + 32, 4) == 'VBRI') { + // Fraunhofer VBR header is hardcoded 'VBRI' at offset 0x24 (36) + // specs taken from http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html + + $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; + $thisfile_mpeg_audio['VBR_method'] = 'Fraunhofer'; + $ThisFileInfo['audio']['codec'] = 'Fraunhofer'; + + $SideInfoData = substr($headerstring, 4 + 2, 32); + + $FraunhoferVBROffset = 36; + + $thisfile_mpeg_audio['VBR_encoder_version'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 4, 2)); // VbriVersion + $thisfile_mpeg_audio['VBR_encoder_delay'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 6, 2)); // VbriDelay + $thisfile_mpeg_audio['VBR_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 8, 2)); // VbriQuality + $thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 10, 4)); // VbriStreamBytes + $thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 14, 4)); // VbriStreamFrames + $thisfile_mpeg_audio['VBR_seek_offsets'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 18, 2)); // VbriTableSize + $thisfile_mpeg_audio['VBR_seek_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 20, 2)); // VbriTableScale + $thisfile_mpeg_audio['VBR_entry_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 22, 2)); // VbriEntryBytes + $thisfile_mpeg_audio['VBR_entry_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 24, 2)); // VbriEntryFrames + + $ExpectedNumberOfAudioBytes = $thisfile_mpeg_audio['VBR_bytes']; + + $previousbyteoffset = $offset; + for ($i = 0; $i < $thisfile_mpeg_audio['VBR_seek_offsets']; $i++) { + $Fraunhofer_OffsetN = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset, $thisfile_mpeg_audio['VBR_entry_bytes'])); + $FraunhoferVBROffset += $thisfile_mpeg_audio['VBR_entry_bytes']; + $thisfile_mpeg_audio['VBR_offsets_relative'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']); + $thisfile_mpeg_audio['VBR_offsets_absolute'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']) + $previousbyteoffset; + $previousbyteoffset += $Fraunhofer_OffsetN; + } + + + } else { + + // Xing VBR header is hardcoded 'Xing' at a offset 0x0D (13), 0x15 (21) or 0x24 (36) + // depending on MPEG layer and number of channels + + $VBRidOffset = getid3_mp3::XingVBRidOffset($thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['channelmode']); + $SideInfoData = substr($headerstring, 4 + 2, $VBRidOffset - 4); + + if ((substr($headerstring, $VBRidOffset, strlen('Xing')) == 'Xing') || (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Info')) { + // 'Xing' is traditional Xing VBR frame + // 'Info' is LAME-encoded CBR (This was done to avoid CBR files to be recognized as traditional Xing VBR files by some decoders.) + // 'Info' *can* legally be used to specify a VBR file as well, however. + + // http://www.multiweb.cz/twoinches/MP3inside.htm + //00..03 = "Xing" or "Info" + //04..07 = Flags: + // 0x01 Frames Flag set if value for number of frames in file is stored + // 0x02 Bytes Flag set if value for filesize in bytes is stored + // 0x04 TOC Flag set if values for TOC are stored + // 0x08 VBR Scale Flag set if values for VBR scale is stored + //08..11 Frames: Number of frames in file (including the first Xing/Info one) + //12..15 Bytes: File length in Bytes + //16..115 TOC (Table of Contents): + // Contains of 100 indexes (one Byte length) for easier lookup in file. Approximately solves problem with moving inside file. + // Each Byte has a value according this formula: + // (TOC[i] / 256) * fileLenInBytes + // So if song lasts eg. 240 sec. and you want to jump to 60. sec. (and file is 5 000 000 Bytes length) you can use: + // TOC[(60/240)*100] = TOC[25] + // and corresponding Byte in file is then approximately at: + // (TOC[25]/256) * 5000000 + //116..119 VBR Scale + + + // should be safe to leave this at 'vbr' and let it be overriden to 'cbr' if a CBR preset/mode is used by LAME +// if (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Xing') { + $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; + $thisfile_mpeg_audio['VBR_method'] = 'Xing'; +// } else { +// $ScanAsCBR = true; +// $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; +// } + + $thisfile_mpeg_audio['xing_flags_raw'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 4, 4)); + + $thisfile_mpeg_audio['xing_flags']['frames'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000001); + $thisfile_mpeg_audio['xing_flags']['bytes'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000002); + $thisfile_mpeg_audio['xing_flags']['toc'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000004); + $thisfile_mpeg_audio['xing_flags']['vbr_scale'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000008); + + if ($thisfile_mpeg_audio['xing_flags']['frames']) { + $thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 8, 4)); + //$thisfile_mpeg_audio['VBR_frames']--; // don't count header Xing/Info frame + } + if ($thisfile_mpeg_audio['xing_flags']['bytes']) { + $thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 12, 4)); + } + + //if (($thisfile_mpeg_audio['bitrate'] == 'free') && !empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) { + if (!empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) { + + $framelengthfloat = $thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']; + + if ($thisfile_mpeg_audio['layer'] == '1') { + // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 + //$ThisFileInfo['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; + $ThisFileInfo['audio']['bitrate'] = ($framelengthfloat / 4) * $thisfile_mpeg_audio['sample_rate'] * (2 / $ThisFileInfo['audio']['channels']) / 12; + } else { + // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 + //$ThisFileInfo['audio']['bitrate'] = (($framelengthfloat - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; + $ThisFileInfo['audio']['bitrate'] = $framelengthfloat * $thisfile_mpeg_audio['sample_rate'] * (2 / $ThisFileInfo['audio']['channels']) / 144; + } + $thisfile_mpeg_audio['framelength'] = floor($framelengthfloat); + } + + if ($thisfile_mpeg_audio['xing_flags']['toc']) { + $LAMEtocData = substr($headerstring, $VBRidOffset + 16, 100); + for ($i = 0; $i < 100; $i++) { + $thisfile_mpeg_audio['toc'][$i] = ord($LAMEtocData{$i}); + } + } + if ($thisfile_mpeg_audio['xing_flags']['vbr_scale']) { + $thisfile_mpeg_audio['VBR_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 116, 4)); + } + + + // http://gabriel.mp3-tech.org/mp3infotag.html + if (substr($headerstring, $VBRidOffset + 120, 4) == 'LAME') { + + // shortcut + $thisfile_mpeg_audio['LAME'] = array(); + $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME']; + + + $thisfile_mpeg_audio_lame['long_version'] = substr($headerstring, $VBRidOffset + 120, 20); + $thisfile_mpeg_audio_lame['short_version'] = substr($thisfile_mpeg_audio_lame['long_version'], 0, 9); + + if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') { + + // extra 11 chars are not part of version string when LAMEtag present + unset($thisfile_mpeg_audio_lame['long_version']); + + // It the LAME tag was only introduced in LAME v3.90 + // http://www.hydrogenaudio.org/?act=ST&f=15&t=9933 + + // Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html + // are assuming a 'Xing' identifier offset of 0x24, which is the case for + // MPEG-1 non-mono, but not for other combinations + $LAMEtagOffsetContant = $VBRidOffset - 0x24; + + // shortcuts + $thisfile_mpeg_audio_lame['RGAD'] = array('track'=>array(), 'album'=>array()); + $thisfile_mpeg_audio_lame_RGAD = &$thisfile_mpeg_audio_lame['RGAD']; + $thisfile_mpeg_audio_lame_RGAD_track = &$thisfile_mpeg_audio_lame_RGAD['track']; + $thisfile_mpeg_audio_lame_RGAD_album = &$thisfile_mpeg_audio_lame_RGAD['album']; + $thisfile_mpeg_audio_lame['raw'] = array(); + $thisfile_mpeg_audio_lame_raw = &$thisfile_mpeg_audio_lame['raw']; + + // byte $9B VBR Quality + // This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications. + // Actually overwrites original Xing bytes + unset($thisfile_mpeg_audio['VBR_scale']); + $thisfile_mpeg_audio_lame['vbr_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1)); + + // bytes $9C-$A4 Encoder short VersionString + $thisfile_mpeg_audio_lame['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9); + + // byte $A5 Info Tag revision + VBR method + $LAMEtagRevisionVBRmethod = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1)); + + $thisfile_mpeg_audio_lame['tag_revision'] = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4; + $thisfile_mpeg_audio_lame_raw['vbr_method'] = $LAMEtagRevisionVBRmethod & 0x0F; + $thisfile_mpeg_audio_lame['vbr_method'] = getid3_mp3::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']); + $thisfile_mpeg_audio['bitrate_mode'] = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr' + + // byte $A6 Lowpass filter value + $thisfile_mpeg_audio_lame['lowpass_frequency'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100; + + // bytes $A7-$AE Replay Gain + // http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html + // bytes $A7-$AA : 32 bit floating point "Peak signal amplitude" + if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.94b') { + // LAME 3.94a16 and later - 9.23 fixed point + // ie 0x0059E2EE / (2^23) = 5890798 / 8388608 = 0.7022378444671630859375 + $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = (float) ((getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4))) / 8388608); + } else { + // LAME 3.94a15 and earlier - 32-bit floating point + // Actually 3.94a16 will fall in here too and be WRONG, but is hard to detect 3.94a16 vs 3.94a15 + $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = getid3_lib::LittleEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4)); + } + if ($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] == 0) { + unset($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']); + } else { + $thisfile_mpeg_audio_lame_RGAD['peak_db'] = getid3_lib::RGADamplitude2dB($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']); + } + + $thisfile_mpeg_audio_lame_raw['RGAD_track'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2)); + $thisfile_mpeg_audio_lame_raw['RGAD_album'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2)); + + + if ($thisfile_mpeg_audio_lame_raw['RGAD_track'] != 0) { + + $thisfile_mpeg_audio_lame_RGAD_track['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0xE000) >> 13; + $thisfile_mpeg_audio_lame_RGAD_track['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x1C00) >> 10; + $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x0200) >> 9; + $thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x01FF; + $thisfile_mpeg_audio_lame_RGAD_track['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['name']); + $thisfile_mpeg_audio_lame_RGAD_track['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']); + $thisfile_mpeg_audio_lame_RGAD_track['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']); + + if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) { + $ThisFileInfo['replay_gain']['track']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; + } + $ThisFileInfo['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator']; + $ThisFileInfo['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db']; + } else { + unset($thisfile_mpeg_audio_lame_RGAD['track']); + } + if ($thisfile_mpeg_audio_lame_raw['RGAD_album'] != 0) { + + $thisfile_mpeg_audio_lame_RGAD_album['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0xE000) >> 13; + $thisfile_mpeg_audio_lame_RGAD_album['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x1C00) >> 10; + $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x0200) >> 9; + $thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x01FF; + $thisfile_mpeg_audio_lame_RGAD_album['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['name']); + $thisfile_mpeg_audio_lame_RGAD_album['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']); + $thisfile_mpeg_audio_lame_RGAD_album['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']); + + if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) { + $ThisFileInfo['replay_gain']['album']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude']; + } + $ThisFileInfo['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator']; + $ThisFileInfo['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db']; + } else { + unset($thisfile_mpeg_audio_lame_RGAD['album']); + } + if (empty($thisfile_mpeg_audio_lame_RGAD)) { + unset($thisfile_mpeg_audio_lame['RGAD']); + } + + + // byte $AF Encoding flags + ATH Type + $EncodingFlagsATHtype = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1)); + $thisfile_mpeg_audio_lame['encoding_flags']['nspsytune'] = (bool) ($EncodingFlagsATHtype & 0x10); + $thisfile_mpeg_audio_lame['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20); + $thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'] = (bool) ($EncodingFlagsATHtype & 0x40); + $thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev'] = (bool) ($EncodingFlagsATHtype & 0x80); + $thisfile_mpeg_audio_lame['ath_type'] = $EncodingFlagsATHtype & 0x0F; + + // byte $B0 if ABR {specified bitrate} else {minimal bitrate} + $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1)); + if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 2) { // Average BitRate (ABR) + $thisfile_mpeg_audio_lame['bitrate_abr'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']; + } elseif ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { // Constant BitRate (CBR) + // ignore + } elseif ($thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] > 0) { // Variable BitRate (VBR) - minimum bitrate + $thisfile_mpeg_audio_lame['bitrate_min'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']; + } + + // bytes $B1-$B3 Encoder delays + $EncoderDelays = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3)); + $thisfile_mpeg_audio_lame['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12; + $thisfile_mpeg_audio_lame['end_padding'] = $EncoderDelays & 0x000FFF; + + // byte $B4 Misc + $MiscByte = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1)); + $thisfile_mpeg_audio_lame_raw['noise_shaping'] = ($MiscByte & 0x03); + $thisfile_mpeg_audio_lame_raw['stereo_mode'] = ($MiscByte & 0x1C) >> 2; + $thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5; + $thisfile_mpeg_audio_lame_raw['source_sample_freq'] = ($MiscByte & 0xC0) >> 6; + $thisfile_mpeg_audio_lame['noise_shaping'] = $thisfile_mpeg_audio_lame_raw['noise_shaping']; + $thisfile_mpeg_audio_lame['stereo_mode'] = getid3_mp3::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']); + $thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality']; + $thisfile_mpeg_audio_lame['source_sample_freq'] = getid3_mp3::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']); + + // byte $B5 MP3 Gain + $thisfile_mpeg_audio_lame_raw['mp3_gain'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true); + $thisfile_mpeg_audio_lame['mp3_gain_db'] = (getid3_lib::RGADamplitude2dB(2) / 4) * $thisfile_mpeg_audio_lame_raw['mp3_gain']; + $thisfile_mpeg_audio_lame['mp3_gain_factor'] = pow(2, ($thisfile_mpeg_audio_lame['mp3_gain_db'] / 6)); + + // bytes $B6-$B7 Preset and surround info + $PresetSurroundBytes = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2)); + // Reserved = ($PresetSurroundBytes & 0xC000); + $thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800); + $thisfile_mpeg_audio_lame['surround_info'] = getid3_mp3::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']); + $thisfile_mpeg_audio_lame['preset_used_id'] = ($PresetSurroundBytes & 0x07FF); + $thisfile_mpeg_audio_lame['preset_used'] = getid3_mp3::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame); + if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) { + $ThisFileInfo['warning'][] = 'Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org'; + } + if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) { + // this may change if 3.90.4 ever comes out + $thisfile_mpeg_audio_lame['short_version'] = 'LAME3.90.3'; + } + + // bytes $B8-$BB MusicLength + $thisfile_mpeg_audio_lame['audio_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4)); + $ExpectedNumberOfAudioBytes = (($thisfile_mpeg_audio_lame['audio_bytes'] > 0) ? $thisfile_mpeg_audio_lame['audio_bytes'] : $thisfile_mpeg_audio['VBR_bytes']); + + // bytes $BC-$BD MusicCRC + $thisfile_mpeg_audio_lame['music_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2)); + + // bytes $BE-$BF CRC-16 of Info Tag + $thisfile_mpeg_audio_lame['lame_tag_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2)); + + + // LAME CBR + if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { + + $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; + $thisfile_mpeg_audio['bitrate'] = getid3_mp3::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']); + $ThisFileInfo['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate']; + //if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) { + // $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min']; + //} + + } + + } + } + + } else { + + // not Fraunhofer or Xing VBR methods, most likely CBR (but could be VBR with no header) + $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; + if ($recursivesearch) { + $thisfile_mpeg_audio['bitrate_mode'] = 'vbr'; + if (getid3_mp3::RecursiveFrameScanning($fd, $ThisFileInfo, $offset, $nextframetestoffset, true)) { + $recursivesearch = false; + $thisfile_mpeg_audio['bitrate_mode'] = 'cbr'; + } + if ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') { + $ThisFileInfo['warning'][] = 'VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.'; + } + } + + } + + } + + if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']))) { + if ($ExpectedNumberOfAudioBytes > ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])) { + if (($ExpectedNumberOfAudioBytes - ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])) == 1) { + $ThisFileInfo['warning'][] = 'Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)'; + } else { + $ThisFileInfo['warning'][] = 'Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'])).' bytes)'; + } + } else { + if ((($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) { + // $prenullbytefileoffset = ftell($fd); + // fseek($fd, $ThisFileInfo['avdataend'], SEEK_SET); + // $PossibleNullByte = fread($fd, 1); + // fseek($fd, $prenullbytefileoffset, SEEK_SET); + // if ($PossibleNullByte === "\x00") { + $ThisFileInfo['avdataend']--; + // $ThisFileInfo['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored'; + // } else { + // $ThisFileInfo['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' ('.(($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; + // } + } else { + $ThisFileInfo['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']).' ('.(($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)'; + } + } + } + + if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($ThisFileInfo['audio']['bitrate'])) { + if (($offset == $ThisFileInfo['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) { + $framebytelength = getid3_mp3::FreeFormatFrameLength($fd, $offset, $ThisFileInfo, true); + if ($framebytelength > 0) { + $thisfile_mpeg_audio['framelength'] = $framebytelength; + if ($thisfile_mpeg_audio['layer'] == '1') { + // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12 + $ThisFileInfo['audio']['bitrate'] = ((($framebytelength / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12; + } else { + // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144 + $ThisFileInfo['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144; + } + } else { + $ThisFileInfo['error'][] = 'Error calculating frame length of free-format MP3 without Xing/LAME header'; + } + } + } + + if (!empty($thisfile_mpeg_audio['VBR_frames'])) { + switch ($thisfile_mpeg_audio['bitrate_mode']) { + case 'vbr': + case 'abr': + if (($thisfile_mpeg_audio['version'] == '1') && ($thisfile_mpeg_audio['layer'] == 1)) { + $thisfile_mpeg_audio['VBR_bitrate'] = (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 384); + } elseif ((($thisfile_mpeg_audio['version'] == '2') || ($thisfile_mpeg_audio['version'] == '2.5')) && ($thisfile_mpeg_audio['layer'] == 3)) { + $thisfile_mpeg_audio['VBR_bitrate'] = (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 576); + } else { + $thisfile_mpeg_audio['VBR_bitrate'] = (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($ThisFileInfo['audio']['sample_rate'] / 1152); + } + if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) { + $ThisFileInfo['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; + $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion + } + break; + } + } + + // End variable-bitrate headers + //////////////////////////////////////////////////////////////////////////////////// + + if ($recursivesearch) { + + if (!getid3_mp3::RecursiveFrameScanning($fd, $ThisFileInfo, $offset, $nextframetestoffset, $ScanAsCBR)) { + return false; + } + + } + + + //if (false) { + // // experimental side info parsing section - not returning anything useful yet + // + // $SideInfoBitstream = getid3_lib::BigEndian2Bin($SideInfoData); + // $SideInfoOffset = 0; + // + // if ($thisfile_mpeg_audio['version'] == '1') { + // if ($thisfile_mpeg_audio['channelmode'] == 'mono') { + // // MPEG-1 (mono) + // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9); + // $SideInfoOffset += 9; + // $SideInfoOffset += 5; + // } else { + // // MPEG-1 (stereo, joint-stereo, dual-channel) + // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9); + // $SideInfoOffset += 9; + // $SideInfoOffset += 3; + // } + // } else { // 2 or 2.5 + // if ($thisfile_mpeg_audio['channelmode'] == 'mono') { + // // MPEG-2, MPEG-2.5 (mono) + // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8); + // $SideInfoOffset += 8; + // $SideInfoOffset += 1; + // } else { + // // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel) + // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8); + // $SideInfoOffset += 8; + // $SideInfoOffset += 2; + // } + // } + // + // if ($thisfile_mpeg_audio['version'] == '1') { + // for ($channel = 0; $channel < $ThisFileInfo['audio']['channels']; $channel++) { + // for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) { + // $thisfile_mpeg_audio['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 2; + // } + // } + // } + // for ($granule = 0; $granule < (($thisfile_mpeg_audio['version'] == '1') ? 2 : 1); $granule++) { + // for ($channel = 0; $channel < $ThisFileInfo['audio']['channels']; $channel++) { + // $thisfile_mpeg_audio['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12); + // $SideInfoOffset += 12; + // $thisfile_mpeg_audio['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9); + // $SideInfoOffset += 9; + // $thisfile_mpeg_audio['global_gain'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 8); + // $SideInfoOffset += 8; + // if ($thisfile_mpeg_audio['version'] == '1') { + // $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4); + // $SideInfoOffset += 4; + // } else { + // $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9); + // $SideInfoOffset += 9; + // } + // $thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // + // if ($thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] == '1') { + // + // $thisfile_mpeg_audio['block_type'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 2); + // $SideInfoOffset += 2; + // $thisfile_mpeg_audio['mixed_block_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // + // for ($region = 0; $region < 2; $region++) { + // $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5); + // $SideInfoOffset += 5; + // } + // $thisfile_mpeg_audio['table_select'][$granule][$channel][2] = 0; + // + // for ($window = 0; $window < 3; $window++) { + // $thisfile_mpeg_audio['subblock_gain'][$granule][$channel][$window] = substr($SideInfoBitstream, $SideInfoOffset, 3); + // $SideInfoOffset += 3; + // } + // + // } else { + // + // for ($region = 0; $region < 3; $region++) { + // $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5); + // $SideInfoOffset += 5; + // } + // + // $thisfile_mpeg_audio['region0_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4); + // $SideInfoOffset += 4; + // $thisfile_mpeg_audio['region1_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 3); + // $SideInfoOffset += 3; + // $thisfile_mpeg_audio['block_type'][$granule][$channel] = 0; + // } + // + // if ($thisfile_mpeg_audio['version'] == '1') { + // $thisfile_mpeg_audio['preflag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // } + // $thisfile_mpeg_audio['scalefac_scale'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // $thisfile_mpeg_audio['count1table_select'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1); + // $SideInfoOffset += 1; + // } + // } + //} + + return true; + } + + function RecursiveFrameScanning(&$fd, &$ThisFileInfo, &$offset, &$nextframetestoffset, $ScanAsCBR) { + for ($i = 0; $i < GETID3_MP3_VALID_CHECK_FRAMES; $i++) { + // check next GETID3_MP3_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch + if (($nextframetestoffset + 4) >= $ThisFileInfo['avdataend']) { + // end of file + return true; + } + + $nextframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$ThisFileInfo['avdataend'], 'avdataoffset'=>$ThisFileInfo['avdataoffset']); + if (getid3_mp3::decodeMPEGaudioHeader($fd, $nextframetestoffset, $nextframetestarray, false)) { + if ($ScanAsCBR) { + // force CBR mode, used for trying to pick out invalid audio streams with + // valid(?) VBR headers, or VBR streams with no VBR header + if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($ThisFileInfo['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $ThisFileInfo['mpeg']['audio']['bitrate'])) { + return false; + } + } + + + // next frame is OK, get ready to check the one after that + if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) { + $nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength']; + } else { + $ThisFileInfo['error'][] = 'Frame at offset ('.$offset.') is has an invalid frame length.'; + return false; + } + + } else { + + // next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence + $ThisFileInfo['error'][] = 'Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.'; + + return false; + } + } + return true; + } + + function FreeFormatFrameLength($fd, $offset, &$ThisFileInfo, $deepscan=false) { + fseek($fd, $offset, SEEK_SET); + $MPEGaudioData = fread($fd, 32768); + + $SyncPattern1 = substr($MPEGaudioData, 0, 4); + // may be different pattern due to padding + $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) | 0x02).$SyncPattern1{3}; + if ($SyncPattern2 === $SyncPattern1) { + $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) & 0xFD).$SyncPattern1{3}; + } + + $framelength = false; + $framelength1 = strpos($MPEGaudioData, $SyncPattern1, 4); + $framelength2 = strpos($MPEGaudioData, $SyncPattern2, 4); + if ($framelength1 > 4) { + $framelength = $framelength1; + } + if (($framelength2 > 4) && ($framelength2 < $framelength1)) { + $framelength = $framelength2; + } + if (!$framelength) { + + // LAME 3.88 has a different value for modeextension on the first frame vs the rest + $framelength1 = strpos($MPEGaudioData, substr($SyncPattern1, 0, 3), 4); + $framelength2 = strpos($MPEGaudioData, substr($SyncPattern2, 0, 3), 4); + + if ($framelength1 > 4) { + $framelength = $framelength1; + } + if (($framelength2 > 4) && ($framelength2 < $framelength1)) { + $framelength = $framelength2; + } + if (!$framelength) { + $ThisFileInfo['error'][] = 'Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset; + return false; + } else { + $ThisFileInfo['warning'][] = 'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)'; + $ThisFileInfo['audio']['codec'] = 'LAME'; + $ThisFileInfo['audio']['encoder'] = 'LAME3.88'; + $SyncPattern1 = substr($SyncPattern1, 0, 3); + $SyncPattern2 = substr($SyncPattern2, 0, 3); + } + } + + if ($deepscan) { + + $ActualFrameLengthValues = array(); + $nextoffset = $offset + $framelength; + while ($nextoffset < ($ThisFileInfo['avdataend'] - 6)) { + fseek($fd, $nextoffset - 1, SEEK_SET); + $NextSyncPattern = fread($fd, 6); + if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) { + // good - found where expected + $ActualFrameLengthValues[] = $framelength; + } elseif ((substr($NextSyncPattern, 0, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 0, strlen($SyncPattern2)) == $SyncPattern2)) { + // ok - found one byte earlier than expected (last frame wasn't padded, first frame was) + $ActualFrameLengthValues[] = ($framelength - 1); + $nextoffset--; + } elseif ((substr($NextSyncPattern, 2, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 2, strlen($SyncPattern2)) == $SyncPattern2)) { + // ok - found one byte later than expected (last frame was padded, first frame wasn't) + $ActualFrameLengthValues[] = ($framelength + 1); + $nextoffset++; + } else { + $ThisFileInfo['error'][] = 'Did not find expected free-format sync pattern at offset '.$nextoffset; + return false; + } + $nextoffset += $framelength; + } + if (count($ActualFrameLengthValues) > 0) { + $framelength = intval(round(array_sum($ActualFrameLengthValues) / count($ActualFrameLengthValues))); + } + } + return $framelength; + } + + function getOnlyMPEGaudioInfoBruteForce($fd, &$ThisFileInfo) { + + $MPEGaudioHeaderDecodeCache = array(); + $MPEGaudioHeaderValidCache = array(); + $MPEGaudioHeaderLengthCache = array(); + $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray(); + $MPEGaudioFrequencyLookup = getid3_mp3::MPEGaudioFrequencyArray(); + $MPEGaudioChannelModeLookup = getid3_mp3::MPEGaudioChannelModeArray(); + $MPEGaudioModeExtensionLookup = getid3_mp3::MPEGaudioModeExtensionArray(); + $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray(); + $LongMPEGversionLookup = array(); + $LongMPEGlayerLookup = array(); + $LongMPEGbitrateLookup = array(); + $LongMPEGpaddingLookup = array(); + $LongMPEGfrequencyLookup = array(); + + $Distribution['bitrate'] = array(); + $Distribution['frequency'] = array(); + $Distribution['layer'] = array(); + $Distribution['version'] = array(); + $Distribution['padding'] = array(); + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + + $previousvalidframe = $ThisFileInfo['avdataoffset']; + while (ftell($fd) < $ThisFileInfo['avdataend']) { + set_time_limit(30); + $head4 = fread($fd, 4); + if (strlen($head4) < 4) { + break; + } + if ($head4{0} != "\xFF") { + for ($i = 1; $i < 4; $i++) { + if ($head4{$i} == "\xFF") { + fseek($fd, $i - 4, SEEK_CUR); + continue 2; + } + } + continue; + } + if (!isset($MPEGaudioHeaderDecodeCache[$head4])) { + $MPEGaudioHeaderDecodeCache[$head4] = getid3_mp3::MPEGaudioHeaderDecode($head4); + } + if (!isset($MPEGaudioHeaderValidCache[$head4])) { + $MPEGaudioHeaderValidCache[$head4] = getid3_mp3::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$head4], false, false); + } + if ($MPEGaudioHeaderValidCache[$head4]) { + + if (!isset($MPEGaudioHeaderLengthCache[$head4])) { + $LongMPEGversionLookup[$head4] = $MPEGaudioVersionLookup[$MPEGaudioHeaderDecodeCache[$head4]['version']]; + $LongMPEGlayerLookup[$head4] = $MPEGaudioLayerLookup[$MPEGaudioHeaderDecodeCache[$head4]['layer']]; + $LongMPEGbitrateLookup[$head4] = $MPEGaudioBitrateLookup[$LongMPEGversionLookup[$head4]][$LongMPEGlayerLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['bitrate']]; + $LongMPEGpaddingLookup[$head4] = (bool) $MPEGaudioHeaderDecodeCache[$head4]['padding']; + $LongMPEGfrequencyLookup[$head4] = $MPEGaudioFrequencyLookup[$LongMPEGversionLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['sample_rate']]; + $MPEGaudioHeaderLengthCache[$head4] = getid3_mp3::MPEGaudioFrameLength( + $LongMPEGbitrateLookup[$head4], + $LongMPEGversionLookup[$head4], + $LongMPEGlayerLookup[$head4], + $LongMPEGpaddingLookup[$head4], + $LongMPEGfrequencyLookup[$head4]); + } + if ($MPEGaudioHeaderLengthCache[$head4] > 4) { + $WhereWeWere = ftell($fd); + fseek($fd, $MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR); + $next4 = fread($fd, 4); + if ($next4{0} == "\xFF") { + if (!isset($MPEGaudioHeaderDecodeCache[$next4])) { + $MPEGaudioHeaderDecodeCache[$next4] = getid3_mp3::MPEGaudioHeaderDecode($next4); + } + if (!isset($MPEGaudioHeaderValidCache[$next4])) { + $MPEGaudioHeaderValidCache[$next4] = getid3_mp3::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false); + } + if ($MPEGaudioHeaderValidCache[$next4]) { + fseek($fd, -4, SEEK_CUR); + + @$Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]++; + @$Distribution['layer'][$LongMPEGlayerLookup[$head4]]++; + @$Distribution['version'][$LongMPEGversionLookup[$head4]]++; + @$Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]++; + @$Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]++; + continue; + } + } + unset($next4); + fseek($fd, $WhereWeWere - 3, SEEK_SET); + } + + } + } + foreach ($Distribution as $key => $value) { + ksort($Distribution[$key], SORT_NUMERIC); + } + ksort($Distribution['version'], SORT_STRING); + $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = $Distribution['bitrate']; + $ThisFileInfo['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency']; + $ThisFileInfo['mpeg']['audio']['layer_distribution'] = $Distribution['layer']; + $ThisFileInfo['mpeg']['audio']['version_distribution'] = $Distribution['version']; + $ThisFileInfo['mpeg']['audio']['padding_distribution'] = $Distribution['padding']; + if (count($Distribution['version']) > 1) { + $ThisFileInfo['error'][] = 'Corrupt file - more than one MPEG version detected'; + } + if (count($Distribution['layer']) > 1) { + $ThisFileInfo['error'][] = 'Corrupt file - more than one MPEG layer detected'; + } + if (count($Distribution['frequency']) > 1) { + $ThisFileInfo['error'][] = 'Corrupt file - more than one MPEG sample rate detected'; + } + + + $bittotal = 0; + foreach ($Distribution['bitrate'] as $bitratevalue => $bitratecount) { + if ($bitratevalue != 'free') { + $bittotal += ($bitratevalue * $bitratecount); + } + } + $ThisFileInfo['mpeg']['audio']['frame_count'] = array_sum($Distribution['bitrate']); + if ($ThisFileInfo['mpeg']['audio']['frame_count'] == 0) { + $ThisFileInfo['error'][] = 'no MPEG audio frames found'; + return false; + } + $ThisFileInfo['mpeg']['audio']['bitrate'] = ($bittotal / $ThisFileInfo['mpeg']['audio']['frame_count']); + $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr'); + $ThisFileInfo['mpeg']['audio']['sample_rate'] = getid3_lib::array_max($Distribution['frequency'], true); + + $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; + $ThisFileInfo['audio']['bitrate_mode'] = $ThisFileInfo['mpeg']['audio']['bitrate_mode']; + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; + $ThisFileInfo['audio']['dataformat'] = 'mp'.getid3_lib::array_max($Distribution['layer'], true); + $ThisFileInfo['fileformat'] = $ThisFileInfo['audio']['dataformat']; + + return true; + } + + + function getOnlyMPEGaudioInfo($fd, &$ThisFileInfo, $avdataoffset, $BitrateHistogram=false) { + // looks for synch, decodes MPEG audio header + + fseek($fd, $avdataoffset, SEEK_SET); + $header = ''; + $SynchSeekOffset = 0; + + static $MPEGaudioVersionLookup; + static $MPEGaudioLayerLookup; + static $MPEGaudioBitrateLookup; + if (empty($MPEGaudioVersionLookup)) { + $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray(); + + } + + $header_len = strlen($header) - intval(round(GETID3_FREAD_BUFFER_SIZE / 2)); + while (true) { + + if (($SynchSeekOffset > $header_len) && (($avdataoffset + $SynchSeekOffset) < $ThisFileInfo['avdataend']) && !feof($fd)) { + + if ($SynchSeekOffset > 131072) { + // if a synch's not found within the first 128k bytes, then give up + $ThisFileInfo['error'][] = 'could not find valid MPEG audio synch within the first 128k bytes'; + if (isset($ThisFileInfo['audio']['bitrate'])) { + unset($ThisFileInfo['audio']['bitrate']); + } + if (isset($ThisFileInfo['mpeg']['audio'])) { + unset($ThisFileInfo['mpeg']['audio']); + } + if (empty($ThisFileInfo['mpeg'])) { + unset($ThisFileInfo['mpeg']); + } + return false; + + } elseif ($header .= fread($fd, GETID3_FREAD_BUFFER_SIZE)) { + + // great + $header_len = strlen($header) - intval(round(GETID3_FREAD_BUFFER_SIZE / 2)); + + } else { + + $ThisFileInfo['error'][] = 'could not find valid MPEG audio synch before end of file'; + if (isset($ThisFileInfo['audio']['bitrate'])) { + unset($ThisFileInfo['audio']['bitrate']); + } + if (isset($ThisFileInfo['mpeg']['audio'])) { + unset($ThisFileInfo['mpeg']['audio']); + } + if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || (count($ThisFileInfo['mpeg']) == 0))) { + unset($ThisFileInfo['mpeg']); + } + return false; + + } + } + + if (($SynchSeekOffset + 1) >= strlen($header)) { + $ThisFileInfo['error'][] = 'could not find valid MPEG synch before end of file'; + return false; + } + + if (($header{$SynchSeekOffset} == "\xFF") && ($header{($SynchSeekOffset + 1)} > "\xE0")) { // synch detected + + if (!isset($FirstFrameThisfileInfo) && !isset($ThisFileInfo['mpeg']['audio'])) { + $FirstFrameThisfileInfo = $ThisFileInfo; + $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset; + if (!getid3_mp3::decodeMPEGaudioHeader($fd, $avdataoffset + $SynchSeekOffset, $FirstFrameThisfileInfo, false)) { + // if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's + // garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below + unset($FirstFrameThisfileInfo); + } + } + + $dummy = $ThisFileInfo; // only overwrite real data if valid header found + if (getid3_mp3::decodeMPEGaudioHeader($fd, $avdataoffset + $SynchSeekOffset, $dummy, true)) { + $ThisFileInfo = $dummy; + $ThisFileInfo['avdataoffset'] = $avdataoffset + $SynchSeekOffset; + switch ($ThisFileInfo['fileformat']) { + case '': + case 'id3': + case 'ape': + case 'mp3': + $ThisFileInfo['fileformat'] = 'mp3'; + $ThisFileInfo['audio']['dataformat'] = 'mp3'; + break; + } + if (isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) { + if (!(abs($ThisFileInfo['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) { + // If there is garbage data between a valid VBR header frame and a sequence + // of valid MPEG-audio frames the VBR data is no longer discarded. + $ThisFileInfo = $FirstFrameThisfileInfo; + $ThisFileInfo['avdataoffset'] = $FirstFrameAVDataOffset; + $ThisFileInfo['fileformat'] = 'mp3'; + $ThisFileInfo['audio']['dataformat'] = 'mp3'; + $dummy = $ThisFileInfo; + unset($dummy['mpeg']['audio']); + $GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength']; + $GarbageOffsetEnd = $avdataoffset + $SynchSeekOffset; + if (getid3_mp3::decodeMPEGaudioHeader($fd, $GarbageOffsetEnd, $dummy, true, true)) { + + $ThisFileInfo = $dummy; + $ThisFileInfo['avdataoffset'] = $GarbageOffsetEnd; + $ThisFileInfo['warning'][] = 'apparently-valid VBR header not used because could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd; + + } else { + + $ThisFileInfo['warning'][] = 'using data from VBR header even though could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')'; + + } + } + } + if (isset($ThisFileInfo['mpeg']['audio']['bitrate_mode']) && ($ThisFileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($ThisFileInfo['mpeg']['audio']['VBR_method'])) { + // VBR file with no VBR header + $BitrateHistogram = true; + } + + if ($BitrateHistogram) { + + $ThisFileInfo['mpeg']['audio']['stereo_distribution'] = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0); + $ThisFileInfo['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0); + + if ($ThisFileInfo['mpeg']['audio']['version'] == '1') { + if ($ThisFileInfo['mpeg']['audio']['layer'] == 3) { + $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0); + } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 2) { + $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0, 384000=>0); + } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 1) { + $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 64000=>0, 96000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 288000=>0, 320000=>0, 352000=>0, 384000=>0, 416000=>0, 448000=>0); + } + } elseif ($ThisFileInfo['mpeg']['audio']['layer'] == 1) { + $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0, 176000=>0, 192000=>0, 224000=>0, 256000=>0); + } else { + $ThisFileInfo['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 8000=>0, 16000=>0, 24000=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0); + } + + $dummy = array('error'=>$ThisFileInfo['error'], 'warning'=>$ThisFileInfo['warning'], 'avdataend'=>$ThisFileInfo['avdataend'], 'avdataoffset'=>$ThisFileInfo['avdataoffset']); + $synchstartoffset = $ThisFileInfo['avdataoffset']; + + $FastMode = false; + $SynchErrorsFound = 0; + while (getid3_mp3::decodeMPEGaudioHeader($fd, $synchstartoffset, $dummy, false, false, $FastMode)) { + $FastMode = true; + $thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']]; + + if (empty($dummy['mpeg']['audio']['framelength'])) { + $SynchErrorsFound++; + } else { + $ThisFileInfo['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]++; + $ThisFileInfo['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]++; + $ThisFileInfo['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]++; + + $synchstartoffset += $dummy['mpeg']['audio']['framelength']; + } + } + if ($SynchErrorsFound > 0) { + $ThisFileInfo['warning'][] = 'Found '.$SynchErrorsFound.' synch errors in histogram analysis'; + //return false; + } + + $bittotal = 0; + $framecounter = 0; + foreach ($ThisFileInfo['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) { + $framecounter += $bitratecount; + if ($bitratevalue != 'free') { + $bittotal += ($bitratevalue * $bitratecount); + } + } + if ($framecounter == 0) { + $ThisFileInfo['error'][] = 'Corrupt MP3 file: framecounter == zero'; + return false; + } + $ThisFileInfo['mpeg']['audio']['frame_count'] = $framecounter; + $ThisFileInfo['mpeg']['audio']['bitrate'] = ($bittotal / $framecounter); + + $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpeg']['audio']['bitrate']; + + + // Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently + $distinct_bitrates = 0; + foreach ($ThisFileInfo['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) { + if ($bitrate_count > 0) { + $distinct_bitrates++; + } + } + if ($distinct_bitrates > 1) { + $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'vbr'; + } else { + $ThisFileInfo['mpeg']['audio']['bitrate_mode'] = 'cbr'; + } + $ThisFileInfo['audio']['bitrate_mode'] = $ThisFileInfo['mpeg']['audio']['bitrate_mode']; + + } + + break; // exit while() + } + } + + $SynchSeekOffset++; + if (($avdataoffset + $SynchSeekOffset) >= $ThisFileInfo['avdataend']) { + // end of file/data + + if (empty($ThisFileInfo['mpeg']['audio'])) { + + $ThisFileInfo['error'][] = 'could not find valid MPEG synch before end of file'; + if (isset($ThisFileInfo['audio']['bitrate'])) { + unset($ThisFileInfo['audio']['bitrate']); + } + if (isset($ThisFileInfo['mpeg']['audio'])) { + unset($ThisFileInfo['mpeg']['audio']); + } + if (isset($ThisFileInfo['mpeg']) && (!is_array($ThisFileInfo['mpeg']) || empty($ThisFileInfo['mpeg']))) { + unset($ThisFileInfo['mpeg']); + } + return false; + + } + break; + } + + } + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['mpeg']['audio']['channels']; + $ThisFileInfo['audio']['channelmode'] = $ThisFileInfo['mpeg']['audio']['channelmode']; + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['mpeg']['audio']['sample_rate']; + return true; + } + + + function MPEGaudioVersionArray() { + static $MPEGaudioVersion = array('2.5', false, '2', '1'); + return $MPEGaudioVersion; + } + + function MPEGaudioLayerArray() { + static $MPEGaudioLayer = array(false, 3, 2, 1); + return $MPEGaudioLayer; + } + + function MPEGaudioBitrateArray() { + static $MPEGaudioBitrate; + if (empty($MPEGaudioBitrate)) { + $MPEGaudioBitrate = array ( + '1' => array (1 => array('free', 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000), + 2 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000), + 3 => array('free', 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000) + ), + + '2' => array (1 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000), + 2 => array('free', 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000), + ) + ); + $MPEGaudioBitrate['2'][3] = $MPEGaudioBitrate['2'][2]; + $MPEGaudioBitrate['2.5'] = $MPEGaudioBitrate['2']; + } + return $MPEGaudioBitrate; + } + + function MPEGaudioFrequencyArray() { + static $MPEGaudioFrequency; + if (empty($MPEGaudioFrequency)) { + $MPEGaudioFrequency = array ( + '1' => array(44100, 48000, 32000), + '2' => array(22050, 24000, 16000), + '2.5' => array(11025, 12000, 8000) + ); + } + return $MPEGaudioFrequency; + } + + function MPEGaudioChannelModeArray() { + static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono'); + return $MPEGaudioChannelMode; + } + + function MPEGaudioModeExtensionArray() { + static $MPEGaudioModeExtension; + if (empty($MPEGaudioModeExtension)) { + $MPEGaudioModeExtension = array ( + 1 => array('4-31', '8-31', '12-31', '16-31'), + 2 => array('4-31', '8-31', '12-31', '16-31'), + 3 => array('', 'IS', 'MS', 'IS+MS') + ); + } + return $MPEGaudioModeExtension; + } + + function MPEGaudioEmphasisArray() { + static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17'); + return $MPEGaudioEmphasis; + } + + function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) { + return getid3_mp3::MPEGaudioHeaderValid(getid3_mp3::MPEGaudioHeaderDecode($head4), false, $allowBitrate15); + } + + function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) { + if (($rawarray['synch'] & 0x0FFE) != 0x0FFE) { + return false; + } + + static $MPEGaudioVersionLookup; + static $MPEGaudioLayerLookup; + static $MPEGaudioBitrateLookup; + static $MPEGaudioFrequencyLookup; + static $MPEGaudioChannelModeLookup; + static $MPEGaudioModeExtensionLookup; + static $MPEGaudioEmphasisLookup; + if (empty($MPEGaudioVersionLookup)) { + $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray(); + $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray(); + $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray(); + $MPEGaudioFrequencyLookup = getid3_mp3::MPEGaudioFrequencyArray(); + $MPEGaudioChannelModeLookup = getid3_mp3::MPEGaudioChannelModeArray(); + $MPEGaudioModeExtensionLookup = getid3_mp3::MPEGaudioModeExtensionArray(); + $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray(); + } + + if (isset($MPEGaudioVersionLookup[$rawarray['version']])) { + $decodedVersion = $MPEGaudioVersionLookup[$rawarray['version']]; + } else { + if ($echoerrors) { + echo "\n".'invalid Version ('.$rawarray['version'].')'; + } + return false; + } + if (isset($MPEGaudioLayerLookup[$rawarray['layer']])) { + $decodedLayer = $MPEGaudioLayerLookup[$rawarray['layer']]; + } else { + if ($echoerrors) { + echo "\n".'invalid Layer ('.$rawarray['layer'].')'; + } + return false; + } + if (!isset($MPEGaudioBitrateLookup[$decodedVersion][$decodedLayer][$rawarray['bitrate']])) { + if ($echoerrors) { + echo "\n".'invalid Bitrate ('.$rawarray['bitrate'].')'; + } + if ($rawarray['bitrate'] == 15) { + // known issue in LAME 3.90 - 3.93.1 where free-format has bitrate ID of 15 instead of 0 + // let it go through here otherwise file will not be identified + if (!$allowBitrate15) { + return false; + } + } else { + return false; + } + } + if (!isset($MPEGaudioFrequencyLookup[$decodedVersion][$rawarray['sample_rate']])) { + if ($echoerrors) { + echo "\n".'invalid Frequency ('.$rawarray['sample_rate'].')'; + } + return false; + } + if (!isset($MPEGaudioChannelModeLookup[$rawarray['channelmode']])) { + if ($echoerrors) { + echo "\n".'invalid ChannelMode ('.$rawarray['channelmode'].')'; + } + return false; + } + if (!isset($MPEGaudioModeExtensionLookup[$decodedLayer][$rawarray['modeextension']])) { + if ($echoerrors) { + echo "\n".'invalid Mode Extension ('.$rawarray['modeextension'].')'; + } + return false; + } + if (!isset($MPEGaudioEmphasisLookup[$rawarray['emphasis']])) { + if ($echoerrors) { + echo "\n".'invalid Emphasis ('.$rawarray['emphasis'].')'; + } + return false; + } + // These are just either set or not set, you can't mess that up :) + // $rawarray['protection']; + // $rawarray['padding']; + // $rawarray['private']; + // $rawarray['copyright']; + // $rawarray['original']; + + return true; + } + + function MPEGaudioHeaderDecode($Header4Bytes) { + // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM + // A - Frame sync (all bits set) + // B - MPEG Audio version ID + // C - Layer description + // D - Protection bit + // E - Bitrate index + // F - Sampling rate frequency index + // G - Padding bit + // H - Private bit + // I - Channel Mode + // J - Mode extension (Only if Joint stereo) + // K - Copyright + // L - Original + // M - Emphasis + + if (strlen($Header4Bytes) != 4) { + return false; + } + + $MPEGrawHeader['synch'] = (getid3_lib::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4; + $MPEGrawHeader['version'] = (ord($Header4Bytes{1}) & 0x18) >> 3; // BB + $MPEGrawHeader['layer'] = (ord($Header4Bytes{1}) & 0x06) >> 1; // CC + $MPEGrawHeader['protection'] = (ord($Header4Bytes{1}) & 0x01); // D + $MPEGrawHeader['bitrate'] = (ord($Header4Bytes{2}) & 0xF0) >> 4; // EEEE + $MPEGrawHeader['sample_rate'] = (ord($Header4Bytes{2}) & 0x0C) >> 2; // FF + $MPEGrawHeader['padding'] = (ord($Header4Bytes{2}) & 0x02) >> 1; // G + $MPEGrawHeader['private'] = (ord($Header4Bytes{2}) & 0x01); // H + $MPEGrawHeader['channelmode'] = (ord($Header4Bytes{3}) & 0xC0) >> 6; // II + $MPEGrawHeader['modeextension'] = (ord($Header4Bytes{3}) & 0x30) >> 4; // JJ + $MPEGrawHeader['copyright'] = (ord($Header4Bytes{3}) & 0x08) >> 3; // K + $MPEGrawHeader['original'] = (ord($Header4Bytes{3}) & 0x04) >> 2; // L + $MPEGrawHeader['emphasis'] = (ord($Header4Bytes{3}) & 0x03); // MM + + return $MPEGrawHeader; + } + + function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) { + static $AudioFrameLengthCache = array(); + + if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) { + $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = false; + if ($bitrate != 'free') { + + if ($version == '1') { + + if ($layer == '1') { + + // For Layer I slot is 32 bits long + $FrameLengthCoefficient = 48; + $SlotLength = 4; + + } else { // Layer 2 / 3 + + // for Layer 2 and Layer 3 slot is 8 bits long. + $FrameLengthCoefficient = 144; + $SlotLength = 1; + + } + + } else { // MPEG-2 / MPEG-2.5 + + if ($layer == '1') { + + // For Layer I slot is 32 bits long + $FrameLengthCoefficient = 24; + $SlotLength = 4; + + } elseif ($layer == '2') { + + // for Layer 2 and Layer 3 slot is 8 bits long. + $FrameLengthCoefficient = 144; + $SlotLength = 1; + + } else { // layer 3 + + // for Layer 2 and Layer 3 slot is 8 bits long. + $FrameLengthCoefficient = 72; + $SlotLength = 1; + + } + + } + + // FrameLengthInBytes = ((Coefficient * BitRate) / SampleRate) + Padding + if ($samplerate > 0) { + $NewFramelength = ($FrameLengthCoefficient * $bitrate) / $samplerate; + $NewFramelength = floor($NewFramelength / $SlotLength) * $SlotLength; // round to next-lower multiple of SlotLength (1 byte for Layer 2/3, 4 bytes for Layer I) + if ($padding) { + $NewFramelength += $SlotLength; + } + $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = (int) $NewFramelength; + } + } + } + return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate]; + } + + function ClosestStandardMP3Bitrate($bitrate) { + static $StandardBitrates = array(320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000); + static $BitrateTable = array(0=>'-'); + $roundbitrate = intval(round($bitrate, -3)); + if (!isset($BitrateTable[$roundbitrate])) { + if ($roundbitrate > 320000) { + $BitrateTable[$roundbitrate] = round($bitrate, -4); + } else { + $LastBitrate = 320000; + foreach ($StandardBitrates as $StandardBitrate) { + $BitrateTable[$roundbitrate] = $StandardBitrate; + if ($roundbitrate >= $StandardBitrate - (($LastBitrate - $StandardBitrate) / 2)) { + break; + } + $LastBitrate = $StandardBitrate; + } + } + } + return $BitrateTable[$roundbitrate]; + } + + function XingVBRidOffset($version, $channelmode) { + static $XingVBRidOffsetCache = array(); + if (empty($XingVBRidOffset)) { + $XingVBRidOffset = array ( + '1' => array ('mono' => 0x15, // 4 + 17 = 21 + 'stereo' => 0x24, // 4 + 32 = 36 + 'joint stereo' => 0x24, + 'dual channel' => 0x24 + ), + + '2' => array ('mono' => 0x0D, // 4 + 9 = 13 + 'stereo' => 0x15, // 4 + 17 = 21 + 'joint stereo' => 0x15, + 'dual channel' => 0x15 + ), + + '2.5' => array ('mono' => 0x15, + 'stereo' => 0x15, + 'joint stereo' => 0x15, + 'dual channel' => 0x15 + ) + ); + } + return $XingVBRidOffset[$version][$channelmode]; + } + + function LAMEvbrMethodLookup($VBRmethodID) { + static $LAMEvbrMethodLookup = array( + 0x00 => 'unknown', + 0x01 => 'cbr', + 0x02 => 'abr', + 0x03 => 'vbr-old / vbr-rh', + 0x04 => 'vbr-new / vbr-mtrh', + 0x05 => 'vbr-mt', + 0x06 => 'Full VBR Method 4', + 0x08 => 'constant bitrate 2 pass', + 0x09 => 'abr 2 pass', + 0x0F => 'reserved' + ); + return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : ''); + } + + function LAMEmiscStereoModeLookup($StereoModeID) { + static $LAMEmiscStereoModeLookup = array( + 0 => 'mono', + 1 => 'stereo', + 2 => 'dual mono', + 3 => 'joint stereo', + 4 => 'forced stereo', + 5 => 'auto', + 6 => 'intensity stereo', + 7 => 'other' + ); + return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : ''); + } + + function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) { + static $LAMEmiscSourceSampleFrequencyLookup = array( + 0 => '<= 32 kHz', + 1 => '44.1 kHz', + 2 => '48 kHz', + 3 => '> 48kHz' + ); + return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : ''); + } + + function LAMEsurroundInfoLookup($SurroundInfoID) { + static $LAMEsurroundInfoLookup = array( + 0 => 'no surround info', + 1 => 'DPL encoding', + 2 => 'DPL2 encoding', + 3 => 'Ambisonic encoding' + ); + return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved'); + } + + function LAMEpresetUsedLookup($LAMEtag) { + if ($LAMEtag['preset_used_id'] == 0) { + // no preset used (LAME >=3.93) + // no preset recorded (LAME <3.93) + return ''; + } + static $LAMEpresetUsedLookup = array(); + if (empty($LAMEpresetUsedLookup)) { + for ($i = 8; $i <= 320; $i++) { + switch ($LAMEtag['vbr_method']) { + case 'cbr': + $LAMEpresetUsedLookup[$i] = '--alt-preset '.$LAMEtag['vbr_method'].' '.$i; + break; + case 'abr': + default: // other VBR modes shouldn't be here(?) + $LAMEpresetUsedLookup[$i] = '--alt-preset '.$i; + break; + } + } + + // named old-style presets (studio, phone, voice, etc) are handled in GuessEncoderOptions() + + // named alt-presets + $LAMEpresetUsedLookup[1000] = '--r3mix'; + $LAMEpresetUsedLookup[1001] = '--alt-preset standard'; + $LAMEpresetUsedLookup[1002] = '--alt-preset extreme'; + $LAMEpresetUsedLookup[1003] = '--alt-preset insane'; + $LAMEpresetUsedLookup[1004] = '--alt-preset fast standard'; + $LAMEpresetUsedLookup[1005] = '--alt-preset fast extreme'; + $LAMEpresetUsedLookup[1006] = '--alt-preset medium'; + $LAMEpresetUsedLookup[1007] = '--alt-preset fast medium'; + + // LAME 3.94 additions/changes + $LAMEpresetUsedLookup[1010] = '--preset portable'; // 3.94a15 Oct 21 2003 + $LAMEpresetUsedLookup[1015] = '--preset radio'; // 3.94a15 Oct 21 2003 + + $LAMEpresetUsedLookup[320] = '--preset insane'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[430] = '--preset radio'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[450] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'portable'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[460] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'medium'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[470] = '--r3mix'; // 3.94b1 Dec 18 2003 + $LAMEpresetUsedLookup[480] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'standard'; // 3.94a15 Nov 12 2003 + $LAMEpresetUsedLookup[500] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'extreme'; // 3.94a15 Nov 12 2003 + } + return (isset($LAMEpresetUsedLookup[$LAMEtag['preset_used_id']]) ? $LAMEpresetUsedLookup[$LAMEtag['preset_used_id']] : 'new/unknown preset: '.$LAMEtag['preset_used_id'].' - report to info@getid3.org'); + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.mpc.php b/modules/id3/getid3/module.audio.mpc.php new file mode 100644 index 00000000..d0a7202f --- /dev/null +++ b/modules/id3/getid3/module.audio.mpc.php @@ -0,0 +1,296 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.mpc.php // +// module for analyzing Musepack/MPEG+ Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_mpc +{ + + function getid3_mpc(&$fd, &$ThisFileInfo) { + // http://www.uni-jena.de/~pfk/mpp/sv8/header.html + + $ThisFileInfo['mpc']['header'] = array(); + $thisfile_mpc_header = &$ThisFileInfo['mpc']['header']; + + $ThisFileInfo['fileformat'] = 'mpc'; + $ThisFileInfo['audio']['dataformat'] = 'mpc'; + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + $ThisFileInfo['audio']['channels'] = 2; // the format appears to be hardcoded for stereo only + $ThisFileInfo['audio']['lossless'] = false; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + + $thisfile_mpc_header['size'] = 28; + $MPCheaderData = fread($fd, $thisfile_mpc_header['size']); + $offset = 0; + + if (substr($MPCheaderData, $offset, 3) == 'MP+') { + + // great, this is SV7+ + $thisfile_mpc_header['raw']['preamble'] = substr($MPCheaderData, $offset, 3); // should be 'MP+' + $offset += 3; + + } elseif (preg_match('/^[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0]/s', substr($MPCheaderData, 0, 4))) { + + // this is SV4 - SV6, handle seperately + $thisfile_mpc_header['size'] = 8; + + // add size of file header to avdataoffset - calc bitrate correctly + MD5 data + $ThisFileInfo['avdataoffset'] += $thisfile_mpc_header['size']; + + // Most of this code adapted from Jurgen Faul's MPEGplus source code - thanks Jurgen! :) + $HeaderDWORD[0] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 0, 4)); + $HeaderDWORD[1] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 4, 4)); + + + // DDDD DDDD CCCC CCCC BBBB BBBB AAAA AAAA + // aaaa aaaa abcd dddd dddd deee eeff ffff + // + // a = bitrate = anything + // b = IS = anything + // c = MS = anything + // d = streamversion = 0000000004 or 0000000005 or 0000000006 + // e = maxband = anything + // f = blocksize = 000001 for SV5+, anything(?) for SV4 + + $thisfile_mpc_header['target_bitrate'] = (($HeaderDWORD[0] & 0xFF800000) >> 23); + $thisfile_mpc_header['intensity_stereo'] = (bool) (($HeaderDWORD[0] & 0x00400000) >> 22); + $thisfile_mpc_header['mid-side_stereo'] = (bool) (($HeaderDWORD[0] & 0x00200000) >> 21); + $thisfile_mpc_header['stream_major_version'] = ($HeaderDWORD[0] & 0x001FF800) >> 11; + $thisfile_mpc_header['stream_minor_version'] = 0; // no sub-version numbers before SV7 + $thisfile_mpc_header['max_band'] = ($HeaderDWORD[0] & 0x000007C0) >> 6; // related to lowpass frequency, not sure how it translates exactly + $thisfile_mpc_header['block_size'] = ($HeaderDWORD[0] & 0x0000003F); + + switch ($thisfile_mpc_header['stream_major_version']) { + case 4: + $thisfile_mpc_header['frame_count'] = ($HeaderDWORD[1] >> 16); + break; + + case 5: + case 6: + $thisfile_mpc_header['frame_count'] = $HeaderDWORD[1]; + break; + + default: + $ThisFileInfo['error'] = 'Expecting 4, 5 or 6 in version field, found '.$thisfile_mpc_header['stream_major_version'].' instead'; + unset($ThisFileInfo['mpc']); + return false; + break; + } + + if (($thisfile_mpc_header['stream_major_version'] > 4) && ($thisfile_mpc_header['block_size'] != 1)) { + $ThisFileInfo['warning'][] = 'Block size expected to be 1, actual value found: '.$thisfile_mpc_header['block_size']; + } + + $thisfile_mpc_header['sample_rate'] = 44100; // AB: used by all files up to SV7 + $ThisFileInfo['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate']; + $thisfile_mpc_header['samples'] = $thisfile_mpc_header['frame_count'] * 1152 * $ThisFileInfo['audio']['channels']; + + if ($thisfile_mpc_header['target_bitrate'] == 0) { + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + } else { + $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + } + + $ThisFileInfo['mpc']['bitrate'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8 * 44100 / $thisfile_mpc_header['frame_count'] / 1152; + $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['mpc']['bitrate']; + $ThisFileInfo['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_major_version']; + + return true; + + } else { + + $ThisFileInfo['error'][] = 'Expecting "MP+" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($MPCheaderData, $offset, 3).'"'; + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['mpc']); + return false; + + } + + // Continue with SV7+ handling + $StreamVersionByte = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1)); + $offset += 1; + $thisfile_mpc_header['stream_major_version'] = ($StreamVersionByte & 0x0F); + $thisfile_mpc_header['stream_minor_version'] = ($StreamVersionByte & 0xF0) >> 4; + $thisfile_mpc_header['frame_count'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4)); + $offset += 4; + + switch ($thisfile_mpc_header['stream_major_version']) { + case 7: + //$ThisFileInfo['fileformat'] = 'SV7'; + break; + + default: + $ThisFileInfo['error'][] = 'Only Musepack SV7 supported'; + return false; + } + + $FlagsDWORD1 = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4)); + $offset += 4; + $thisfile_mpc_header['intensity_stereo'] = (bool) (($FlagsDWORD1 & 0x80000000) >> 31); + $thisfile_mpc_header['mid_side_stereo'] = (bool) (($FlagsDWORD1 & 0x40000000) >> 30); + $thisfile_mpc_header['max_subband'] = ($FlagsDWORD1 & 0x3F000000) >> 24; + $thisfile_mpc_header['raw']['profile'] = ($FlagsDWORD1 & 0x00F00000) >> 20; + $thisfile_mpc_header['begin_loud'] = (bool) (($FlagsDWORD1 & 0x00080000) >> 19); + $thisfile_mpc_header['end_loud'] = (bool) (($FlagsDWORD1 & 0x00040000) >> 18); + $thisfile_mpc_header['raw']['sample_rate'] = ($FlagsDWORD1 & 0x00030000) >> 16; + $thisfile_mpc_header['max_level'] = ($FlagsDWORD1 & 0x0000FFFF); + + $thisfile_mpc_header['raw']['title_peak'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2)); + $offset += 2; + $thisfile_mpc_header['raw']['title_gain'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2), true); + $offset += 2; + + $thisfile_mpc_header['raw']['album_peak'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2)); + $offset += 2; + $thisfile_mpc_header['raw']['album_gain'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2), true); + $offset += 2; + + $FlagsDWORD2 = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4)); + $offset += 4; + $thisfile_mpc_header['true_gapless'] = (bool) (($FlagsDWORD2 & 0x80000000) >> 31); + $thisfile_mpc_header['last_frame_length'] = ($FlagsDWORD2 & 0x7FF00000) >> 20; + + + $thisfile_mpc_header['raw']['not_sure_what'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 3)); + $offset += 3; + $thisfile_mpc_header['raw']['encoder_version'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1)); + $offset += 1; + + $thisfile_mpc_header['profile'] = $this->MPCprofileNameLookup($thisfile_mpc_header['raw']['profile']); + $thisfile_mpc_header['sample_rate'] = $this->MPCfrequencyLookup($thisfile_mpc_header['raw']['sample_rate']); + if ($thisfile_mpc_header['sample_rate'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt MPC file: frequency == zero'; + return false; + } + $ThisFileInfo['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate']; + $thisfile_mpc_header['samples'] = ((($thisfile_mpc_header['frame_count'] - 1) * 1152) + $thisfile_mpc_header['last_frame_length']) * $ThisFileInfo['audio']['channels']; + + $ThisFileInfo['playtime_seconds'] = ($thisfile_mpc_header['samples'] / $ThisFileInfo['audio']['channels']) / $ThisFileInfo['audio']['sample_rate']; + if ($ThisFileInfo['playtime_seconds'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt MPC file: playtime_seconds == zero'; + return false; + } + + // add size of file header to avdataoffset - calc bitrate correctly + MD5 data + $ThisFileInfo['avdataoffset'] += $thisfile_mpc_header['size']; + + $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + + $thisfile_mpc_header['title_peak'] = $thisfile_mpc_header['raw']['title_peak']; + $thisfile_mpc_header['title_peak_db'] = $this->MPCpeakDBLookup($thisfile_mpc_header['title_peak']); + if ($thisfile_mpc_header['raw']['title_gain'] < 0) { + $thisfile_mpc_header['title_gain_db'] = (float) (32768 + $thisfile_mpc_header['raw']['title_gain']) / -100; + } else { + $thisfile_mpc_header['title_gain_db'] = (float) $thisfile_mpc_header['raw']['title_gain'] / 100; + } + + $thisfile_mpc_header['album_peak'] = $thisfile_mpc_header['raw']['album_peak']; + $thisfile_mpc_header['album_peak_db'] = $this->MPCpeakDBLookup($thisfile_mpc_header['album_peak']); + if ($thisfile_mpc_header['raw']['album_gain'] < 0) { + $thisfile_mpc_header['album_gain_db'] = (float) (32768 + $thisfile_mpc_header['raw']['album_gain']) / -100; + } else { + $thisfile_mpc_header['album_gain_db'] = (float) $thisfile_mpc_header['raw']['album_gain'] / 100;; + } + $thisfile_mpc_header['encoder_version'] = $this->MPCencoderVersionLookup($thisfile_mpc_header['raw']['encoder_version']); + + $ThisFileInfo['replay_gain']['track']['adjustment'] = $thisfile_mpc_header['title_gain_db']; + $ThisFileInfo['replay_gain']['album']['adjustment'] = $thisfile_mpc_header['album_gain_db']; + + if ($thisfile_mpc_header['title_peak'] > 0) { + $ThisFileInfo['replay_gain']['track']['peak'] = $thisfile_mpc_header['title_peak']; + } elseif (round($thisfile_mpc_header['max_level'] * 1.18) > 0) { + $ThisFileInfo['replay_gain']['track']['peak'] = getid3_lib::CastAsInt(round($thisfile_mpc_header['max_level'] * 1.18)); // why? I don't know - see mppdec.c + } + if ($thisfile_mpc_header['album_peak'] > 0) { + $ThisFileInfo['replay_gain']['album']['peak'] = $thisfile_mpc_header['album_peak']; + } + + //$ThisFileInfo['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_major_version'].'.'.$thisfile_mpc_header['stream_minor_version'].', '.$thisfile_mpc_header['encoder_version']; + $ThisFileInfo['audio']['encoder'] = $thisfile_mpc_header['encoder_version']; + $ThisFileInfo['audio']['encoder_options'] = $thisfile_mpc_header['profile']; + + return true; + } + + function MPCprofileNameLookup($profileid) { + static $MPCprofileNameLookup = array( + 0 => 'no profile', + 1 => 'Experimental', + 2 => 'unused', + 3 => 'unused', + 4 => 'unused', + 5 => 'below Telephone (q = 0.0)', + 6 => 'below Telephone (q = 1.0)', + 7 => 'Telephone (q = 2.0)', + 8 => 'Thumb (q = 3.0)', + 9 => 'Radio (q = 4.0)', + 10 => 'Standard (q = 5.0)', + 11 => 'Extreme (q = 6.0)', + 12 => 'Insane (q = 7.0)', + 13 => 'BrainDead (q = 8.0)', + 14 => 'above BrainDead (q = 9.0)', + 15 => 'above BrainDead (q = 10.0)' + ); + return (isset($MPCprofileNameLookup[$profileid]) ? $MPCprofileNameLookup[$profileid] : 'invalid'); + } + + function MPCfrequencyLookup($frequencyid) { + static $MPCfrequencyLookup = array( + 0 => 44100, + 1 => 48000, + 2 => 37800, + 3 => 32000 + ); + return (isset($MPCfrequencyLookup[$frequencyid]) ? $MPCfrequencyLookup[$frequencyid] : 'invalid'); + } + + function MPCpeakDBLookup($intvalue) { + if ($intvalue > 0) { + return ((log10($intvalue) / log10(2)) - 15) * 6; + } + return false; + } + + function MPCencoderVersionLookup($encoderversion) { + //Encoder version * 100 (106 = 1.06) + //EncoderVersion % 10 == 0 Release (1.0) + //EncoderVersion % 2 == 0 Beta (1.06) + //EncoderVersion % 2 == 1 Alpha (1.05a...z) + + if ($encoderversion == 0) { + // very old version, not known exactly which + return 'Buschmann v1.7.0-v1.7.9 or Klemm v0.90-v1.05'; + } + + if (($encoderversion % 10) == 0) { + + // release version + return number_format($encoderversion / 100, 2); + + } elseif (($encoderversion % 2) == 0) { + + // beta version + return number_format($encoderversion / 100, 2).' beta'; + + } + + // alpha version + return number_format($encoderversion / 100, 2).' alpha'; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.ogg.php b/modules/id3/getid3/module.audio.ogg.php new file mode 100644 index 00000000..f722b33c --- /dev/null +++ b/modules/id3/getid3/module.audio.ogg.php @@ -0,0 +1,543 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.ogg.php // +// module for analyzing Ogg Vorbis, OggFLAC and Speex files // +// dependencies: module.audio.flac.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true); + +class getid3_ogg +{ + + function getid3_ogg(&$fd, &$ThisFileInfo) { + + $ThisFileInfo['fileformat'] = 'ogg'; + + // Warn about illegal tags - only vorbiscomments are allowed + if (isset($ThisFileInfo['id3v2'])) { + $ThisFileInfo['warning'][] = 'Illegal ID3v2 tag present.'; + } + if (isset($ThisFileInfo['id3v1'])) { + $ThisFileInfo['warning'][] = 'Illegal ID3v1 tag present.'; + } + if (isset($ThisFileInfo['ape'])) { + $ThisFileInfo['warning'][] = 'Illegal APE tag present.'; + } + + + // Page 1 - Stream Header + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + + $oggpageinfo = getid3_ogg::ParseOggPageHeader($fd); + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; + + if (ftell($fd) >= GETID3_FREAD_BUFFER_SIZE) { + $ThisFileInfo['error'][] = 'Could not find start of Ogg page in the first '.GETID3_FREAD_BUFFER_SIZE.' bytes (this might not be an Ogg-Vorbis file?)'; + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['ogg']); + return false; + } + + $filedata = fread($fd, $oggpageinfo['page_length']); + $filedataoffset = 0; + + if (substr($filedata, 0, 4) == 'fLaC') { + + $ThisFileInfo['audio']['dataformat'] = 'flac'; + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + $ThisFileInfo['audio']['lossless'] = true; + + } elseif (substr($filedata, 1, 6) == 'vorbis') { + + $ThisFileInfo['audio']['dataformat'] = 'vorbis'; + $ThisFileInfo['audio']['lossless'] = false; + + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis' + $filedataoffset += 6; + $ThisFileInfo['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['ogg']['numberofchannels']; + $ThisFileInfo['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + if ($ThisFileInfo['ogg']['samplerate'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt Ogg file: sample rate == zero'; + return false; + } + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['ogg']['samplerate']; + $ThisFileInfo['ogg']['samples'] = 0; // filled in later + $ThisFileInfo['ogg']['bitrate_average'] = 0; // filled in later + $ThisFileInfo['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F); + $ThisFileInfo['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4); + $ThisFileInfo['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet + + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr + if ($ThisFileInfo['ogg']['bitrate_max'] == 0xFFFFFFFF) { + unset($ThisFileInfo['ogg']['bitrate_max']); + $ThisFileInfo['audio']['bitrate_mode'] = 'abr'; + } + if ($ThisFileInfo['ogg']['bitrate_nominal'] == 0xFFFFFFFF) { + unset($ThisFileInfo['ogg']['bitrate_nominal']); + } + if ($ThisFileInfo['ogg']['bitrate_min'] == 0xFFFFFFFF) { + unset($ThisFileInfo['ogg']['bitrate_min']); + $ThisFileInfo['audio']['bitrate_mode'] = 'abr'; + } + + } elseif (substr($filedata, 0, 8) == 'Speex ') { + + // http://www.speex.org/manual/node10.html + + $ThisFileInfo['audio']['dataformat'] = 'speex'; + $ThisFileInfo['mime_type'] = 'audio/speex'; + $ThisFileInfo['audio']['bitrate_mode'] = 'abr'; + $ThisFileInfo['audio']['lossless'] = false; + + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex ' + $filedataoffset += 8; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20); + $filedataoffset += 20; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + + $ThisFileInfo['speex']['speex_version'] = trim($ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']); + $ThisFileInfo['speex']['sample_rate'] = $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']; + $ThisFileInfo['speex']['channels'] = $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']; + $ThisFileInfo['speex']['vbr'] = (bool) $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']; + $ThisFileInfo['speex']['band_type'] = getid3_ogg::SpeexBandModeLookup($ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']); + + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['speex']['sample_rate']; + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['speex']['channels']; + if ($ThisFileInfo['speex']['vbr']) { + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + } + + } else { + + $ThisFileInfo['error'][] = 'Expecting either "Speex " or "vorbis" identifier strings, found neither'; + unset($ThisFileInfo['ogg']); + unset($ThisFileInfo['mime_type']); + return false; + + } + + + // Page 2 - Comment Header + + $oggpageinfo = getid3_ogg::ParseOggPageHeader($fd); + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; + + switch ($ThisFileInfo['audio']['dataformat']) { + + case 'vorbis': + $filedata = fread($fd, $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1)); + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis' + + getid3_ogg::ParseVorbisCommentsFilepointer($fd, $ThisFileInfo); + break; + + case 'flac': + if (!getid3_flac::FLACparseMETAdata($fd, $ThisFileInfo)) { + $ThisFileInfo['error'][] = 'Failed to parse FLAC headers'; + return false; + } + break; + + case 'speex': + fseek($fd, $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR); + getid3_ogg::ParseVorbisCommentsFilepointer($fd, $ThisFileInfo); + break; + + } + + + + // Last Page - Number of Samples + + fseek($fd, max($ThisFileInfo['avdataend'] - GETID3_FREAD_BUFFER_SIZE, 0), SEEK_SET); + $LastChunkOfOgg = strrev(fread($fd, GETID3_FREAD_BUFFER_SIZE)); + if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) { + fseek($fd, $ThisFileInfo['avdataend'] - ($LastOggSpostion + strlen('SggO')), SEEK_SET); + $ThisFileInfo['avdataend'] = ftell($fd); + $ThisFileInfo['ogg']['pageheader']['eos'] = getid3_ogg::ParseOggPageHeader($fd); + $ThisFileInfo['ogg']['samples'] = $ThisFileInfo['ogg']['pageheader']['eos']['pcm_abs_position']; + if ($ThisFileInfo['ogg']['samples'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt Ogg file: eos.number of samples == zero'; + return false; + } + $ThisFileInfo['ogg']['bitrate_average'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / ($ThisFileInfo['ogg']['samples'] / $ThisFileInfo['audio']['sample_rate']); + } + + if (!empty($ThisFileInfo['ogg']['bitrate_average'])) { + $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['ogg']['bitrate_average']; + } elseif (!empty($ThisFileInfo['ogg']['bitrate_nominal'])) { + $ThisFileInfo['audio']['bitrate'] = $ThisFileInfo['ogg']['bitrate_nominal']; + } elseif (!empty($ThisFileInfo['ogg']['bitrate_min']) && !empty($ThisFileInfo['ogg']['bitrate_max'])) { + $ThisFileInfo['audio']['bitrate'] = ($ThisFileInfo['ogg']['bitrate_min'] + $ThisFileInfo['ogg']['bitrate_max']) / 2; + } + if (isset($ThisFileInfo['audio']['bitrate']) && !isset($ThisFileInfo['playtime_seconds'])) { + if ($ThisFileInfo['audio']['bitrate'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt Ogg file: bitrate_audio == zero'; + return false; + } + $ThisFileInfo['playtime_seconds'] = (float) ((($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['audio']['bitrate']); + } + + if (isset($ThisFileInfo['ogg']['vendor'])) { + $ThisFileInfo['audio']['encoder'] = preg_replace('/^Encoded with /', '', $ThisFileInfo['ogg']['vendor']); + + // Vorbis only + if ($ThisFileInfo['audio']['dataformat'] == 'vorbis') { + + // Vorbis 1.0 starts with Xiph.Org + if (preg_match('/^Xiph.Org/', $ThisFileInfo['audio']['encoder'])) { + + if ($ThisFileInfo['audio']['bitrate_mode'] == 'abr') { + + // Set -b 128 on abr files + $ThisFileInfo['audio']['encoder_options'] = '-b '.round($ThisFileInfo['ogg']['bitrate_nominal'] / 1000); + + } elseif (($ThisFileInfo['audio']['bitrate_mode'] == 'vbr') && ($ThisFileInfo['audio']['channels'] == 2) && ($ThisFileInfo['audio']['sample_rate'] >= 44100) && ($ThisFileInfo['audio']['sample_rate'] <= 48000)) { + // Set -q N on vbr files + $ThisFileInfo['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($ThisFileInfo['ogg']['bitrate_nominal']); + + } + } + + if (empty($ThisFileInfo['audio']['encoder_options']) && !empty($ThisFileInfo['ogg']['bitrate_nominal'])) { + $ThisFileInfo['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($ThisFileInfo['ogg']['bitrate_nominal'] / 1000)).'kbps'; + } + } + } + + return true; + } + + + function ParseOggPageHeader(&$fd) { + // http://xiph.org/ogg/vorbis/doc/framing.html + $oggheader['page_start_offset'] = ftell($fd); // where we started from in the file + + $filedata = fread($fd, GETID3_FREAD_BUFFER_SIZE); + $filedataoffset = 0; + while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) { + if ((ftell($fd) - $oggheader['page_start_offset']) >= GETID3_FREAD_BUFFER_SIZE) { + // should be found before here + return false; + } + if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) { + if (feof($fd) || (($filedata .= fread($fd, GETID3_FREAD_BUFFER_SIZE)) === false)) { + // get some more data, unless eof, in which case fail + return false; + } + } + } + $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS' + + $oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet + $oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos) + $oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos) + + $oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); + $filedataoffset += 8; + $oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); + $filedataoffset += 4; + $oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $oggheader['page_length'] = 0; + for ($i = 0; $i < $oggheader['page_segments']; $i++) { + $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); + $filedataoffset += 1; + $oggheader['page_length'] += $oggheader['segment_table'][$i]; + } + $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset; + $oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length']; + fseek($fd, $oggheader['header_end_offset'], SEEK_SET); + + return $oggheader; + } + + + function ParseVorbisCommentsFilepointer(&$fd, &$ThisFileInfo) { + + $OriginalOffset = ftell($fd); + $CommentStartOffset = $OriginalOffset; + $commentdataoffset = 0; + $VorbisCommentPage = 1; + + switch ($ThisFileInfo['audio']['dataformat']) { + case 'vorbis': + $CommentStartOffset = $ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block + fseek($fd, $CommentStartOffset, SEEK_SET); + $commentdataoffset = 27 + $ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; + $commentdata = fread($fd, getid3_ogg::OggPageSegmentLength($ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); + + $commentdataoffset += (strlen('vorbis') + 1); + break; + + case 'flac': + fseek($fd, $ThisFileInfo['flac']['VORBIS_COMMENT']['raw']['offset'] + 4, SEEK_SET); + $commentdata = fread($fd, $ThisFileInfo['flac']['VORBIS_COMMENT']['raw']['block_length']); + break; + + case 'speex': + $CommentStartOffset = $ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block + fseek($fd, $CommentStartOffset, SEEK_SET); + $commentdataoffset = 27 + $ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; + $commentdata = fread($fd, getid3_ogg::OggPageSegmentLength($ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); + break; + + default: + return false; + break; + } + + $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); + $commentdataoffset += 4; + + $ThisFileInfo['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize); + $commentdataoffset += $VendorSize; + + $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); + $commentdataoffset += 4; + $ThisFileInfo['avdataoffset'] = $CommentStartOffset + $commentdataoffset; + + $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT'); + for ($i = 0; $i < $CommentsCount; $i++) { + + $ThisFileInfo['ogg']['comments_raw'][$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset; + + if (ftell($fd) < ($ThisFileInfo['ogg']['comments_raw'][$i]['dataoffset'] + 4)) { + $VorbisCommentPage++; + + $oggpageinfo = getid3_ogg::ParseOggPageHeader($fd); + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; + + // First, save what we haven't read yet + $AsYetUnusedData = substr($commentdata, $commentdataoffset); + + // Then take that data off the end + $commentdata = substr($commentdata, 0, $commentdataoffset); + + // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct + $commentdata .= str_repeat("\x00", 27 + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); + $commentdataoffset += (27 + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); + + // Finally, stick the unused data back on the end + $commentdata .= $AsYetUnusedData; + + //$commentdata .= fread($fd, $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + $commentdata .= fread($fd, getid3_ogg::OggPageSegmentLength($ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage], 1)); + + } + $ThisFileInfo['ogg']['comments_raw'][$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); + + // replace avdataoffset with position just after the last vorbiscomment + $ThisFileInfo['avdataoffset'] = $ThisFileInfo['ogg']['comments_raw'][$i]['dataoffset'] + $ThisFileInfo['ogg']['comments_raw'][$i]['size'] + 4; + + $commentdataoffset += 4; + while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo['ogg']['comments_raw'][$i]['size']) { + if (($ThisFileInfo['ogg']['comments_raw'][$i]['size'] > $ThisFileInfo['avdataend']) || ($ThisFileInfo['ogg']['comments_raw'][$i]['size'] < 0)) { + $ThisFileInfo['error'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo['ogg']['comments_raw'][$i]['size']).' bytes) - aborting reading comments'; + break 2; + } + + $VorbisCommentPage++; + + $oggpageinfo = getid3_ogg::ParseOggPageHeader($fd); + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; + + // First, save what we haven't read yet + $AsYetUnusedData = substr($commentdata, $commentdataoffset); + + // Then take that data off the end + $commentdata = substr($commentdata, 0, $commentdataoffset); + + // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct + $commentdata .= str_repeat("\x00", 27 + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); + $commentdataoffset += (27 + $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); + + // Finally, stick the unused data back on the end + $commentdata .= $AsYetUnusedData; + + //$commentdata .= fread($fd, $ThisFileInfo['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); + $commentdata .= fread($fd, getid3_ogg::OggPageSegmentLength($ThisFileInfo['ogg']['pageheader'][$VorbisCommentPage], 1)); + + //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset']; + } + $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo['ogg']['comments_raw'][$i]['size']); + $commentdataoffset += $ThisFileInfo['ogg']['comments_raw'][$i]['size']; + + if (!$commentstring) { + + // no comment? + $ThisFileInfo['warning'][] = 'Blank Ogg comment ['.$i.']'; + + } elseif (strstr($commentstring, '=')) { + + $commentexploded = explode('=', $commentstring, 2); + $ThisFileInfo['ogg']['comments_raw'][$i]['key'] = strtoupper($commentexploded[0]); + $ThisFileInfo['ogg']['comments_raw'][$i]['value'] = @$commentexploded[1]; + $ThisFileInfo['ogg']['comments_raw'][$i]['data'] = base64_decode($ThisFileInfo['ogg']['comments_raw'][$i]['value']); + + $ThisFileInfo['ogg']['comments'][strtolower($ThisFileInfo['ogg']['comments_raw'][$i]['key'])][] = $ThisFileInfo['ogg']['comments_raw'][$i]['value']; + + $imagechunkcheck = getid3_lib::GetDataImageSize($ThisFileInfo['ogg']['comments_raw'][$i]['data']); + $ThisFileInfo['ogg']['comments_raw'][$i]['image_mime'] = getid3_lib::image_type_to_mime_type($imagechunkcheck[2]); + if (!$ThisFileInfo['ogg']['comments_raw'][$i]['image_mime'] || ($ThisFileInfo['ogg']['comments_raw'][$i]['image_mime'] == 'application/octet-stream')) { + unset($ThisFileInfo['ogg']['comments_raw'][$i]['image_mime']); + unset($ThisFileInfo['ogg']['comments_raw'][$i]['data']); + } + + } else { + + $ThisFileInfo['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring; + + } + } + + + // Replay Gain Adjustment + // http://privatewww.essex.ac.uk/~djmrob/replaygain/ + if (isset($ThisFileInfo['ogg']['comments']) && is_array($ThisFileInfo['ogg']['comments'])) { + foreach ($ThisFileInfo['ogg']['comments'] as $index => $commentvalue) { + switch ($index) { + case 'rg_audiophile': + case 'replaygain_album_gain': + $ThisFileInfo['replay_gain']['album']['adjustment'] = (double) $commentvalue[0]; + unset($ThisFileInfo['ogg']['comments'][$index]); + break; + + case 'rg_radio': + case 'replaygain_track_gain': + $ThisFileInfo['replay_gain']['track']['adjustment'] = (double) $commentvalue[0]; + unset($ThisFileInfo['ogg']['comments'][$index]); + break; + + case 'replaygain_album_peak': + $ThisFileInfo['replay_gain']['album']['peak'] = (double) $commentvalue[0]; + unset($ThisFileInfo['ogg']['comments'][$index]); + break; + + case 'rg_peak': + case 'replaygain_track_peak': + $ThisFileInfo['replay_gain']['track']['peak'] = (double) $commentvalue[0]; + unset($ThisFileInfo['ogg']['comments'][$index]); + break; + + + default: + // do nothing + break; + } + } + } + + fseek($fd, $OriginalOffset, SEEK_SET); + + return true; + } + + function SpeexBandModeLookup($mode) { + static $SpeexBandModeLookup = array(); + if (empty($SpeexBandModeLookup)) { + $SpeexBandModeLookup[0] = 'narrow'; + $SpeexBandModeLookup[1] = 'wide'; + $SpeexBandModeLookup[2] = 'ultra-wide'; + } + return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null); + } + + + function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) { + for ($i = 0; $i < $SegmentNumber; $i++) { + $segmentlength = 0; + foreach ($OggInfoArray['segment_table'] as $key => $value) { + $segmentlength += $value; + if ($value < 255) { + break; + } + } + } + return $segmentlength; + } + + + function get_quality_from_nominal_bitrate($nominal_bitrate) { + + // decrease precision + $nominal_bitrate = $nominal_bitrate / 1000; + + if ($nominal_bitrate < 128) { + // q-1 to q4 + $qval = ($nominal_bitrate - 64) / 16; + } elseif ($nominal_bitrate < 256) { + // q4 to q8 + $qval = $nominal_bitrate / 32; + } elseif ($nominal_bitrate < 320) { + // q8 to q9 + $qval = ($nominal_bitrate + 256) / 64; + } else { + // q9 to q10 + $qval = ($nominal_bitrate + 1300) / 180; + } + //return $qval; // 5.031324 + //return intval($qval); // 5 + return round($qval, 1); // 5 or 4.9 + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.optimfrog.php b/modules/id3/getid3/module.audio.optimfrog.php new file mode 100644 index 00000000..3c2dfb0b --- /dev/null +++ b/modules/id3/getid3/module.audio.optimfrog.php @@ -0,0 +1,408 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.optimfrog.php // +// module for analyzing OptimFROG audio files // +// dependencies: module.audio.riff.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + +class getid3_optimfrog +{ + + function getid3_optimfrog(&$fd, &$ThisFileInfo) { + $ThisFileInfo['fileformat'] = 'ofr'; + $ThisFileInfo['audio']['dataformat'] = 'ofr'; + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + $ThisFileInfo['audio']['lossless'] = true; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $OFRheader = fread($fd, 8); + if (substr($OFRheader, 0, 5) == '*RIFF') { + + return $this->ParseOptimFROGheader42($fd, $ThisFileInfo); + + } elseif (substr($OFRheader, 0, 3) == 'OFR') { + + return $this->ParseOptimFROGheader45($fd, $ThisFileInfo); + + } + + $ThisFileInfo['error'][] = 'Expecting "*RIFF" or "OFR " at offset '.$ThisFileInfo['avdataoffset'].', found "'.$OFRheader.'"'; + unset($ThisFileInfo['fileformat']); + return false; + } + + + function ParseOptimFROGheader42(&$fd, &$ThisFileInfo) { + // for fileformat of v4.21 and older + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $OptimFROGheaderData = fread($fd, 45); + $ThisFileInfo['avdataoffset'] = 45; + + $OptimFROGencoderVersion_raw = getid3_lib::LittleEndian2Int(substr($OptimFROGheaderData, 0, 1)); + $OptimFROGencoderVersion_major = floor($OptimFROGencoderVersion_raw / 10); + $OptimFROGencoderVersion_minor = $OptimFROGencoderVersion_raw - ($OptimFROGencoderVersion_major * 10); + $RIFFdata = substr($OptimFROGheaderData, 1, 44); + $OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8; + $OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44; + + if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) { + $ThisFileInfo['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize); + fseek($fd, $ThisFileInfo['avdataend'], SEEK_SET); + $RIFFdata .= fread($fd, $OrignalRIFFheaderSize - $OrignalRIFFdataSize); + } + + // move the data chunk after all other chunks (if any) + // so that the RIFF parser doesn't see EOF when trying + // to skip over the data chunk + $RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8); + getid3_riff::ParseRIFFdata($RIFFdata, $ThisFileInfo); + + $ThisFileInfo['audio']['encoder'] = 'OptimFROG '.$OptimFROGencoderVersion_major.'.'.$OptimFROGencoderVersion_minor; + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['riff']['audio'][0]['channels']; + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['riff']['audio'][0]['sample_rate']; + $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['riff']['audio'][0]['bits_per_sample']; + $ThisFileInfo['playtime_seconds'] = $OrignalRIFFdataSize / ($ThisFileInfo['audio']['channels'] * $ThisFileInfo['audio']['sample_rate'] * ($ThisFileInfo['audio']['bits_per_sample'] / 8)); + $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + + return true; + } + + + function ParseOptimFROGheader45(&$fd, &$ThisFileInfo) { + // for fileformat of v4.50a and higher + + $RIFFdata = ''; + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + while (!feof($fd) && (ftell($fd) < $ThisFileInfo['avdataend'])) { + $BlockOffset = ftell($fd); + $BlockData = fread($fd, 8); + $offset = 8; + $BlockName = substr($BlockData, 0, 4); + $BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4)); + + if ($BlockName == 'OFRX') { + $BlockName = 'OFR '; + } + if (!isset($ThisFileInfo['ofr'][$BlockName])) { + $ThisFileInfo['ofr'][$BlockName] = array(); + } + $thisfile_ofr_thisblock = &$ThisFileInfo['ofr'][$BlockName]; + + switch ($BlockName) { + case 'OFR ': + + // shortcut + $thisfile_ofr_thisblock['offset'] = $BlockOffset; + $thisfile_ofr_thisblock['size'] = $BlockSize; + + $ThisFileInfo['audio']['encoder'] = 'OptimFROG 4.50 alpha'; + switch ($BlockSize) { + case 12: + case 15: + // good + break; + + default: + $ThisFileInfo['warning'][] = '"'.$BlockName.'" contains more data than expected (expected 12 or 15 bytes, found '.$BlockSize.' bytes)'; + break; + } + $BlockData .= fread($fd, $BlockSize); + + $thisfile_ofr_thisblock['total_samples'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 6)); + $offset += 6; + $thisfile_ofr_thisblock['raw']['sample_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); + $thisfile_ofr_thisblock['sample_type'] = $this->OptimFROGsampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']); + $offset += 1; + $thisfile_ofr_thisblock['channel_config'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); + $thisfile_ofr_thisblock['channels'] = $thisfile_ofr_thisblock['channel_config']; + $offset += 1; + $thisfile_ofr_thisblock['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4)); + $offset += 4; + + if ($BlockSize > 12) { + + // OFR 4.504b or higher + $thisfile_ofr_thisblock['channels'] = $this->OptimFROGchannelConfigNumChannelsLookup($thisfile_ofr_thisblock['channel_config']); + $thisfile_ofr_thisblock['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2)); + $thisfile_ofr_thisblock['encoder'] = $this->OptimFROGencoderNameLookup($thisfile_ofr_thisblock['raw']['encoder_id']); + $offset += 2; + $thisfile_ofr_thisblock['raw']['compression'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); + $thisfile_ofr_thisblock['compression'] = $this->OptimFROGcompressionLookup($thisfile_ofr_thisblock['raw']['compression']); + $thisfile_ofr_thisblock['speedup'] = $this->OptimFROGspeedupLookup($thisfile_ofr_thisblock['raw']['compression']); + $offset += 1; + + $ThisFileInfo['audio']['encoder'] = 'OptimFROG '.$thisfile_ofr_thisblock['encoder']; + $ThisFileInfo['audio']['encoder_options'] = '--mode '.$thisfile_ofr_thisblock['compression']; + + if ((($thisfile_ofr_thisblock['raw']['encoder_id'] & 0xF0) >> 4) == 7) { // v4.507 + if (strtolower(getid3_lib::fileextension($ThisFileInfo['filename'])) == 'ofs') { + // OptimFROG DualStream format is lossy, but as of v4.507 there is no way to tell the difference + // between lossless and lossy other than the file extension. + $ThisFileInfo['audio']['dataformat'] = 'ofs'; + $ThisFileInfo['audio']['lossless'] = true; + } + } + + } + + $ThisFileInfo['audio']['channels'] = $thisfile_ofr_thisblock['channels']; + $ThisFileInfo['audio']['sample_rate'] = $thisfile_ofr_thisblock['sample_rate']; + $ThisFileInfo['audio']['bits_per_sample'] = $this->OptimFROGbitsPerSampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']); + break; + + + case 'COMP': + // unlike other block types, there CAN be multiple COMP blocks + + $COMPdata['offset'] = $BlockOffset; + $COMPdata['size'] = $BlockSize; + + if ($ThisFileInfo['avdataoffset'] == 0) { + $ThisFileInfo['avdataoffset'] = $BlockOffset; + } + + // Only interested in first 14 bytes (only first 12 needed for v4.50 alpha), not actual audio data + $BlockData .= fread($fd, 14); + fseek($fd, $BlockSize - 14, SEEK_CUR); + + $COMPdata['crc_32'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4)); + $offset += 4; + $COMPdata['sample_count'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4)); + $offset += 4; + $COMPdata['raw']['sample_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); + $COMPdata['sample_type'] = $this->OptimFROGsampleTypeLookup($COMPdata['raw']['sample_type']); + $offset += 1; + $COMPdata['raw']['channel_configuration'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1)); + $COMPdata['channel_configuration'] = $this->OptimFROGchannelConfigurationLookup($COMPdata['raw']['channel_configuration']); + $offset += 1; + $COMPdata['raw']['algorithm_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2)); + //$COMPdata['algorithm'] = OptimFROGalgorithmNameLookup($COMPdata['raw']['algorithm_id']); + $offset += 2; + + if ($ThisFileInfo['ofr']['OFR ']['size'] > 12) { + + // OFR 4.504b or higher + $COMPdata['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2)); + $COMPdata['encoder'] = $this->OptimFROGencoderNameLookup($COMPdata['raw']['encoder_id']); + $offset += 2; + + } + + if ($COMPdata['crc_32'] == 0x454E4F4E) { + // ASCII value of 'NONE' - placeholder value in v4.50a + $COMPdata['crc_32'] = false; + } + + $thisfile_ofr_thisblock[] = $COMPdata; + break; + + case 'HEAD': + $thisfile_ofr_thisblock['offset'] = $BlockOffset; + $thisfile_ofr_thisblock['size'] = $BlockSize; + + $RIFFdata .= fread($fd, $BlockSize); + break; + + case 'TAIL': + $thisfile_ofr_thisblock['offset'] = $BlockOffset; + $thisfile_ofr_thisblock['size'] = $BlockSize; + + if ($BlockSize > 0) { + $RIFFdata .= fread($fd, $BlockSize); + } + break; + + case 'RECV': + // block contains no useful meta data - simply note and skip + + $thisfile_ofr_thisblock['offset'] = $BlockOffset; + $thisfile_ofr_thisblock['size'] = $BlockSize; + + fseek($fd, $BlockSize, SEEK_CUR); + break; + + + case 'APET': + // APEtag v2 + + $thisfile_ofr_thisblock['offset'] = $BlockOffset; + $thisfile_ofr_thisblock['size'] = $BlockSize; + $ThisFileInfo['warning'][] = 'APEtag processing inside OptimFROG not supported in this version ('.GETID3_VERSION.') of getID3()'; + + fseek($fd, $BlockSize, SEEK_CUR); + break; + + + case 'MD5 ': + // APEtag v2 + + $thisfile_ofr_thisblock['offset'] = $BlockOffset; + $thisfile_ofr_thisblock['size'] = $BlockSize; + + if ($BlockSize == 16) { + + $thisfile_ofr_thisblock['md5_binary'] = fread($fd, $BlockSize); + $thisfile_ofr_thisblock['md5_string'] = getid3_lib::PrintHexBytes($thisfile_ofr_thisblock['md5_binary'], true, false, false); + $ThisFileInfo['md5_data_source'] = $thisfile_ofr_thisblock['md5_string']; + + } else { + + $ThisFileInfo['warning'][] = 'Expecting block size of 16 in "MD5 " chunk, found '.$BlockSize.' instead'; + fseek($fd, $BlockSize, SEEK_CUR); + + } + break; + + + default: + $thisfile_ofr_thisblock['offset'] = $BlockOffset; + $thisfile_ofr_thisblock['size'] = $BlockSize; + + $ThisFileInfo['warning'][] = 'Unhandled OptimFROG block type "'.$BlockName.'" at offset '.$thisfile_ofr_thisblock['offset']; + fseek($fd, $BlockSize, SEEK_CUR); + break; + } + } + if (isset($ThisFileInfo['ofr']['TAIL']['offset'])) { + $ThisFileInfo['avdataend'] = $ThisFileInfo['ofr']['TAIL']['offset']; + } + + $ThisFileInfo['playtime_seconds'] = (float) $ThisFileInfo['ofr']['OFR ']['total_samples'] / ($ThisFileInfo['audio']['channels'] * $ThisFileInfo['audio']['sample_rate']); + $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + + // move the data chunk after all other chunks (if any) + // so that the RIFF parser doesn't see EOF when trying + // to skip over the data chunk + $RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8); + getid3_riff::ParseRIFFdata($RIFFdata, $ThisFileInfo); + + return true; + } + + + function OptimFROGsampleTypeLookup($SampleType) { + static $OptimFROGsampleTypeLookup = array( + 0 => 'unsigned int (8-bit)', + 1 => 'signed int (8-bit)', + 2 => 'unsigned int (16-bit)', + 3 => 'signed int (16-bit)', + 4 => 'unsigned int (24-bit)', + 5 => 'signed int (24-bit)', + 6 => 'unsigned int (32-bit)', + 7 => 'signed int (32-bit)', + 8 => 'float 0.24 (32-bit)', + 9 => 'float 16.8 (32-bit)', + 10 => 'float 24.0 (32-bit)' + ); + return (isset($OptimFROGsampleTypeLookup[$SampleType]) ? $OptimFROGsampleTypeLookup[$SampleType] : false); + } + + function OptimFROGbitsPerSampleTypeLookup($SampleType) { + static $OptimFROGbitsPerSampleTypeLookup = array( + 0 => 8, + 1 => 8, + 2 => 16, + 3 => 16, + 4 => 24, + 5 => 24, + 6 => 32, + 7 => 32, + 8 => 32, + 9 => 32, + 10 => 32 + ); + return (isset($OptimFROGbitsPerSampleTypeLookup[$SampleType]) ? $OptimFROGbitsPerSampleTypeLookup[$SampleType] : false); + } + + function OptimFROGchannelConfigurationLookup($ChannelConfiguration) { + static $OptimFROGchannelConfigurationLookup = array( + 0 => 'mono', + 1 => 'stereo' + ); + return (isset($OptimFROGchannelConfigurationLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigurationLookup[$ChannelConfiguration] : false); + } + + function OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration) { + static $OptimFROGchannelConfigNumChannelsLookup = array( + 0 => 1, + 1 => 2 + ); + return (isset($OptimFROGchannelConfigNumChannelsLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigNumChannelsLookup[$ChannelConfiguration] : false); + } + + + + // function OptimFROGalgorithmNameLookup($AlgorithID) { + // static $OptimFROGalgorithmNameLookup = array(); + // return (isset($OptimFROGalgorithmNameLookup[$AlgorithID]) ? $OptimFROGalgorithmNameLookup[$AlgorithID] : false); + // } + + + function OptimFROGencoderNameLookup($EncoderID) { + // version = (encoderID >> 4) + 4500 + // system = encoderID & 0xF + + $EncoderVersion = number_format(((($EncoderID & 0xF0) >> 4) + 4500) / 1000, 3); + $EncoderSystemID = ($EncoderID & 0x0F); + + static $OptimFROGencoderSystemLookup = array( + 0x00 => 'Windows console', + 0x01 => 'Linux console', + 0x0F => 'unknown' + ); + return $EncoderVersion.' ('.(isset($OptimFROGencoderSystemLookup[$EncoderSystemID]) ? $OptimFROGencoderSystemLookup[$EncoderSystemID] : 'undefined encoder type (0x'.dechex($EncoderSystemID).')').')'; + } + + function OptimFROGcompressionLookup($CompressionID) { + // mode = compression >> 3 + // speedup = compression & 0x07 + + $CompressionModeID = ($CompressionID & 0xF8) >> 3; + //$CompressionSpeedupID = ($CompressionID & 0x07); + + static $OptimFROGencoderModeLookup = array( + 0x00 => 'fast', + 0x01 => 'normal', + 0x02 => 'high', + 0x03 => 'extra', // extranew (some versions) + 0x04 => 'best', // bestnew (some versions) + 0x05 => 'ultra', + 0x06 => 'insane', + 0x07 => 'highnew', + 0x08 => 'extranew', + 0x09 => 'bestnew' + ); + return (isset($OptimFROGencoderModeLookup[$CompressionModeID]) ? $OptimFROGencoderModeLookup[$CompressionModeID] : 'undefined mode (0x'.str_pad(dechex($CompressionModeID), 2, '0', STR_PAD_LEFT).')'); + } + + function OptimFROGspeedupLookup($CompressionID) { + // mode = compression >> 3 + // speedup = compression & 0x07 + + //$CompressionModeID = ($CompressionID & 0xF8) >> 3; + $CompressionSpeedupID = ($CompressionID & 0x07); + + static $OptimFROGencoderSpeedupLookup = array( + 0x00 => '1x', + 0x01 => '2x', + 0x02 => '4x' + ); + + return (isset($OptimFROGencoderSpeedupLookup[$CompressionSpeedupID]) ? $OptimFROGencoderSpeedupLookup[$CompressionSpeedupID] : 'undefined mode (0x'.dechex($CompressionSpeedupID)); + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.rkau.php b/modules/id3/getid3/module.audio.rkau.php new file mode 100644 index 00000000..0e344d43 --- /dev/null +++ b/modules/id3/getid3/module.audio.rkau.php @@ -0,0 +1,92 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.shorten.php // +// module for analyzing Shorten Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_rkau +{ + + function getid3_rkau(&$fd, &$ThisFileInfo) { + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $RKAUHeader = fread($fd, 20); + if (substr($RKAUHeader, 0, 3) != 'RKA') { + $ThisFileInfo['error'][] = 'Expecting "RKA" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($RKAUHeader, 0, 3).'"'; + return false; + } + + $ThisFileInfo['fileformat'] = 'rkau'; + $ThisFileInfo['audio']['dataformat'] = 'rkau'; + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + + $ThisFileInfo['rkau']['raw']['version'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 3, 1)); + $ThisFileInfo['rkau']['version'] = '1.'.str_pad($ThisFileInfo['rkau']['raw']['version'] & 0x0F, 2, '0', STR_PAD_LEFT); + if (($ThisFileInfo['rkau']['version'] > 1.07) || ($ThisFileInfo['rkau']['version'] < 1.06)) { + $ThisFileInfo['error'][] = 'This version of getID3() can only parse RKAU files v1.06 and 1.07 (this file is v'.$ThisFileInfo['rkau']['version'].')'; + unset($ThisFileInfo['rkau']); + return false; + } + + $ThisFileInfo['rkau']['source_bytes'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 4, 4)); + $ThisFileInfo['rkau']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 8, 4)); + $ThisFileInfo['rkau']['channels'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 12, 1)); + $ThisFileInfo['rkau']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 13, 1)); + + $ThisFileInfo['rkau']['raw']['quality'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 14, 1)); + $this->RKAUqualityLookup($ThisFileInfo['rkau']); + + $ThisFileInfo['rkau']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 15, 1)); + $ThisFileInfo['rkau']['flags']['joint_stereo'] = (bool) (!($ThisFileInfo['rkau']['raw']['flags'] & 0x01)); + $ThisFileInfo['rkau']['flags']['streaming'] = (bool) ($ThisFileInfo['rkau']['raw']['flags'] & 0x02); + $ThisFileInfo['rkau']['flags']['vrq_lossy_mode'] = (bool) ($ThisFileInfo['rkau']['raw']['flags'] & 0x04); + + if ($ThisFileInfo['rkau']['flags']['streaming']) { + $ThisFileInfo['avdataoffset'] += 20; + $ThisFileInfo['rkau']['compressed_bytes'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 16, 4)); + } else { + $ThisFileInfo['avdataoffset'] += 16; + $ThisFileInfo['rkau']['compressed_bytes'] = $ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'] - 1; + } + // Note: compressed_bytes does not always equal what appears to be the actual number of compressed bytes, + // sometimes it's more, sometimes less. No idea why(?) + + $ThisFileInfo['audio']['lossless'] = $ThisFileInfo['rkau']['lossless']; + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['rkau']['channels']; + $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['rkau']['bits_per_sample']; + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['rkau']['sample_rate']; + + $ThisFileInfo['playtime_seconds'] = $ThisFileInfo['rkau']['source_bytes'] / ($ThisFileInfo['rkau']['sample_rate'] * $ThisFileInfo['rkau']['channels'] * ($ThisFileInfo['rkau']['bits_per_sample'] / 8)); + $ThisFileInfo['audio']['bitrate'] = ($ThisFileInfo['rkau']['compressed_bytes'] * 8) / $ThisFileInfo['playtime_seconds']; + + return true; + + } + + + function RKAUqualityLookup(&$RKAUdata) { + $level = ($RKAUdata['raw']['quality'] & 0xF0) >> 4; + $quality = $RKAUdata['raw']['quality'] & 0x0F; + + $RKAUdata['lossless'] = (($quality == 0) ? true : false); + $RKAUdata['compression_level'] = $level + 1; + if (!$RKAUdata['lossless']) { + $RKAUdata['quality_setting'] = $quality; + } + + return true; + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.shorten.php b/modules/id3/getid3/module.audio.shorten.php new file mode 100644 index 00000000..2088db72 --- /dev/null +++ b/modules/id3/getid3/module.audio.shorten.php @@ -0,0 +1,178 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.shorten.php // +// module for analyzing Shorten Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_shorten +{ + + function getid3_shorten(&$fd, &$ThisFileInfo) { + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + + $ShortenHeader = fread($fd, 8); + if (substr($ShortenHeader, 0, 4) != 'ajkg') { + $ThisFileInfo['error'][] = 'Expecting "ajkg" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($ShortenHeader, 0, 4).'"'; + return false; + } + $ThisFileInfo['fileformat'] = 'shn'; + $ThisFileInfo['audio']['dataformat'] = 'shn'; + $ThisFileInfo['audio']['lossless'] = true; + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + + $ThisFileInfo['shn']['version'] = getid3_lib::LittleEndian2Int(substr($ShortenHeader, 4, 1)); + + fseek($fd, $ThisFileInfo['avdataend'] - 12, SEEK_SET); + $SeekTableSignatureTest = fread($fd, 12); + $ThisFileInfo['shn']['seektable']['present'] = (bool) (substr($SeekTableSignatureTest, 4, 8) == 'SHNAMPSK'); + if ($ThisFileInfo['shn']['seektable']['present']) { + $ThisFileInfo['shn']['seektable']['length'] = getid3_lib::LittleEndian2Int(substr($SeekTableSignatureTest, 0, 4)); + $ThisFileInfo['shn']['seektable']['offset'] = $ThisFileInfo['avdataend'] - $ThisFileInfo['shn']['seektable']['length']; + fseek($fd, $ThisFileInfo['shn']['seektable']['offset'], SEEK_SET); + $SeekTableMagic = fread($fd, 4); + if ($SeekTableMagic != 'SEEK') { + + $ThisFileInfo['error'][] = 'Expecting "SEEK" at offset '.$ThisFileInfo['shn']['seektable']['offset'].', found "'.$SeekTableMagic.'"'; + return false; + + } else { + + // typedef struct tag_TSeekEntry + // { + // unsigned long SampleNumber; + // unsigned long SHNFileByteOffset; + // unsigned long SHNLastBufferReadPosition; + // unsigned short SHNByteGet; + // unsigned short SHNBufferOffset; + // unsigned short SHNFileBitOffset; + // unsigned long SHNGBuffer; + // unsigned short SHNBitShift; + // long CBuf0[3]; + // long CBuf1[3]; + // long Offset0[4]; + // long Offset1[4]; + // }TSeekEntry; + + $SeekTableData = fread($fd, $ThisFileInfo['shn']['seektable']['length'] - 16); + $ThisFileInfo['shn']['seektable']['entry_count'] = floor(strlen($SeekTableData) / 80); + //$ThisFileInfo['shn']['seektable']['entries'] = array(); + //$SeekTableOffset = 0; + //for ($i = 0; $i < $ThisFileInfo['shn']['seektable']['entry_count']; $i++) { + // $SeekTableEntry['sample_number'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4)); + // $SeekTableOffset += 4; + // $SeekTableEntry['shn_file_byte_offset'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4)); + // $SeekTableOffset += 4; + // $SeekTableEntry['shn_last_buffer_read_position'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4)); + // $SeekTableOffset += 4; + // $SeekTableEntry['shn_byte_get'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2)); + // $SeekTableOffset += 2; + // $SeekTableEntry['shn_buffer_offset'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2)); + // $SeekTableOffset += 2; + // $SeekTableEntry['shn_file_bit_offset'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2)); + // $SeekTableOffset += 2; + // $SeekTableEntry['shn_gbuffer'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4)); + // $SeekTableOffset += 4; + // $SeekTableEntry['shn_bit_shift'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2)); + // $SeekTableOffset += 2; + // for ($j = 0; $j < 3; $j++) { + // $SeekTableEntry['cbuf0'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4)); + // $SeekTableOffset += 4; + // } + // for ($j = 0; $j < 3; $j++) { + // $SeekTableEntry['cbuf1'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4)); + // $SeekTableOffset += 4; + // } + // for ($j = 0; $j < 4; $j++) { + // $SeekTableEntry['offset0'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4)); + // $SeekTableOffset += 4; + // } + // for ($j = 0; $j < 4; $j++) { + // $SeekTableEntry['offset1'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4)); + // $SeekTableOffset += 4; + // } + // + // $ThisFileInfo['shn']['seektable']['entries'][] = $SeekTableEntry; + //} + + } + + } + + if ((bool) ini_get('safe_mode')) { + $ThisFileInfo['error'][] = 'PHP running in Safe Mode - backtick operator not available, cannot run shntool to analyze Shorten files'; + return false; + } + + if (GETID3_OS_ISWINDOWS) { + + $RequiredFiles = array('shorten.exe', 'cygwin1.dll', 'head.exe'); + foreach ($RequiredFiles as $required_file) { + if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) { + $ThisFileInfo['error'][] = GETID3_HELPERAPPSDIR.$required_file.' does not exist'; + return false; + } + } + $commandline = GETID3_HELPERAPPSDIR.'shorten.exe -x "'.$ThisFileInfo['filenamepath'].'" - | '.GETID3_HELPERAPPSDIR.'head.exe -c 44'; + + } else { + + static $shorten_present; + if (!isset($shorten_present)) { + $shorten_present = file_exists('/usr/local/bin/shorten') || `which shorten`; + } + if (!$shorten_present) { + $ThisFileInfo['error'][] = 'shorten binary was not found in path or /usr/local/bin'; + return false; + } + $commandline = (file_exists('/usr/local/bin/shorten') ? '/usr/local/bin/' : '' ) . 'shorten -x "'.$ThisFileInfo['filenamepath'].'" - | head -c 44'; + + } + + $output = `$commandline`; + + if (!empty($output) && (substr($output, 12, 4) == 'fmt ')) { + + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + + $DecodedWAVFORMATEX = getid3_riff::RIFFparseWAVEFORMATex(substr($output, 20, 16)); + $ThisFileInfo['audio']['channels'] = $DecodedWAVFORMATEX['channels']; + $ThisFileInfo['audio']['bits_per_sample'] = $DecodedWAVFORMATEX['bits_per_sample']; + $ThisFileInfo['audio']['sample_rate'] = $DecodedWAVFORMATEX['sample_rate']; + + if (substr($output, 36, 4) == 'data') { + + $ThisFileInfo['playtime_seconds'] = getid3_lib::LittleEndian2Int(substr($output, 40, 4)) / $DecodedWAVFORMATEX['raw']['nAvgBytesPerSec']; + + } else { + + $ThisFileInfo['error'][] = 'shorten failed to decode DATA chunk to expected location, cannot determine playtime'; + return false; + + } + + $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) / $ThisFileInfo['playtime_seconds']) * 8; + + } else { + + $ThisFileInfo['error'][] = 'shorten failed to decode file to WAV for parsing'; + return false; + + } + + return true; + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.tta.php b/modules/id3/getid3/module.audio.tta.php new file mode 100644 index 00000000..903de6bf --- /dev/null +++ b/modules/id3/getid3/module.audio.tta.php @@ -0,0 +1,107 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.tta.php // +// module for analyzing TTA Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_tta +{ + + function getid3_tta(&$fd, &$ThisFileInfo) { + + $ThisFileInfo['fileformat'] = 'tta'; + $ThisFileInfo['audio']['dataformat'] = 'tta'; + $ThisFileInfo['audio']['lossless'] = true; + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $ttaheader = fread($fd, 26); + + $ThisFileInfo['tta']['magic'] = substr($ttaheader, 0, 3); + if ($ThisFileInfo['tta']['magic'] != 'TTA') { + $ThisFileInfo['error'][] = 'Expecting "TTA" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$ThisFileInfo['tta']['magic'].'"'; + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['audio']); + unset($ThisFileInfo['tta']); + return false; + } + + switch ($ttaheader{3}) { + case "\x01": // TTA v1.x + case "\x02": // TTA v1.x + case "\x03": // TTA v1.x + // "It was the demo-version of the TTA encoder. There is no released format with such header. TTA encoder v1 is not supported about a year." + $ThisFileInfo['tta']['major_version'] = 1; + $ThisFileInfo['avdataoffset'] += 16; + + $ThisFileInfo['tta']['compression_level'] = ord($ttaheader{3}); + $ThisFileInfo['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2)); + $ThisFileInfo['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2)); + $ThisFileInfo['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 4)); + $ThisFileInfo['tta']['samples_per_channel'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 12, 4)); + + $ThisFileInfo['audio']['encoder_options'] = '-e'.$ThisFileInfo['tta']['compression_level']; + $ThisFileInfo['playtime_seconds'] = $ThisFileInfo['tta']['samples_per_channel'] / $ThisFileInfo['tta']['sample_rate']; + break; + + case '2': // TTA v2.x + // "I have hurried to release the TTA 2.0 encoder. Format documentation is removed from our site. This format still in development. Please wait the TTA2 format, encoder v4." + $ThisFileInfo['tta']['major_version'] = 2; + $ThisFileInfo['avdataoffset'] += 20; + + $ThisFileInfo['tta']['compression_level'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2)); + $ThisFileInfo['tta']['audio_format'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2)); + $ThisFileInfo['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 2)); + $ThisFileInfo['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 10, 2)); + $ThisFileInfo['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 12, 4)); + $ThisFileInfo['tta']['data_length'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 16, 4)); + + $ThisFileInfo['audio']['encoder_options'] = '-e'.$ThisFileInfo['tta']['compression_level']; + $ThisFileInfo['playtime_seconds'] = $ThisFileInfo['tta']['data_length'] / $ThisFileInfo['tta']['sample_rate']; + break; + + case '1': // TTA v3.x + // "This is a first stable release of the TTA format. It will be supported by the encoders v3 or higher." + $ThisFileInfo['tta']['major_version'] = 3; + $ThisFileInfo['avdataoffset'] += 26; + + $ThisFileInfo['tta']['audio_format'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2)); // getid3_riff::RIFFwFormatTagLookup() + $ThisFileInfo['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2)); + $ThisFileInfo['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 2)); + $ThisFileInfo['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 10, 4)); + $ThisFileInfo['tta']['data_length'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 14, 4)); + $ThisFileInfo['tta']['crc32_footer'] = substr($ttaheader, 18, 4); + $ThisFileInfo['tta']['seek_point'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 22, 4)); + + $ThisFileInfo['playtime_seconds'] = $ThisFileInfo['tta']['data_length'] / $ThisFileInfo['tta']['sample_rate']; + break; + + default: + $ThisFileInfo['error'][] = 'This version of getID3() only knows how to handle TTA v1 and v2 - it may not work correctly with this file which appears to be TTA v'.$ttaheader{3}; + return false; + break; + } + + $ThisFileInfo['audio']['encoder'] = 'TTA v'.$ThisFileInfo['tta']['major_version']; + $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['tta']['bits_per_sample']; + $ThisFileInfo['audio']['sample_rate'] = $ThisFileInfo['tta']['sample_rate']; + $ThisFileInfo['audio']['channels'] = $ThisFileInfo['tta']['channels']; + $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + + return true; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.voc.php b/modules/id3/getid3/module.audio.voc.php new file mode 100644 index 00000000..e93b44fa --- /dev/null +++ b/modules/id3/getid3/module.audio.voc.php @@ -0,0 +1,205 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.voc.php // +// module for analyzing Creative VOC Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_voc +{ + + function getid3_voc(&$fd, &$ThisFileInfo) { + + $OriginalAVdataOffset = $ThisFileInfo['avdataoffset']; + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $VOCheader = fread($fd, 26); + + if (substr($VOCheader, 0, 19) != 'Creative Voice File') { + $ThisFileInfo['error'][] = 'Expecting "Creative Voice File" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($VOCheader, 0, 19).'"'; + return false; + } + + // shortcuts + $thisfile_audio = &$ThisFileInfo['audio']; + $ThisFileInfo['voc'] = array(); + $thisfile_voc = &$ThisFileInfo['voc']; + + $ThisFileInfo['fileformat'] = 'voc'; + $thisfile_audio['dataformat'] = 'voc'; + $thisfile_audio['bitrate_mode'] = 'cbr'; + $thisfile_audio['lossless'] = true; + $thisfile_audio['channels'] = 1; // might be overriden below + $thisfile_audio['bits_per_sample'] = 8; // might be overriden below + + // byte # Description + // ------ ------------------------------------------ + // 00-12 'Creative Voice File' + // 13 1A (eof to abort printing of file) + // 14-15 Offset of first datablock in .voc file (std 1A 00 in Intel Notation) + // 16-17 Version number (minor,major) (VOC-HDR puts 0A 01) + // 18-19 2's Comp of Ver. # + 1234h (VOC-HDR puts 29 11) + + $thisfile_voc['header']['datablock_offset'] = getid3_lib::LittleEndian2Int(substr($VOCheader, 20, 2)); + $thisfile_voc['header']['minor_version'] = getid3_lib::LittleEndian2Int(substr($VOCheader, 22, 1)); + $thisfile_voc['header']['major_version'] = getid3_lib::LittleEndian2Int(substr($VOCheader, 23, 1)); + + do { + + $BlockOffset = ftell($fd); + $BlockData = fread($fd, 4); + $BlockType = ord($BlockData{0}); + $BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 1, 3)); + $ThisBlock = array(); + + @$thisfile_voc['blocktypes'][$BlockType]++; + switch ($BlockType) { + case 0: // Terminator + // do nothing, we'll break out of the loop down below + break; + + case 1: // Sound data + $BlockData .= fread($fd, 2); + if ($ThisFileInfo['avdataoffset'] <= $OriginalAVdataOffset) { + $ThisFileInfo['avdataoffset'] = ftell($fd); + } + fseek($fd, $BlockSize - 2, SEEK_CUR); + + $ThisBlock['sample_rate_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 1)); + $ThisBlock['compression_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, 5, 1)); + + $ThisBlock['compression_name'] = $this->VOCcompressionTypeLookup($ThisBlock['compression_type']); + if ($ThisBlock['compression_type'] <= 3) { + $thisfile_voc['compressed_bits_per_sample'] = getid3_lib::CastAsInt(str_replace('-bit', '', $ThisBlock['compression_name'])); + } + + // Less accurate sample_rate calculation than the Extended block (#8) data (but better than nothing if Extended Block is not available) + if (empty($thisfile_audio['sample_rate'])) { + // SR byte = 256 - (1000000 / sample_rate) + $thisfile_audio['sample_rate'] = getid3_lib::trunc((1000000 / (256 - $ThisBlock['sample_rate_id'])) / $thisfile_audio['channels']); + } + break; + + case 2: // Sound continue + case 3: // Silence + case 4: // Marker + case 6: // Repeat + case 7: // End repeat + // nothing useful, just skip + fseek($fd, $BlockSize, SEEK_CUR); + break; + + case 8: // Extended + $BlockData .= fread($fd, 4); + + //00-01 Time Constant: + // Mono: 65536 - (256000000 / sample_rate) + // Stereo: 65536 - (256000000 / (sample_rate * 2)) + $ThisBlock['time_constant'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 2)); + $ThisBlock['pack_method'] = getid3_lib::LittleEndian2Int(substr($BlockData, 6, 1)); + $ThisBlock['stereo'] = (bool) getid3_lib::LittleEndian2Int(substr($BlockData, 7, 1)); + + $thisfile_audio['channels'] = ($ThisBlock['stereo'] ? 2 : 1); + $thisfile_audio['sample_rate'] = getid3_lib::trunc((256000000 / (65536 - $ThisBlock['time_constant'])) / $thisfile_audio['channels']); + break; + + case 9: // data block that supersedes blocks 1 and 8. Used for stereo, 16 bit + $BlockData .= fread($fd, 12); + if ($ThisFileInfo['avdataoffset'] <= $OriginalAVdataOffset) { + $ThisFileInfo['avdataoffset'] = ftell($fd); + } + fseek($fd, $BlockSize - 12, SEEK_CUR); + + $ThisBlock['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4)); + $ThisBlock['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($BlockData, 8, 1)); + $ThisBlock['channels'] = getid3_lib::LittleEndian2Int(substr($BlockData, 9, 1)); + $ThisBlock['wFormat'] = getid3_lib::LittleEndian2Int(substr($BlockData, 10, 2)); + + $ThisBlock['compression_name'] = $this->VOCwFormatLookup($ThisBlock['wFormat']); + if ($this->VOCwFormatActualBitsPerSampleLookup($ThisBlock['wFormat'])) { + $thisfile_voc['compressed_bits_per_sample'] = $this->VOCwFormatActualBitsPerSampleLookup($ThisBlock['wFormat']); + } + + $thisfile_audio['sample_rate'] = $ThisBlock['sample_rate']; + $thisfile_audio['bits_per_sample'] = $ThisBlock['bits_per_sample']; + $thisfile_audio['channels'] = $ThisBlock['channels']; + break; + + default: + $ThisFileInfo['warning'][] = 'Unhandled block type "'.$BlockType.'" at offset '.$BlockOffset; + fseek($fd, $BlockSize, SEEK_CUR); + break; + } + + if (!empty($ThisBlock)) { + $ThisBlock['block_offset'] = $BlockOffset; + $ThisBlock['block_size'] = $BlockSize; + $ThisBlock['block_type_id'] = $BlockType; + $thisfile_voc['blocks'][] = $ThisBlock; + } + + } while (!feof($fd) && ($BlockType != 0)); + + // Terminator block doesn't have size field, so seek back 3 spaces + fseek($fd, -3, SEEK_CUR); + + ksort($thisfile_voc['blocktypes']); + + if (!empty($thisfile_voc['compressed_bits_per_sample'])) { + $ThisFileInfo['playtime_seconds'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / ($thisfile_voc['compressed_bits_per_sample'] * $thisfile_audio['channels'] * $thisfile_audio['sample_rate']); + $thisfile_audio['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + } + + return true; + } + + function VOCcompressionTypeLookup($index) { + static $VOCcompressionTypeLookup = array( + 0 => '8-bit', + 1 => '4-bit', + 2 => '2.6-bit', + 3 => '2-bit' + ); + return (isset($VOCcompressionTypeLookup[$index]) ? $VOCcompressionTypeLookup[$index] : 'Multi DAC ('.($index - 3).') channels'); + } + + function VOCwFormatLookup($index) { + static $VOCwFormatLookup = array( + 0x0000 => '8-bit unsigned PCM', + 0x0001 => 'Creative 8-bit to 4-bit ADPCM', + 0x0002 => 'Creative 8-bit to 3-bit ADPCM', + 0x0003 => 'Creative 8-bit to 2-bit ADPCM', + 0x0004 => '16-bit signed PCM', + 0x0006 => 'CCITT a-Law', + 0x0007 => 'CCITT u-Law', + 0x2000 => 'Creative 16-bit to 4-bit ADPCM' + ); + return (isset($VOCwFormatLookup[$index]) ? $VOCwFormatLookup[$index] : false); + } + + function VOCwFormatActualBitsPerSampleLookup($index) { + static $VOCwFormatLookup = array( + 0x0000 => 8, + 0x0001 => 4, + 0x0002 => 3, + 0x0003 => 2, + 0x0004 => 16, + 0x0006 => 8, + 0x0007 => 8, + 0x2000 => 4 + ); + return (isset($VOCwFormatLookup[$index]) ? $VOCwFormatLookup[$index] : false); + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.vqf.php b/modules/id3/getid3/module.audio.vqf.php new file mode 100644 index 00000000..49d4e851 --- /dev/null +++ b/modules/id3/getid3/module.audio.vqf.php @@ -0,0 +1,159 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.vqf.php // +// module for analyzing VQF audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_vqf +{ + function getid3_vqf(&$fd, &$ThisFileInfo) { + // based loosely on code from TTwinVQ by Jurgen Faul <jfaulØgmx*de> + // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html + + $ThisFileInfo['fileformat'] = 'vqf'; + $ThisFileInfo['audio']['dataformat'] = 'vqf'; + $ThisFileInfo['audio']['bitrate_mode'] = 'cbr'; + $ThisFileInfo['audio']['lossless'] = false; + + // shortcut + $ThisFileInfo['vqf']['raw'] = array(); + $thisfile_vqf = &$ThisFileInfo['vqf']; + $thisfile_vqf_raw = &$thisfile_vqf['raw']; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $VQFheaderData = fread($fd, 16); + + $offset = 0; + $thisfile_vqf_raw['header_tag'] = substr($VQFheaderData, $offset, 4); + if ($thisfile_vqf_raw['header_tag'] != 'TWIN') { + $ThisFileInfo['error'][] = 'Expecting "TWIN" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$thisfile_vqf_raw['header_tag'].'"'; + unset($ThisFileInfo['vqf']); + unset($ThisFileInfo['fileformat']); + return false; + } + $offset += 4; + $thisfile_vqf_raw['version'] = substr($VQFheaderData, $offset, 8); + $offset += 8; + $thisfile_vqf_raw['size'] = getid3_lib::BigEndian2Int(substr($VQFheaderData, $offset, 4)); + $offset += 4; + + while (ftell($fd) < $ThisFileInfo['avdataend']) { + + $ChunkBaseOffset = ftell($fd); + $chunkoffset = 0; + $ChunkData = fread($fd, 8); + $ChunkName = substr($ChunkData, $chunkoffset, 4); + if ($ChunkName == 'DATA') { + $ThisFileInfo['avdataoffset'] = $ChunkBaseOffset; + break; + } + $chunkoffset += 4; + $ChunkSize = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); + $chunkoffset += 4; + if ($ChunkSize > ($ThisFileInfo['avdataend'] - ftell($fd))) { + $ThisFileInfo['error'][] = 'Invalid chunk size ('.$ChunkSize.') for chunk "'.$ChunkName.'" at offset '.$ChunkBaseOffset; + break; + } + if ($ChunkSize > 0) { + $ChunkData .= fread($fd, $ChunkSize); + } + + switch ($ChunkName) { + case 'COMM': + // shortcut + $thisfile_vqf['COMM'] = array(); + $thisfile_vqf_COMM = &$thisfile_vqf['COMM']; + + $thisfile_vqf_COMM['channel_mode'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); + $chunkoffset += 4; + $thisfile_vqf_COMM['bitrate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); + $chunkoffset += 4; + $thisfile_vqf_COMM['sample_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); + $chunkoffset += 4; + $thisfile_vqf_COMM['security_level'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4)); + $chunkoffset += 4; + + $ThisFileInfo['audio']['channels'] = $thisfile_vqf_COMM['channel_mode'] + 1; + $ThisFileInfo['audio']['sample_rate'] = $this->VQFchannelFrequencyLookup($thisfile_vqf_COMM['sample_rate']); + $ThisFileInfo['audio']['bitrate'] = $thisfile_vqf_COMM['bitrate'] * 1000; + $ThisFileInfo['audio']['encoder_options'] = 'CBR' . ceil($ThisFileInfo['audio']['bitrate']/1000); + + if ($ThisFileInfo['audio']['bitrate'] == 0) { + $ThisFileInfo['error'][] = 'Corrupt VQF file: bitrate_audio == zero'; + return false; + } + break; + + case 'NAME': + case 'AUTH': + case '(c) ': + case 'FILE': + case 'COMT': + case 'ALBM': + $thisfile_vqf['comments'][$this->VQFcommentNiceNameLookup($ChunkName)][] = trim(substr($ChunkData, 8)); + break; + + case 'DSIZ': + $thisfile_vqf['DSIZ'] = getid3_lib::BigEndian2Int(substr($ChunkData, 8, 4)); + break; + + default: + $ThisFileInfo['warning'][] = 'Unhandled chunk type "'.$ChunkName.'" at offset '.$ChunkBaseOffset; + break; + } + } + + $ThisFileInfo['playtime_seconds'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['audio']['bitrate']; + + if (isset($thisfile_vqf['DSIZ']) && (($thisfile_vqf['DSIZ'] != ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'] - strlen('DATA'))))) { + switch ($thisfile_vqf['DSIZ']) { + case 0: + case 1: + $ThisFileInfo['warning'][] = 'Invalid DSIZ value "'.$thisfile_vqf['DSIZ'].'". This is known to happen with VQF files encoded by Ahead Nero, and seems to be its way of saying this is TwinVQF v'.($thisfile_vqf['DSIZ'] + 1).'.0'; + $ThisFileInfo['audio']['encoder'] = 'Ahead Nero'; + break; + + default: + $ThisFileInfo['warning'][] = 'Probable corrupted file - should be '.$thisfile_vqf['DSIZ'].' bytes, actually '.($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset'] - strlen('DATA')); + break; + } + } + + return true; + } + + function VQFchannelFrequencyLookup($frequencyid) { + static $VQFchannelFrequencyLookup = array( + 11 => 11025, + 22 => 22050, + 44 => 44100 + ); + return (isset($VQFchannelFrequencyLookup[$frequencyid]) ? $VQFchannelFrequencyLookup[$frequencyid] : $frequencyid * 1000); + } + + function VQFcommentNiceNameLookup($shortname) { + static $VQFcommentNiceNameLookup = array( + 'NAME' => 'title', + 'AUTH' => 'artist', + '(c) ' => 'copyright', + 'FILE' => 'filename', + 'COMT' => 'comment', + 'ALBM' => 'album' + ); + return (isset($VQFcommentNiceNameLookup[$shortname]) ? $VQFcommentNiceNameLookup[$shortname] : $shortname); + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.audio.wavpack.php b/modules/id3/getid3/module.audio.wavpack.php new file mode 100644 index 00000000..0435f08d --- /dev/null +++ b/modules/id3/getid3/module.audio.wavpack.php @@ -0,0 +1,372 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.audio.wavpack.php // +// module for analyzing WavPack v4.0+ Audio files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_wavpack +{ + + function getid3_wavpack(&$fd, &$ThisFileInfo) { + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + + while (true) { + + $wavpackheader = fread($fd, 32); + + if (ftell($fd) >= $ThisFileInfo['avdataend']) { + break; + } elseif (feof($fd)) { + break; + } elseif ( + (@$ThisFileInfo['wavpack']['blockheader']['total_samples'] > 0) && + (@$ThisFileInfo['wavpack']['blockheader']['block_samples'] > 0) && + (!isset($ThisFileInfo['wavpack']['riff_trailer_size']) || ($ThisFileInfo['wavpack']['riff_trailer_size'] <= 0)) && + ((@$ThisFileInfo['wavpack']['config_flags']['md5_checksum'] === false) || !empty($ThisFileInfo['md5_data_source']))) { + break; + } + + $blockheader_offset = ftell($fd) - 32; + $blockheader_magic = substr($wavpackheader, 0, 4); + $blockheader_size = getid3_lib::LittleEndian2Int(substr($wavpackheader, 4, 4)); + + if ($blockheader_magic != 'wvpk') { + $ThisFileInfo['error'][] = 'Expecting "wvpk" at offset '.$blockheader_offset.', found "'.$blockheader_magic.'"'; + if ((@$ThisFileInfo['audio']['dataformat'] != 'wavpack') && (@$ThisFileInfo['audio']['dataformat'] != 'wvc')) { + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['audio']); + unset($ThisFileInfo['wavpack']); + } + return false; + } + + + if ((@$ThisFileInfo['wavpack']['blockheader']['block_samples'] <= 0) || + (@$ThisFileInfo['wavpack']['blockheader']['total_samples'] <= 0)) { + // Also, it is possible that the first block might not have + // any samples (block_samples == 0) and in this case you should skip blocks + // until you find one with samples because the other information (like + // total_samples) are not guaranteed to be correct until (block_samples > 0) + + // Finally, I have defined a format for files in which the length is not known
+ // (for example when raw files are created using pipes). In these cases
+ // total_samples will be -1 and you must seek to the final block to determine
+ // the total number of samples.
+ + + $ThisFileInfo['audio']['dataformat'] = 'wavpack'; + $ThisFileInfo['fileformat'] = 'wavpack'; + $ThisFileInfo['audio']['lossless'] = true; + $ThisFileInfo['audio']['bitrate_mode'] = 'vbr'; + + $ThisFileInfo['wavpack']['blockheader']['offset'] = $blockheader_offset; + $ThisFileInfo['wavpack']['blockheader']['magic'] = $blockheader_magic; + $ThisFileInfo['wavpack']['blockheader']['size'] = $blockheader_size; + + if ($ThisFileInfo['wavpack']['blockheader']['size'] >= 0x100000) { + $ThisFileInfo['error'][] = 'Expecting WavPack block size less than "0x100000", found "'.$ThisFileInfo['wavpack']['blockheader']['size'].'" at offset '.$ThisFileInfo['wavpack']['blockheader']['offset']; + if ((@$ThisFileInfo['audio']['dataformat'] != 'wavpack') && (@$ThisFileInfo['audio']['dataformat'] != 'wvc')) { + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['audio']); + unset($ThisFileInfo['wavpack']); + } + return false; + } + + $ThisFileInfo['wavpack']['blockheader']['minor_version'] = ord($wavpackheader{8}); + $ThisFileInfo['wavpack']['blockheader']['major_version'] = ord($wavpackheader{9}); + + if (($ThisFileInfo['wavpack']['blockheader']['major_version'] != 4) || + (($ThisFileInfo['wavpack']['blockheader']['minor_version'] < 4) && + ($ThisFileInfo['wavpack']['blockheader']['minor_version'] > 16))) { + $ThisFileInfo['error'][] = 'Expecting WavPack version between "4.2" and "4.16", found version "'.$ThisFileInfo['wavpack']['blockheader']['major_version'].'.'.$ThisFileInfo['wavpack']['blockheader']['minor_version'].'" at offset '.$ThisFileInfo['wavpack']['blockheader']['offset']; + if ((@$ThisFileInfo['audio']['dataformat'] != 'wavpack') && (@$ThisFileInfo['audio']['dataformat'] != 'wvc')) { + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['audio']); + unset($ThisFileInfo['wavpack']); + } + return false; + } + + $ThisFileInfo['wavpack']['blockheader']['track_number'] = ord($wavpackheader{10}); // unused + $ThisFileInfo['wavpack']['blockheader']['index_number'] = ord($wavpackheader{11}); // unused + $ThisFileInfo['wavpack']['blockheader']['total_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 12, 4)); + $ThisFileInfo['wavpack']['blockheader']['block_index'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 16, 4)); + $ThisFileInfo['wavpack']['blockheader']['block_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 20, 4)); + $ThisFileInfo['wavpack']['blockheader']['flags_raw'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 24, 4)); + $ThisFileInfo['wavpack']['blockheader']['crc'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 28, 4)); + + $ThisFileInfo['wavpack']['blockheader']['flags']['bytes_per_sample'] = 1 + ($ThisFileInfo['wavpack']['blockheader']['flags_raw'] & 0x00000003); + $ThisFileInfo['wavpack']['blockheader']['flags']['mono'] = (bool) ($ThisFileInfo['wavpack']['blockheader']['flags_raw'] & 0x00000004); + $ThisFileInfo['wavpack']['blockheader']['flags']['hybrid'] = (bool) ($ThisFileInfo['wavpack']['blockheader']['flags_raw'] & 0x00000008); + $ThisFileInfo['wavpack']['blockheader']['flags']['joint_stereo'] = (bool) ($ThisFileInfo['wavpack']['blockheader']['flags_raw'] & 0x00000010); + $ThisFileInfo['wavpack']['blockheader']['flags']['cross_decorrelation'] = (bool) ($ThisFileInfo['wavpack']['blockheader']['flags_raw'] & 0x00000020); + $ThisFileInfo['wavpack']['blockheader']['flags']['hybrid_noiseshape'] = (bool) ($ThisFileInfo['wavpack']['blockheader']['flags_raw'] & 0x00000040); + $ThisFileInfo['wavpack']['blockheader']['flags']['ieee_32bit_float'] = (bool) ($ThisFileInfo['wavpack']['blockheader']['flags_raw'] & 0x00000080); + $ThisFileInfo['wavpack']['blockheader']['flags']['int_32bit'] = (bool) ($ThisFileInfo['wavpack']['blockheader']['flags_raw'] & 0x00000100); + $ThisFileInfo['wavpack']['blockheader']['flags']['hybrid_bitrate_noise'] = (bool) ($ThisFileInfo['wavpack']['blockheader']['flags_raw'] & 0x00000200); + $ThisFileInfo['wavpack']['blockheader']['flags']['hybrid_balance_noise'] = (bool) ($ThisFileInfo['wavpack']['blockheader']['flags_raw'] & 0x00000400); + $ThisFileInfo['wavpack']['blockheader']['flags']['multichannel_initial'] = (bool) ($ThisFileInfo['wavpack']['blockheader']['flags_raw'] & 0x00000800); + $ThisFileInfo['wavpack']['blockheader']['flags']['multichannel_final'] = (bool) ($ThisFileInfo['wavpack']['blockheader']['flags_raw'] & 0x00001000); + + $ThisFileInfo['audio']['lossless'] = !$ThisFileInfo['wavpack']['blockheader']['flags']['hybrid']; + } + + while (!feof($fd) && (ftell($fd) < ($blockheader_offset + $blockheader_size + 8))) { + + $metablock = array('offset'=>ftell($fd)); + $metablockheader = fread($fd, 2); + if (feof($fd)) { + break; + } + $metablock['id'] = ord($metablockheader{0}); + $metablock['function_id'] = ($metablock['id'] & 0x3F); + $metablock['function_name'] = $this->WavPackMetablockNameLookup($metablock['function_id']); + + // The 0x20 bit in the id of the meta subblocks (which is defined as + // ID_OPTIONAL_DATA) is a permanent part of the id. The idea is that + // if a decoder encounters an id that it does not know about, it uses + // that "ID_OPTIONAL_DATA" flag to determine what to do. If it is set + // then the decoder simply ignores the metadata, but if it is zero + // then the decoder should quit because it means that an understanding + // of the metadata is required to correctly decode the audio. + $metablock['non_decoder'] = (bool) ($metablock['id'] & 0x20); + + $metablock['padded_data'] = (bool) ($metablock['id'] & 0x40); + $metablock['large_block'] = (bool) ($metablock['id'] & 0x80); + if ($metablock['large_block']) { + $metablockheader .= fread($fd, 2); + } + $metablock['size'] = getid3_lib::LittleEndian2Int(substr($metablockheader, 1)) * 2; // size is stored in words + $metablock['data'] = null; + + if ($metablock['size'] > 0) { + + switch ($metablock['function_id']) { + case 0x21: // ID_RIFF_HEADER + case 0x22: // ID_RIFF_TRAILER + case 0x23: // ID_REPLAY_GAIN + case 0x24: // ID_CUESHEET + case 0x25: // ID_CONFIG_BLOCK + case 0x26: // ID_MD5_CHECKSUM + $metablock['data'] = fread($fd, $metablock['size']); + + if ($metablock['padded_data']) { + // padded to the nearest even byte + $metablock['size']--; + $metablock['data'] = substr($metablock['data'], 0, -1); + } + break; + + case 0x00: // ID_DUMMY + case 0x01: // ID_ENCODER_INFO + case 0x02: // ID_DECORR_TERMS + case 0x03: // ID_DECORR_WEIGHTS + case 0x04: // ID_DECORR_SAMPLES + case 0x05: // ID_ENTROPY_VARS + case 0x06: // ID_HYBRID_PROFILE + case 0x07: // ID_SHAPING_WEIGHTS + case 0x08: // ID_FLOAT_INFO + case 0x09: // ID_INT32_INFO + case 0x0A: // ID_WV_BITSTREAM + case 0x0B: // ID_WVC_BITSTREAM + case 0x0C: // ID_WVX_BITSTREAM + case 0x0D: // ID_CHANNEL_INFO + fseek($fd, $metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size'], SEEK_SET); + break; + + default: + $ThisFileInfo['warning'][] = 'Unexpected metablock type "0x'.str_pad(dechex($metablock['function_id']), 2, '0', STR_PAD_LEFT).'" at offset '.$metablock['offset']; + fseek($fd, $metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size'], SEEK_SET); + break; + } + + switch ($metablock['function_id']) { + case 0x21: // ID_RIFF_HEADER + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + $original_wav_filesize = getid3_lib::LittleEndian2Int(substr($metablock['data'], 4, 4)); + getid3_riff::ParseRIFFdata($metablock['data'], $ParsedRIFFheader); + $metablock['riff'] = $ParsedRIFFheader['riff']; + $metablock['riff']['original_filesize'] = $original_wav_filesize; + $ThisFileInfo['wavpack']['riff_trailer_size'] = $original_wav_filesize - $metablock['riff']['WAVE']['data'][0]['size'] - $metablock['riff']['header_size']; + + $ThisFileInfo['audio']['sample_rate'] = $ParsedRIFFheader['riff']['raw']['fmt ']['nSamplesPerSec']; + $ThisFileInfo['playtime_seconds'] = $ThisFileInfo['wavpack']['blockheader']['total_samples'] / $ThisFileInfo['audio']['sample_rate']; + + // Safe RIFF header in case there's a RIFF footer later + $metablockRIFFheader = $metablock['data']; + break; + + + case 0x22: // ID_RIFF_TRAILER + $metablockRIFFfooter = $metablockRIFFheader.$metablock['data']; + getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); + + $ftell_old = ftell($fd); + $startoffset = $metablock['offset'] + ($metablock['large_block'] ? 4 : 2); + $ParsedRIFFfooter = array('avdataend'=>$ThisFileInfo['avdataend'], 'fileformat'=>'riff', 'error'=>array(), 'warning'=>array()); + $metablock['riff'] = getid3_riff::ParseRIFF($fd, $startoffset, $startoffset + $metablock['size'], $ParsedRIFFfooter); + fseek($fd, $ftell_old, SEEK_SET); + + if (!empty($metablock['riff']['INFO'])) { + getid3_riff::RIFFcommentsParse($metablock['riff']['INFO'], $metablock['comments']); + $ThisFileInfo['tags']['riff'] = $metablock['comments']; + } + break; + + + case 0x23: // ID_REPLAY_GAIN + $ThisFileInfo['warning'][] = 'WavPack "Replay Gain" contents not yet handled by getID3() in metablock at offset '.$metablock['offset']; + break; + + + case 0x24: // ID_CUESHEET + $ThisFileInfo['warning'][] = 'WavPack "Cuesheet" contents not yet handled by getID3() in metablock at offset '.$metablock['offset']; + break; + + + case 0x25: // ID_CONFIG_BLOCK + $metablock['flags_raw'] = getid3_lib::LittleEndian2Int(substr($metablock['data'], 0, 3)); + + $metablock['flags']['adobe_mode'] = (bool) ($metablock['flags_raw'] & 0x000001); // "adobe" mode for 32-bit floats + $metablock['flags']['fast_flag'] = (bool) ($metablock['flags_raw'] & 0x000002); // fast mode + $metablock['flags']['very_fast_flag'] = (bool) ($metablock['flags_raw'] & 0x000004); // double fast + $metablock['flags']['high_flag'] = (bool) ($metablock['flags_raw'] & 0x000008); // high quality mode + $metablock['flags']['very_high_flag'] = (bool) ($metablock['flags_raw'] & 0x000010); // double high (not used yet) + $metablock['flags']['bitrate_kbps'] = (bool) ($metablock['flags_raw'] & 0x000020); // bitrate is kbps, not bits / sample + $metablock['flags']['auto_shaping'] = (bool) ($metablock['flags_raw'] & 0x000040); // automatic noise shaping + $metablock['flags']['shape_override'] = (bool) ($metablock['flags_raw'] & 0x000080); // shaping mode specified + $metablock['flags']['joint_override'] = (bool) ($metablock['flags_raw'] & 0x000100); // joint-stereo mode specified + $metablock['flags']['copy_time'] = (bool) ($metablock['flags_raw'] & 0x000200); // copy file-time from source + $metablock['flags']['create_exe'] = (bool) ($metablock['flags_raw'] & 0x000400); // create executable + $metablock['flags']['create_wvc'] = (bool) ($metablock['flags_raw'] & 0x000800); // create correction file + $metablock['flags']['optimize_wvc'] = (bool) ($metablock['flags_raw'] & 0x001000); // maximize bybrid compression + $metablock['flags']['quality_mode'] = (bool) ($metablock['flags_raw'] & 0x002000); // psychoacoustic quality mode + $metablock['flags']['raw_flag'] = (bool) ($metablock['flags_raw'] & 0x004000); // raw mode (not implemented yet) + $metablock['flags']['calc_noise'] = (bool) ($metablock['flags_raw'] & 0x008000); // calc noise in hybrid mode + $metablock['flags']['lossy_mode'] = (bool) ($metablock['flags_raw'] & 0x010000); // obsolete (for information) + $metablock['flags']['extra_mode'] = (bool) ($metablock['flags_raw'] & 0x020000); // extra processing mode + $metablock['flags']['skip_wvx'] = (bool) ($metablock['flags_raw'] & 0x040000); // no wvx stream w/ floats & big ints + $metablock['flags']['md5_checksum'] = (bool) ($metablock['flags_raw'] & 0x080000); // compute & store MD5 signature + $metablock['flags']['quiet_mode'] = (bool) ($metablock['flags_raw'] & 0x100000); // don't report progress % + + $ThisFileInfo['wavpack']['config_flags'] = $metablock['flags']; + + + if ($ThisFileInfo['wavpack']['blockheader']['flags']['hybrid']) { + @$ThisFileInfo['audio']['encoder_options'] .= ' -b???'; + } + @$ThisFileInfo['audio']['encoder_options'] .= ($metablock['flags']['adobe_mode'] ? ' -a' : ''); + @$ThisFileInfo['audio']['encoder_options'] .= ($metablock['flags']['optimize_wvc'] ? ' -cc' : ''); + @$ThisFileInfo['audio']['encoder_options'] .= ($metablock['flags']['create_exe'] ? ' -e' : ''); + @$ThisFileInfo['audio']['encoder_options'] .= ($metablock['flags']['fast_flag'] ? ' -f' : ''); + @$ThisFileInfo['audio']['encoder_options'] .= ($metablock['flags']['joint_override'] ? ' -j?' : ''); + @$ThisFileInfo['audio']['encoder_options'] .= ($metablock['flags']['high_flag'] ? ' -h' : ''); + @$ThisFileInfo['audio']['encoder_options'] .= ($metablock['flags']['md5_checksum'] ? ' -m' : ''); + @$ThisFileInfo['audio']['encoder_options'] .= ($metablock['flags']['calc_noise'] ? ' -n' : ''); + @$ThisFileInfo['audio']['encoder_options'] .= ($metablock['flags']['shape_override'] ? ' -s?' : ''); + @$ThisFileInfo['audio']['encoder_options'] .= ($metablock['flags']['extra_mode'] ? ' -x?' : ''); + if (@$ThisFileInfo['audio']['encoder_options']) { + $ThisFileInfo['audio']['encoder_options'] = trim(@$ThisFileInfo['audio']['encoder_options']); + } + elseif (isset($ThisFileInfo['audio']['encoder_options'])) { + unset($ThisFileInfo['audio']['encoder_options']); + } + break; + + + case 0x26: // ID_MD5_CHECKSUM + if (strlen($metablock['data']) == 16) { + $ThisFileInfo['md5_data_source'] = strtolower(getid3_lib::PrintHexBytes($metablock['data'], true, false, false)); + } else { + $ThisFileInfo['warning'][] = 'Expecting 16 bytes of WavPack "MD5 Checksum" in metablock at offset '.$metablock['offset'].', but found '.strlen($metablock['data']).' bytes'; + } + break; + + + case 0x00: // ID_DUMMY + case 0x01: // ID_ENCODER_INFO + case 0x02: // ID_DECORR_TERMS + case 0x03: // ID_DECORR_WEIGHTS + case 0x04: // ID_DECORR_SAMPLES + case 0x05: // ID_ENTROPY_VARS + case 0x06: // ID_HYBRID_PROFILE + case 0x07: // ID_SHAPING_WEIGHTS + case 0x08: // ID_FLOAT_INFO + case 0x09: // ID_INT32_INFO + case 0x0A: // ID_WV_BITSTREAM + case 0x0B: // ID_WVC_BITSTREAM + case 0x0C: // ID_WVX_BITSTREAM + case 0x0D: // ID_CHANNEL_INFO + unset($metablock); + break; + } + + } + if (!empty($metablock)) { + $ThisFileInfo['wavpack']['metablocks'][] = $metablock; + } + + } + + } + + $ThisFileInfo['audio']['encoder'] = 'WavPack v'.$ThisFileInfo['wavpack']['blockheader']['major_version'].'.'.str_pad($ThisFileInfo['wavpack']['blockheader']['minor_version'], 2, '0', STR_PAD_LEFT); + $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['wavpack']['blockheader']['flags']['bytes_per_sample'] * 8; + $ThisFileInfo['audio']['channels'] = ($ThisFileInfo['wavpack']['blockheader']['flags']['mono'] ? 1 : 2); + + if (@$ThisFileInfo['playtime_seconds']) { + + $ThisFileInfo['audio']['bitrate'] = (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8) / $ThisFileInfo['playtime_seconds']; + + } else { + + $ThisFileInfo['audio']['dataformat'] = 'wvc'; + + } + + return true; + } + + + function WavPackMetablockNameLookup(&$id) { + static $WavPackMetablockNameLookup = array( + 0x00 => 'Dummy', + 0x01 => 'Encoder Info', + 0x02 => 'Decorrelation Terms', + 0x03 => 'Decorrelation Weights', + 0x04 => 'Decorrelation Samples', + 0x05 => 'Entropy Variables', + 0x06 => 'Hybrid Profile', + 0x07 => 'Shaping Weights', + 0x08 => 'Float Info', + 0x09 => 'Int32 Info', + 0x0A => 'WV Bitstream', + 0x0B => 'WVC Bitstream', + 0x0C => 'WVX Bitstream', + 0x0D => 'Channel Info', + 0x21 => 'RIFF header', + 0x22 => 'RIFF trailer', + 0x23 => 'Replay Gain', + 0x24 => 'Cuesheet', + 0x25 => 'Config Block', + 0x26 => 'MD5 Checksum', + ); + return (@$WavPackMetablockNameLookup[$id]); + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.graphic.bmp.php b/modules/id3/getid3/module.graphic.bmp.php new file mode 100644 index 00000000..dc6d733c --- /dev/null +++ b/modules/id3/getid3/module.graphic.bmp.php @@ -0,0 +1,683 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.graphic.bmp.php // +// module for analyzing BMP Image files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_bmp +{ + + function getid3_bmp(&$fd, &$ThisFileInfo, $ExtractPalette=false, $ExtractData=false) { + + // shortcuts + $ThisFileInfo['bmp']['header']['raw'] = array(); + $thisfile_bmp = &$ThisFileInfo['bmp']; + $thisfile_bmp_header = &$thisfile_bmp['header']; + $thisfile_bmp_header_raw = &$thisfile_bmp_header['raw']; + + // BITMAPFILEHEADER [14 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_62uq.asp + // all versions + // WORD bfType; + // DWORD bfSize; + // WORD bfReserved1; + // WORD bfReserved2; + // DWORD bfOffBits; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $offset = 0; + $BMPheader = fread($fd, 14 + 40); + + $thisfile_bmp_header_raw['identifier'] = substr($BMPheader, $offset, 2); + $offset += 2; + + if ($thisfile_bmp_header_raw['identifier'] != 'BM') { + $ThisFileInfo['error'][] = 'Expecting "BM" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$thisfile_bmp_header_raw['identifier'].'"'; + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['bmp']); + return false; + } + + $thisfile_bmp_header_raw['filesize'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['reserved1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); + $offset += 2; + $thisfile_bmp_header_raw['reserved2'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); + $offset += 2; + $thisfile_bmp_header_raw['data_offset'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['header_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + + + // check if the hardcoded-to-1 "planes" is at offset 22 or 26 + $planes22 = getid3_lib::LittleEndian2Int(substr($BMPheader, 22, 2)); + $planes26 = getid3_lib::LittleEndian2Int(substr($BMPheader, 26, 2)); + if (($planes22 == 1) && ($planes26 != 1)) { + $thisfile_bmp['type_os'] = 'OS/2'; + $thisfile_bmp['type_version'] = 1; + } elseif (($planes26 == 1) && ($planes22 != 1)) { + $thisfile_bmp['type_os'] = 'Windows'; + $thisfile_bmp['type_version'] = 1; + } elseif ($thisfile_bmp_header_raw['header_size'] == 12) { + $thisfile_bmp['type_os'] = 'OS/2'; + $thisfile_bmp['type_version'] = 1; + } elseif ($thisfile_bmp_header_raw['header_size'] == 40) { + $thisfile_bmp['type_os'] = 'Windows'; + $thisfile_bmp['type_version'] = 1; + } elseif ($thisfile_bmp_header_raw['header_size'] == 84) { + $thisfile_bmp['type_os'] = 'Windows'; + $thisfile_bmp['type_version'] = 4; + } elseif ($thisfile_bmp_header_raw['header_size'] == 100) { + $thisfile_bmp['type_os'] = 'Windows'; + $thisfile_bmp['type_version'] = 5; + } else { + $ThisFileInfo['error'][] = 'Unknown BMP subtype (or not a BMP file)'; + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['bmp']); + return false; + } + + $ThisFileInfo['fileformat'] = 'bmp'; + $ThisFileInfo['video']['dataformat'] = 'bmp'; + $ThisFileInfo['video']['lossless'] = true; + $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1; + + if ($thisfile_bmp['type_os'] == 'OS/2') { + + // OS/2-format BMP + // http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm + + // DWORD Size; /* Size of this structure in bytes */ + // DWORD Width; /* Bitmap width in pixels */ + // DWORD Height; /* Bitmap height in pixel */ + // WORD NumPlanes; /* Number of bit planes (color depth) */ + // WORD BitsPerPixel; /* Number of bits per pixel per plane */ + + $thisfile_bmp_header_raw['width'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); + $offset += 2; + $thisfile_bmp_header_raw['height'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); + $offset += 2; + $thisfile_bmp_header_raw['planes'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); + $offset += 2; + $thisfile_bmp_header_raw['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); + $offset += 2; + + $ThisFileInfo['video']['resolution_x'] = $thisfile_bmp_header_raw['width']; + $ThisFileInfo['video']['resolution_y'] = $thisfile_bmp_header_raw['height']; + $ThisFileInfo['video']['codec'] = 'BI_RGB '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit'; + $ThisFileInfo['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel']; + + if ($thisfile_bmp['type_version'] >= 2) { + // DWORD Compression; /* Bitmap compression scheme */ + // DWORD ImageDataSize; /* Size of bitmap data in bytes */ + // DWORD XResolution; /* X resolution of display device */ + // DWORD YResolution; /* Y resolution of display device */ + // DWORD ColorsUsed; /* Number of color table indices used */ + // DWORD ColorsImportant; /* Number of important color indices */ + // WORD Units; /* Type of units used to measure resolution */ + // WORD Reserved; /* Pad structure to 4-byte boundary */ + // WORD Recording; /* Recording algorithm */ + // WORD Rendering; /* Halftoning algorithm used */ + // DWORD Size1; /* Reserved for halftoning algorithm use */ + // DWORD Size2; /* Reserved for halftoning algorithm use */ + // DWORD ColorEncoding; /* Color model used in bitmap */ + // DWORD Identifier; /* Reserved for application use */ + + $thisfile_bmp_header_raw['compression'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['bmp_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['resolution_h'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['resolution_v'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['colors_used'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['colors_important'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['resolution_units'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); + $offset += 2; + $thisfile_bmp_header_raw['reserved1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); + $offset += 2; + $thisfile_bmp_header_raw['recording'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); + $offset += 2; + $thisfile_bmp_header_raw['rendering'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); + $offset += 2; + $thisfile_bmp_header_raw['size1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['size2'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['color_encoding'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['identifier'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + + $thisfile_bmp_header['compression'] = $this->BMPcompressionOS2Lookup($thisfile_bmp_header_raw['compression']); + + $ThisFileInfo['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit'; + } + + } elseif ($thisfile_bmp['type_os'] == 'Windows') { + + // Windows-format BMP + + // BITMAPINFOHEADER - [40 bytes] http://msdn.microsoft.com/library/en-us/gdi/bitmaps_1rw2.asp + // all versions + // DWORD biSize; + // LONG biWidth; + // LONG biHeight; + // WORD biPlanes; + // WORD biBitCount; + // DWORD biCompression; + // DWORD biSizeImage; + // LONG biXPelsPerMeter; + // LONG biYPelsPerMeter; + // DWORD biClrUsed; + // DWORD biClrImportant; + + $thisfile_bmp_header_raw['width'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true); + $offset += 4; + $thisfile_bmp_header_raw['height'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true); + $offset += 4; + $thisfile_bmp_header_raw['planes'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); + $offset += 2; + $thisfile_bmp_header_raw['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2)); + $offset += 2; + $thisfile_bmp_header_raw['compression'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['bmp_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['resolution_h'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true); + $offset += 4; + $thisfile_bmp_header_raw['resolution_v'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true); + $offset += 4; + $thisfile_bmp_header_raw['colors_used'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['colors_important'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + + $thisfile_bmp_header['compression'] = $this->BMPcompressionWindowsLookup($thisfile_bmp_header_raw['compression']); + $ThisFileInfo['video']['resolution_x'] = $thisfile_bmp_header_raw['width']; + $ThisFileInfo['video']['resolution_y'] = $thisfile_bmp_header_raw['height']; + $ThisFileInfo['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit'; + $ThisFileInfo['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel']; + + if (($thisfile_bmp['type_version'] >= 4) || ($thisfile_bmp_header_raw['compression'] == 3)) { + // should only be v4+, but BMPs with type_version==1 and BI_BITFIELDS compression have been seen + $BMPheader .= fread($fd, 44); + + // BITMAPV4HEADER - [44 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_2k1e.asp + // Win95+, WinNT4.0+ + // DWORD bV4RedMask; + // DWORD bV4GreenMask; + // DWORD bV4BlueMask; + // DWORD bV4AlphaMask; + // DWORD bV4CSType; + // CIEXYZTRIPLE bV4Endpoints; + // DWORD bV4GammaRed; + // DWORD bV4GammaGreen; + // DWORD bV4GammaBlue; + $thisfile_bmp_header_raw['red_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['green_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['blue_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['alpha_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['cs_type'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['ciexyz_red'] = substr($BMPheader, $offset, 4); + $offset += 4; + $thisfile_bmp_header_raw['ciexyz_green'] = substr($BMPheader, $offset, 4); + $offset += 4; + $thisfile_bmp_header_raw['ciexyz_blue'] = substr($BMPheader, $offset, 4); + $offset += 4; + $thisfile_bmp_header_raw['gamma_red'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['gamma_green'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['gamma_blue'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + + $thisfile_bmp_header['ciexyz_red'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_red'])); + $thisfile_bmp_header['ciexyz_green'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_green'])); + $thisfile_bmp_header['ciexyz_blue'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_blue'])); + } + + if ($thisfile_bmp['type_version'] >= 5) { + $BMPheader .= fread($fd, 16); + + // BITMAPV5HEADER - [16 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_7c36.asp + // Win98+, Win2000+ + // DWORD bV5Intent; + // DWORD bV5ProfileData; + // DWORD bV5ProfileSize; + // DWORD bV5Reserved; + $thisfile_bmp_header_raw['intent'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['profile_data_offset'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['profile_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + $thisfile_bmp_header_raw['reserved3'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4)); + $offset += 4; + } + + } else { + + $ThisFileInfo['error'][] = 'Unknown BMP format in header.'; + return false; + + } + + + if ($ExtractPalette || $ExtractData) { + $PaletteEntries = 0; + if ($thisfile_bmp_header_raw['bits_per_pixel'] < 16) { + $PaletteEntries = pow(2, $thisfile_bmp_header_raw['bits_per_pixel']); + } elseif (isset($thisfile_bmp_header_raw['colors_used']) && ($thisfile_bmp_header_raw['colors_used'] > 0) && ($thisfile_bmp_header_raw['colors_used'] <= 256)) { + $PaletteEntries = $thisfile_bmp_header_raw['colors_used']; + } + if ($PaletteEntries > 0) { + $BMPpalette = fread($fd, 4 * $PaletteEntries); + $paletteoffset = 0; + for ($i = 0; $i < $PaletteEntries; $i++) { + // RGBQUAD - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_5f8y.asp + // BYTE rgbBlue; + // BYTE rgbGreen; + // BYTE rgbRed; + // BYTE rgbReserved; + $blue = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1)); + $green = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1)); + $red = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1)); + if (($thisfile_bmp['type_os'] == 'OS/2') && ($thisfile_bmp['type_version'] == 1)) { + // no padding byte + } else { + $paletteoffset++; // padding byte + } + $thisfile_bmp['palette'][$i] = (($red << 16) | ($green << 8) | $blue); + } + } + } + + if ($ExtractData) { + fseek($fd, $thisfile_bmp_header_raw['data_offset'], SEEK_SET); + $RowByteLength = ceil(($thisfile_bmp_header_raw['width'] * ($thisfile_bmp_header_raw['bits_per_pixel'] / 8)) / 4) * 4; // round up to nearest DWORD boundry + $BMPpixelData = fread($fd, $thisfile_bmp_header_raw['height'] * $RowByteLength); + $pixeldataoffset = 0; + switch (@$thisfile_bmp_header_raw['compression']) { + + case 0: // BI_RGB + switch ($thisfile_bmp_header_raw['bits_per_pixel']) { + case 1: + for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) { + for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) { + $paletteindexbyte = ord($BMPpixelData{$pixeldataoffset++}); + for ($i = 7; $i >= 0; $i--) { + $paletteindex = ($paletteindexbyte & (0x01 << $i)) >> $i; + $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex]; + $col++; + } + } + while (($pixeldataoffset % 4) != 0) { + // lines are padded to nearest DWORD + $pixeldataoffset++; + } + } + break; + + case 4: + for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) { + for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) { + $paletteindexbyte = ord($BMPpixelData{$pixeldataoffset++}); + for ($i = 1; $i >= 0; $i--) { + $paletteindex = ($paletteindexbyte & (0x0F << (4 * $i))) >> (4 * $i); + $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex]; + $col++; + } + } + while (($pixeldataoffset % 4) != 0) { + // lines are padded to nearest DWORD + $pixeldataoffset++; + } + } + break; + + case 8: + for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) { + for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) { + $paletteindex = ord($BMPpixelData{$pixeldataoffset++}); + $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex]; + } + while (($pixeldataoffset % 4) != 0) { + // lines are padded to nearest DWORD + $pixeldataoffset++; + } + } + break; + + case 24: + for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) { + for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) { + $thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData{$pixeldataoffset+2}) << 16) | (ord($BMPpixelData{$pixeldataoffset+1}) << 8) | ord($BMPpixelData{$pixeldataoffset});
+ $pixeldataoffset += 3;
+ } + while (($pixeldataoffset % 4) != 0) { + // lines are padded to nearest DWORD + $pixeldataoffset++; + } + } + break; + + case 32: + for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) { + for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) { + $thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData{$pixeldataoffset+3}) << 24) | (ord($BMPpixelData{$pixeldataoffset+2}) << 16) | (ord($BMPpixelData{$pixeldataoffset+1}) << 8) | ord($BMPpixelData{$pixeldataoffset});
+ $pixeldataoffset += 4;
+ } + while (($pixeldataoffset % 4) != 0) { + // lines are padded to nearest DWORD + $pixeldataoffset++; + } + } + break; + + case 16: + // ? + break; + + default: + $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; + break; + } + break; + + + case 1: // BI_RLE8 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp + switch ($thisfile_bmp_header_raw['bits_per_pixel']) { + case 8: + $pixelcounter = 0; + while ($pixeldataoffset < strlen($BMPpixelData)) { + $firstbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1)); + $secondbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1)); + if ($firstbyte == 0) { + + // escaped/absolute mode - the first byte of the pair can be set to zero to + // indicate an escape character that denotes the end of a line, the end of + // a bitmap, or a delta, depending on the value of the second byte. + switch ($secondbyte) { + case 0: + // end of line + // no need for special processing, just ignore + break; + + case 1: + // end of bitmap + $pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case + break; + + case 2: + // delta - The 2 bytes following the escape contain unsigned values + // indicating the horizontal and vertical offsets of the next pixel + // from the current position. + $colincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1)); + $rowincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1)); + $col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement; + $row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement; + $pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col; + break; + + default: + // In absolute mode, the first byte is zero and the second byte is a + // value in the range 03H through FFH. The second byte represents the + // number of bytes that follow, each of which contains the color index + // of a single pixel. Each run must be aligned on a word boundary. + for ($i = 0; $i < $secondbyte; $i++) { + $paletteindex = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1)); + $col = $pixelcounter % $thisfile_bmp_header_raw['width']; + $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']); + $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex]; + $pixelcounter++; + } + while (($pixeldataoffset % 2) != 0) { + // Each run must be aligned on a word boundary. + $pixeldataoffset++; + } + break; + } + + } else { + + // encoded mode - the first byte specifies the number of consecutive pixels + // to be drawn using the color index contained in the second byte. + for ($i = 0; $i < $firstbyte; $i++) { + $col = $pixelcounter % $thisfile_bmp_header_raw['width']; + $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']); + $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$secondbyte]; + $pixelcounter++; + } + + } + } + break; + + default: + $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; + break; + } + break; + + + + case 2: // BI_RLE4 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp + switch ($thisfile_bmp_header_raw['bits_per_pixel']) { + case 4: + $pixelcounter = 0; + while ($pixeldataoffset < strlen($BMPpixelData)) { + $firstbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1)); + $secondbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1)); + if ($firstbyte == 0) { + + // escaped/absolute mode - the first byte of the pair can be set to zero to + // indicate an escape character that denotes the end of a line, the end of + // a bitmap, or a delta, depending on the value of the second byte. + switch ($secondbyte) { + case 0: + // end of line + // no need for special processing, just ignore + break; + + case 1: + // end of bitmap + $pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case + break; + + case 2: + // delta - The 2 bytes following the escape contain unsigned values + // indicating the horizontal and vertical offsets of the next pixel + // from the current position. + $colincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1)); + $rowincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1)); + $col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement; + $row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement; + $pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col; + break; + + default: + // In absolute mode, the first byte is zero. The second byte contains the number + // of color indexes that follow. Subsequent bytes contain color indexes in their + // high- and low-order 4 bits, one color index for each pixel. In absolute mode, + // each run must be aligned on a word boundary. + unset($paletteindexes); + for ($i = 0; $i < ceil($secondbyte / 2); $i++) { + $paletteindexbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1)); + $paletteindexes[] = ($paletteindexbyte & 0xF0) >> 4; + $paletteindexes[] = ($paletteindexbyte & 0x0F); + } + while (($pixeldataoffset % 2) != 0) { + // Each run must be aligned on a word boundary. + $pixeldataoffset++; + } + + foreach ($paletteindexes as $paletteindex) { + $col = $pixelcounter % $thisfile_bmp_header_raw['width']; + $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']); + $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex]; + $pixelcounter++; + } + break; + } + + } else { + + // encoded mode - the first byte of the pair contains the number of pixels to be + // drawn using the color indexes in the second byte. The second byte contains two + // color indexes, one in its high-order 4 bits and one in its low-order 4 bits. + // The first of the pixels is drawn using the color specified by the high-order + // 4 bits, the second is drawn using the color in the low-order 4 bits, the third + // is drawn using the color in the high-order 4 bits, and so on, until all the + // pixels specified by the first byte have been drawn. + $paletteindexes[0] = ($secondbyte & 0xF0) >> 4; + $paletteindexes[1] = ($secondbyte & 0x0F); + for ($i = 0; $i < $firstbyte; $i++) { + $col = $pixelcounter % $thisfile_bmp_header_raw['width']; + $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']); + $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindexes[($i % 2)]]; + $pixelcounter++; + } + + } + } + break; + + default: + $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; + break; + } + break; + + + case 3: // BI_BITFIELDS + switch ($thisfile_bmp_header_raw['bits_per_pixel']) { + case 16: + case 32: + $redshift = 0; + $greenshift = 0; + $blueshift = 0; + while ((($thisfile_bmp_header_raw['red_mask'] >> $redshift) & 0x01) == 0) { + $redshift++; + } + while ((($thisfile_bmp_header_raw['green_mask'] >> $greenshift) & 0x01) == 0) { + $greenshift++; + } + while ((($thisfile_bmp_header_raw['blue_mask'] >> $blueshift) & 0x01) == 0) { + $blueshift++; + } + for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) { + for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) { + $pixelvalue = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset, $thisfile_bmp_header_raw['bits_per_pixel'] / 8)); + $pixeldataoffset += $thisfile_bmp_header_raw['bits_per_pixel'] / 8; + + $red = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['red_mask']) >> $redshift) / ($thisfile_bmp_header_raw['red_mask'] >> $redshift)) * 255)); + $green = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['green_mask']) >> $greenshift) / ($thisfile_bmp_header_raw['green_mask'] >> $greenshift)) * 255)); + $blue = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['blue_mask']) >> $blueshift) / ($thisfile_bmp_header_raw['blue_mask'] >> $blueshift)) * 255)); + $thisfile_bmp['data'][$row][$col] = (($red << 16) | ($green << 8) | ($blue)); + } + while (($pixeldataoffset % 4) != 0) { + // lines are padded to nearest DWORD + $pixeldataoffset++; + } + } + break; + + default: + $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data'; + break; + } + break; + + + default: // unhandled compression type + $ThisFileInfo['error'][] = 'Unknown/unhandled compression type value ('.$thisfile_bmp_header_raw['compression'].') - cannot decompress pixel data'; + break; + } + } + + return true; + } + + + function PlotBMP(&$BMPinfo) { + $starttime = time(); + if (!isset($BMPinfo['bmp']['data']) || !is_array($BMPinfo['bmp']['data'])) { + echo 'ERROR: no pixel data<BR>'; + return false; + } + set_time_limit(intval(round($BMPinfo['resolution_x'] * $BMPinfo['resolution_y'] / 10000))); + if ($im = ImageCreateTrueColor($BMPinfo['resolution_x'], $BMPinfo['resolution_y'])) { + for ($row = 0; $row < $BMPinfo['resolution_y']; $row++) { + for ($col = 0; $col < $BMPinfo['resolution_x']; $col++) { + if (isset($BMPinfo['bmp']['data'][$row][$col])) { + $red = ($BMPinfo['bmp']['data'][$row][$col] & 0x00FF0000) >> 16; + $green = ($BMPinfo['bmp']['data'][$row][$col] & 0x0000FF00) >> 8; + $blue = ($BMPinfo['bmp']['data'][$row][$col] & 0x000000FF); + $pixelcolor = ImageColorAllocate($im, $red, $green, $blue); + ImageSetPixel($im, $col, $row, $pixelcolor); + } else { + //echo 'ERROR: no data for pixel '.$row.' x '.$col.'<BR>'; + //return false; + } + } + } + if (headers_sent()) { + echo 'plotted '.($BMPinfo['resolution_x'] * $BMPinfo['resolution_y']).' pixels in '.(time() - $starttime).' seconds<BR>'; + ImageDestroy($im); + exit; + } else { + header('Content-type: image/png'); + ImagePNG($im); + ImageDestroy($im); + return true; + } + } + return false; + } + + function BMPcompressionWindowsLookup($compressionid) { + static $BMPcompressionWindowsLookup = array( + 0 => 'BI_RGB', + 1 => 'BI_RLE8', + 2 => 'BI_RLE4', + 3 => 'BI_BITFIELDS', + 4 => 'BI_JPEG', + 5 => 'BI_PNG' + ); + return (isset($BMPcompressionWindowsLookup[$compressionid]) ? $BMPcompressionWindowsLookup[$compressionid] : 'invalid'); + } + + function BMPcompressionOS2Lookup($compressionid) { + static $BMPcompressionOS2Lookup = array( + 0 => 'BI_RGB', + 1 => 'BI_RLE8', + 2 => 'BI_RLE4', + 3 => 'Huffman 1D', + 4 => 'BI_RLE24', + ); + return (isset($BMPcompressionOS2Lookup[$compressionid]) ? $BMPcompressionOS2Lookup[$compressionid] : 'invalid'); + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.graphic.gif.php b/modules/id3/getid3/module.graphic.gif.php new file mode 100644 index 00000000..986ada30 --- /dev/null +++ b/modules/id3/getid3/module.graphic.gif.php @@ -0,0 +1,183 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.graphic.gif.php // +// module for analyzing GIF Image files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_gif +{ + + function getid3_gif(&$fd, &$ThisFileInfo) { + $ThisFileInfo['fileformat'] = 'gif'; + $ThisFileInfo['video']['dataformat'] = 'gif'; + $ThisFileInfo['video']['lossless'] = true; + $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $GIFheader = fread($fd, 13); + $offset = 0; + + $ThisFileInfo['gif']['header']['raw']['identifier'] = substr($GIFheader, $offset, 3); + $offset += 3; + + if ($ThisFileInfo['gif']['header']['raw']['identifier'] != 'GIF') { + $ThisFileInfo['error'][] = 'Expecting "GIF" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$ThisFileInfo['gif']['header']['raw']['identifier'].'"'; + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['gif']); + return false; + } + + $ThisFileInfo['gif']['header']['raw']['version'] = substr($GIFheader, $offset, 3); + $offset += 3; + $ThisFileInfo['gif']['header']['raw']['width'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 2)); + $offset += 2; + $ThisFileInfo['gif']['header']['raw']['height'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 2)); + $offset += 2; + $ThisFileInfo['gif']['header']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1)); + $offset += 1; + $ThisFileInfo['gif']['header']['raw']['bg_color_index'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1)); + $offset += 1; + $ThisFileInfo['gif']['header']['raw']['aspect_ratio'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1)); + $offset += 1; + + $ThisFileInfo['video']['resolution_x'] = $ThisFileInfo['gif']['header']['raw']['width']; + $ThisFileInfo['video']['resolution_y'] = $ThisFileInfo['gif']['header']['raw']['height']; + $ThisFileInfo['gif']['version'] = $ThisFileInfo['gif']['header']['raw']['version']; + $ThisFileInfo['gif']['header']['flags']['global_color_table'] = (bool) ($ThisFileInfo['gif']['header']['raw']['flags'] & 0x80); + if ($ThisFileInfo['gif']['header']['raw']['flags'] & 0x80) { + // Number of bits per primary color available to the original image, minus 1 + $ThisFileInfo['gif']['header']['bits_per_pixel'] = 3 * ((($ThisFileInfo['gif']['header']['raw']['flags'] & 0x70) >> 4) + 1); + } else { + $ThisFileInfo['gif']['header']['bits_per_pixel'] = 0; + } + $ThisFileInfo['gif']['header']['flags']['global_color_sorted'] = (bool) ($ThisFileInfo['gif']['header']['raw']['flags'] & 0x40); + if ($ThisFileInfo['gif']['header']['flags']['global_color_table']) { + // the number of bytes contained in the Global Color Table. To determine that + // actual size of the color table, raise 2 to [the value of the field + 1] + $ThisFileInfo['gif']['header']['global_color_size'] = pow(2, ($ThisFileInfo['gif']['header']['raw']['flags'] & 0x07) + 1); + $ThisFileInfo['video']['bits_per_sample'] = ($ThisFileInfo['gif']['header']['raw']['flags'] & 0x07) + 1; + } else { + $ThisFileInfo['gif']['header']['global_color_size'] = 0; + } + if ($ThisFileInfo['gif']['header']['raw']['aspect_ratio'] != 0) { + // Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 + $ThisFileInfo['gif']['header']['aspect_ratio'] = ($ThisFileInfo['gif']['header']['raw']['aspect_ratio'] + 15) / 64; + } + +// if ($ThisFileInfo['gif']['header']['flags']['global_color_table']) { +// $GIFcolorTable = fread($fd, 3 * $ThisFileInfo['gif']['header']['global_color_size']); +// $offset = 0; +// for ($i = 0; $i < $ThisFileInfo['gif']['header']['global_color_size']; $i++) { +// $red = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1)); +// $green = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1)); +// $blue = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1)); +// $ThisFileInfo['gif']['global_color_table'][$i] = (($red << 16) | ($green << 8) | ($blue)); +// } +// } +// +// // Image Descriptor +// while (!feof($fd)) { +// $NextBlockTest = fread($fd, 1); +// switch ($NextBlockTest) { +// +// case ',': // ',' - Image separator character +// +// $ImageDescriptorData = $NextBlockTest.fread($fd, 9); +// $ImageDescriptor = array(); +// $ImageDescriptor['image_left'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 1, 2)); +// $ImageDescriptor['image_top'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 3, 2)); +// $ImageDescriptor['image_width'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 5, 2)); +// $ImageDescriptor['image_height'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 7, 2)); +// $ImageDescriptor['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 9, 1)); +// $ImageDescriptor['flags']['use_local_color_map'] = (bool) ($ImageDescriptor['flags_raw'] & 0x80); +// $ImageDescriptor['flags']['image_interlaced'] = (bool) ($ImageDescriptor['flags_raw'] & 0x40); +// $ThisFileInfo['gif']['image_descriptor'][] = $ImageDescriptor; +// +// if ($ImageDescriptor['flags']['use_local_color_map']) { +// +// $ThisFileInfo['warning'][] = 'This version of getID3() cannot parse local color maps for GIFs'; +// return true; +// +// } +//echo 'Start of raster data: '.ftell($fd).'<BR>'; +// $RasterData = array(); +// $RasterData['code_size'] = getid3_lib::LittleEndian2Int(fread($fd, 1)); +// $RasterData['block_byte_count'] = getid3_lib::LittleEndian2Int(fread($fd, 1)); +// $ThisFileInfo['gif']['raster_data'][count($ThisFileInfo['gif']['image_descriptor']) - 1] = $RasterData; +// +// $CurrentCodeSize = $RasterData['code_size'] + 1; +// for ($i = 0; $i < pow(2, $RasterData['code_size']); $i++) { +// $DefaultDataLookupTable[$i] = chr($i); +// } +// $DefaultDataLookupTable[pow(2, $RasterData['code_size']) + 0] = ''; // Clear Code +// $DefaultDataLookupTable[pow(2, $RasterData['code_size']) + 1] = ''; // End Of Image Code +// +// +// $NextValue = $this->GetLSBits($fd, $CurrentCodeSize); +// echo 'Clear Code: '.$NextValue.'<BR>'; +// +// $NextValue = $this->GetLSBits($fd, $CurrentCodeSize); +// echo 'First Color: '.$NextValue.'<BR>'; +// +// $Prefix = $NextValue; +//$i = 0; +// while ($i++ < 20) { +// $NextValue = $this->GetLSBits($fd, $CurrentCodeSize); +// echo $NextValue.'<BR>'; +// } +//return true; +// break; +// +// case '!': +// // GIF Extension Block +// $ExtensionBlockData = $NextBlockTest.fread($fd, 2); +// $ExtensionBlock = array(); +// $ExtensionBlock['function_code'] = getid3_lib::LittleEndian2Int(substr($ExtensionBlockData, 1, 1)); +// $ExtensionBlock['byte_length'] = getid3_lib::LittleEndian2Int(substr($ExtensionBlockData, 2, 1)); +// $ExtensionBlock['data'] = fread($fd, $ExtensionBlock['byte_length']); +// $ThisFileInfo['gif']['extension_blocks'][] = $ExtensionBlock; +// break; +// +// case ';': +// $ThisFileInfo['gif']['terminator_offset'] = ftell($fd) - 1; +// // GIF Terminator +// break; +// +// default: +// break; +// +// +// } +// } + + return true; + } + + + function GetLSBits($fd, $bits) { + static $bitbuffer = ''; + while (strlen($bitbuffer) < $bits) { +//echo 'Read another byte: '.ftell($fd).'<BR>'; + $bitbuffer = str_pad(decbin(ord(fread($fd, 1))), 8, '0', STR_PAD_LEFT).$bitbuffer; + } + + $value = bindec(substr($bitbuffer, 0 - $bits)); + $bitbuffer = substr($bitbuffer, 0, 0 - $bits); + + return $value; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.graphic.jpg.php b/modules/id3/getid3/module.graphic.jpg.php new file mode 100644 index 00000000..0cd305ce --- /dev/null +++ b/modules/id3/getid3/module.graphic.jpg.php @@ -0,0 +1,72 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.graphic.jpg.php // +// module for analyzing JPEG Image files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_jpg +{ + + + function getid3_jpg(&$fd, &$ThisFileInfo) { + $ThisFileInfo['fileformat'] = 'jpg'; + $ThisFileInfo['video']['dataformat'] = 'jpg'; + $ThisFileInfo['video']['lossless'] = false; + $ThisFileInfo['video']['bits_per_sample'] = 24; + $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + + list($width, $height, $type) = getid3_lib::GetDataImageSize(fread($fd, $ThisFileInfo['filesize'])); + if ($type == 2) { + + $ThisFileInfo['video']['resolution_x'] = $width; + $ThisFileInfo['video']['resolution_y'] = $height; + + if (version_compare(phpversion(), '4.2.0', '>=')) { + + if (function_exists('exif_read_data')) { + + ob_start(); + $ThisFileInfo['jpg']['exif'] = exif_read_data($ThisFileInfo['filenamepath'], '', true, false); + $errors = ob_get_contents(); + if ($errors) { + $ThisFileInfo['error'][] = strip_tags($errors); + unset($ThisFileInfo['jpg']['exif']); + } + ob_end_clean(); + + } else { + + $ThisFileInfo['warning'][] = 'EXIF parsing only available when '.(GETID3_OS_ISWINDOWS ? 'php_exif.dll enabled' : 'compiled with --enable-exif'); + + } + + } else { + + $ThisFileInfo['warning'][] = 'EXIF parsing only available in PHP v4.2.0 and higher compiled with --enable-exif (or php_exif.dll enabled for Windows). You are using PHP v'.phpversion(); + + } + + return true; + + } + + unset($ThisFileInfo['fileformat']); + return false; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.graphic.pcd.php b/modules/id3/getid3/module.graphic.pcd.php new file mode 100644 index 00000000..60efabdf --- /dev/null +++ b/modules/id3/getid3/module.graphic.pcd.php @@ -0,0 +1,130 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.graphic.pcd.php // +// module for analyzing PhotoCD (PCD) Image files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_pcd +{ + function getid3_pcd(&$fd, &$ThisFileInfo, $ExtractData=0) { + $ThisFileInfo['fileformat'] = 'pcd'; + $ThisFileInfo['video']['dataformat'] = 'pcd'; + $ThisFileInfo['video']['lossless'] = false; + + + fseek($fd, $ThisFileInfo['avdataoffset'] + 72, SEEK_SET); + + $PCDflags = fread($fd, 1); + $PCDisVertical = ((ord($PCDflags) & 0x01) ? true : false); + + + if ($PCDisVertical) { + $ThisFileInfo['video']['resolution_x'] = 3072; + $ThisFileInfo['video']['resolution_y'] = 2048; + } else { + $ThisFileInfo['video']['resolution_x'] = 2048; + $ThisFileInfo['video']['resolution_y'] = 3072; + } + + + if ($ExtractData > 3) { + + $ThisFileInfo['error'][] = 'Cannot extract PSD image data for detail levels above BASE (3)'; + + } elseif ($ExtractData > 0) { + + $PCD_levels[1] = array( 192, 128, 0x02000); // BASE/16 + $PCD_levels[2] = array( 384, 256, 0x0B800); // BASE/4 + $PCD_levels[3] = array( 768, 512, 0x30000); // BASE + //$PCD_levels[4] = array(1536, 1024, ??); // BASE*4 - encrypted with Kodak-proprietary compression/encryption + //$PCD_levels[5] = array(3072, 2048, ??); // BASE*16 - encrypted with Kodak-proprietary compression/encryption + //$PCD_levels[6] = array(6144, 4096, ??); // BASE*64 - encrypted with Kodak-proprietary compression/encryption; PhotoCD-Pro only + + list($PCD_width, $PCD_height, $PCD_dataOffset) = $PCD_levels[3]; + + fseek($fd, $ThisFileInfo['avdataoffset'] + $PCD_dataOffset, SEEK_SET); + + for ($y = 0; $y < $PCD_height; $y += 2) { + // The image-data of these subtypes start at the respective offsets of 02000h, 0b800h and 30000h. + // To decode the YcbYr to the more usual RGB-code, three lines of data have to be read, each + // consisting of ‘w’ bytes, where ‘w’ is the width of the image-subtype. The first ‘w’ bytes and + // the first half of the third ‘w’ bytes contain data for the first RGB-line, the second ‘w’ bytes + // and the second half of the third ‘w’ bytes contain data for a second RGB-line. + + $PCD_data_Y1 = fread($fd, $PCD_width); + $PCD_data_Y2 = fread($fd, $PCD_width); + $PCD_data_Cb = fread($fd, intval(round($PCD_width / 2))); + $PCD_data_Cr = fread($fd, intval(round($PCD_width / 2))); + + for ($x = 0; $x < $PCD_width; $x++) { + if ($PCDisVertical) { + $ThisFileInfo['pcd']['data'][$PCD_width - $x][$y] = $this->YCbCr2RGB(ord($PCD_data_Y1{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)})); + $ThisFileInfo['pcd']['data'][$PCD_width - $x][$y + 1] = $this->YCbCr2RGB(ord($PCD_data_Y2{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)})); + } else { + $ThisFileInfo['pcd']['data'][$y][$x] = $this->YCbCr2RGB(ord($PCD_data_Y1{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)})); + $ThisFileInfo['pcd']['data'][$y + 1][$x] = $this->YCbCr2RGB(ord($PCD_data_Y2{$x}), ord($PCD_data_Cb{floor($x / 2)}), ord($PCD_data_Cr{floor($x / 2)})); + } + } + } + + // Example for plotting extracted data + //getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, true); + //if ($PCDisVertical) { + // $BMPinfo['resolution_x'] = $PCD_height; + // $BMPinfo['resolution_y'] = $PCD_width; + //} else { + // $BMPinfo['resolution_x'] = $PCD_width; + // $BMPinfo['resolution_y'] = $PCD_height; + //} + //$BMPinfo['bmp']['data'] = $ThisFileInfo['pcd']['data']; + //getid3_bmp::PlotBMP($BMPinfo); + //exit; + + } + + } + + function YCbCr2RGB($Y, $Cb, $Cr) { + static $YCbCr_constants = array(); + if (empty($YCbCr_constants)) { + $YCbCr_constants['red']['Y'] = 0.0054980 * 256; + $YCbCr_constants['red']['Cb'] = 0.0000000 * 256; + $YCbCr_constants['red']['Cr'] = 0.0051681 * 256; + $YCbCr_constants['green']['Y'] = 0.0054980 * 256; + $YCbCr_constants['green']['Cb'] = -0.0015446 * 256; + $YCbCr_constants['green']['Cr'] = -0.0026325 * 256; + $YCbCr_constants['blue']['Y'] = 0.0054980 * 256; + $YCbCr_constants['blue']['Cb'] = 0.0079533 * 256; + $YCbCr_constants['blue']['Cr'] = 0.0000000 * 256; + } + + $RGBcolor = array('red'=>0, 'green'=>0, 'blue'=>0); + foreach ($RGBcolor as $rgbname => $dummy) { + $RGBcolor[$rgbname] = max(0, + min(255, + intval( + round( + ($YCbCr_constants[$rgbname]['Y'] * $Y) + + ($YCbCr_constants[$rgbname]['Cb'] * ($Cb - 156)) + + ($YCbCr_constants[$rgbname]['Cr'] * ($Cr - 137)) + ) + ) + ) + ); + } + return (($RGBcolor['red'] * 65536) + ($RGBcolor['green'] * 256) + $RGBcolor['blue']); + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.graphic.png.php b/modules/id3/getid3/module.graphic.png.php new file mode 100644 index 00000000..7d82b849 --- /dev/null +++ b/modules/id3/getid3/module.graphic.png.php @@ -0,0 +1,519 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.graphic.png.php // +// module for analyzing PNG Image files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_png +{ + + function getid3_png(&$fd, &$ThisFileInfo) { + + // shortcut + $ThisFileInfo['png'] = array(); + $thisfile_png = &$ThisFileInfo['png']; + + $ThisFileInfo['fileformat'] = 'png'; + $ThisFileInfo['video']['dataformat'] = 'png'; + $ThisFileInfo['video']['lossless'] = false; + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $PNGfiledata = fread($fd, GETID3_FREAD_BUFFER_SIZE); + $offset = 0; + + $PNGidentifier = substr($PNGfiledata, $offset, 8); // $89 $50 $4E $47 $0D $0A $1A $0A + $offset += 8; + + if ($PNGidentifier != "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") { + $ThisFileInfo['error'][] = 'First 8 bytes of file ('.getid3_lib::PrintHexBytes($PNGidentifier).') did not match expected PNG identifier'; + unset($ThisFileInfo['fileformat']); + return false; + } + + while (((ftell($fd) - (strlen($PNGfiledata) - $offset)) < $ThisFileInfo['filesize'])) { + $chunk['data_length'] = getid3_lib::BigEndian2Int(substr($PNGfiledata, $offset, 4)); + $offset += 4; + while (((strlen($PNGfiledata) - $offset) < ($chunk['data_length'] + 4)) && (ftell($fd) < $ThisFileInfo['filesize'])) { + $PNGfiledata .= fread($fd, GETID3_FREAD_BUFFER_SIZE); + } + $chunk['type_text'] = substr($PNGfiledata, $offset, 4); + $offset += 4; + $chunk['type_raw'] = getid3_lib::BigEndian2Int($chunk['type_text']); + $chunk['data'] = substr($PNGfiledata, $offset, $chunk['data_length']); + $offset += $chunk['data_length']; + $chunk['crc'] = getid3_lib::BigEndian2Int(substr($PNGfiledata, $offset, 4)); + $offset += 4; + + $chunk['flags']['ancilliary'] = (bool) ($chunk['type_raw'] & 0x20000000); + $chunk['flags']['private'] = (bool) ($chunk['type_raw'] & 0x00200000); + $chunk['flags']['reserved'] = (bool) ($chunk['type_raw'] & 0x00002000); + $chunk['flags']['safe_to_copy'] = (bool) ($chunk['type_raw'] & 0x00000020); + + // shortcut + $thisfile_png[$chunk['type_text']] = array(); + $thisfile_png_chunk_type_text = &$thisfile_png[$chunk['type_text']]; + + switch ($chunk['type_text']) { + + case 'IHDR': // Image Header + $thisfile_png_chunk_type_text['header'] = $chunk; + $thisfile_png_chunk_type_text['width'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0, 4)); + $thisfile_png_chunk_type_text['height'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 4, 4)); + $thisfile_png_chunk_type_text['raw']['bit_depth'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 8, 1)); + $thisfile_png_chunk_type_text['raw']['color_type'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 9, 1)); + $thisfile_png_chunk_type_text['raw']['compression_method'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 10, 1)); + $thisfile_png_chunk_type_text['raw']['filter_method'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 11, 1)); + $thisfile_png_chunk_type_text['raw']['interlace_method'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 12, 1)); + + $thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['raw']['compression_method']); + $thisfile_png_chunk_type_text['color_type']['palette'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x01); + $thisfile_png_chunk_type_text['color_type']['true_color'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x02); + $thisfile_png_chunk_type_text['color_type']['alpha'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x04); + + $ThisFileInfo['video']['resolution_x'] = $thisfile_png_chunk_type_text['width']; + $ThisFileInfo['video']['resolution_y'] = $thisfile_png_chunk_type_text['height']; + + $ThisFileInfo['video']['bits_per_sample'] = $this->IHDRcalculateBitsPerSample($thisfile_png_chunk_type_text['raw']['color_type'], $thisfile_png_chunk_type_text['raw']['bit_depth']); + break; + + + case 'PLTE': // Palette + $thisfile_png_chunk_type_text['header'] = $chunk; + $paletteoffset = 0; + for ($i = 0; $i <= 255; $i++) { + //$thisfile_png_chunk_type_text['red'][$i] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], $paletteoffset++, 1)); + //$thisfile_png_chunk_type_text['green'][$i] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], $paletteoffset++, 1)); + //$thisfile_png_chunk_type_text['blue'][$i] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], $paletteoffset++, 1)); + $red = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], $paletteoffset++, 1)); + $green = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], $paletteoffset++, 1)); + $blue = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], $paletteoffset++, 1)); + $thisfile_png_chunk_type_text[$i] = (($red << 16) | ($green << 8) | ($blue)); + } + break; + + + case 'tRNS': // Transparency + $thisfile_png_chunk_type_text['header'] = $chunk; + switch ($thisfile_png['IHDR']['raw']['color_type']) { + case 0: + $thisfile_png_chunk_type_text['transparent_color_gray'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0, 2)); + break; + + case 2: + $thisfile_png_chunk_type_text['transparent_color_red'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0, 2)); + $thisfile_png_chunk_type_text['transparent_color_green'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 2, 2)); + $thisfile_png_chunk_type_text['transparent_color_blue'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 4, 2)); + break; + + case 3: + for ($i = 0; $i < strlen($thisfile_png_chunk_type_text['header']['data']); $i++) { + $thisfile_png_chunk_type_text['palette_opacity'][$i] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], $i, 1)); + } + break; + + case 4: + case 6: + $ThisFileInfo['error'][] = 'Invalid color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type']; + + default: + $ThisFileInfo['warning'][] = 'Unhandled color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type']; + break; + } + break; + + + case 'gAMA': // Image Gamma + $thisfile_png_chunk_type_text['header'] = $chunk; + $thisfile_png_chunk_type_text['gamma'] = getid3_lib::BigEndian2Int($thisfile_png_chunk_type_text['header']['data']) / 100000; + break; + + + case 'cHRM': // Primary Chromaticities + $thisfile_png_chunk_type_text['header'] = $chunk; + $thisfile_png_chunk_type_text['white_x'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0, 4)) / 100000; + $thisfile_png_chunk_type_text['white_y'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 4, 4)) / 100000; + $thisfile_png_chunk_type_text['red_y'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 8, 4)) / 100000; + $thisfile_png_chunk_type_text['red_y'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 12, 4)) / 100000; + $thisfile_png_chunk_type_text['green_y'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 16, 4)) / 100000; + $thisfile_png_chunk_type_text['green_y'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 20, 4)) / 100000; + $thisfile_png_chunk_type_text['blue_y'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 24, 4)) / 100000; + $thisfile_png_chunk_type_text['blue_y'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 28, 4)) / 100000; + break; + + + case 'sRGB': // Standard RGB Color Space + $thisfile_png_chunk_type_text['header'] = $chunk; + $thisfile_png_chunk_type_text['reindering_intent'] = getid3_lib::BigEndian2Int($thisfile_png_chunk_type_text['header']['data']); + $thisfile_png_chunk_type_text['reindering_intent_text'] = $this->PNGsRGBintentLookup($thisfile_png_chunk_type_text['reindering_intent']); + break; + + + case 'iCCP': // Embedded ICC Profile + $thisfile_png_chunk_type_text['header'] = $chunk; + list($profilename, $compressiondata) = explode("\x00", $thisfile_png_chunk_type_text['header']['data'], 2); + $thisfile_png_chunk_type_text['profile_name'] = $profilename; + $thisfile_png_chunk_type_text['compression_method'] = getid3_lib::BigEndian2Int(substr($compressiondata, 0, 1)); + $thisfile_png_chunk_type_text['compression_profile'] = substr($compressiondata, 1); + + $thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']); + break; + + + case 'tEXt': // Textual Data + $thisfile_png_chunk_type_text['header'] = $chunk; + list($keyword, $text) = explode("\x00", $thisfile_png_chunk_type_text['header']['data'], 2); + $thisfile_png_chunk_type_text['keyword'] = $keyword; + $thisfile_png_chunk_type_text['text'] = $text; + + $thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text']; + break; + + + case 'zTXt': // Compressed Textual Data + $thisfile_png_chunk_type_text['header'] = $chunk; + list($keyword, $otherdata) = explode("\x00", $thisfile_png_chunk_type_text['header']['data'], 2); + $thisfile_png_chunk_type_text['keyword'] = $keyword; + $thisfile_png_chunk_type_text['compression_method'] = getid3_lib::BigEndian2Int(substr($otherdata, 0, 1)); + $thisfile_png_chunk_type_text['compressed_text'] = substr($otherdata, 1); + $thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']); + switch ($thisfile_png_chunk_type_text['compression_method']) { + case 0: + $thisfile_png_chunk_type_text['text'] = gzuncompress($thisfile_png_chunk_type_text['compressed_text']); + break; + + default: + // unknown compression method + break; + } + + if (isset($thisfile_png_chunk_type_text['text'])) { + $thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text']; + } + break; + + + case 'iTXt': // International Textual Data + $thisfile_png_chunk_type_text['header'] = $chunk; + list($keyword, $otherdata) = explode("\x00", $thisfile_png_chunk_type_text['header']['data'], 2); + $thisfile_png_chunk_type_text['keyword'] = $keyword; + $thisfile_png_chunk_type_text['compression'] = (bool) getid3_lib::BigEndian2Int(substr($otherdata, 0, 1)); + $thisfile_png_chunk_type_text['compression_method'] = getid3_lib::BigEndian2Int(substr($otherdata, 1, 1)); + $thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']); + list($languagetag, $translatedkeyword, $text) = explode("\x00", substr($otherdata, 2), 3); + $thisfile_png_chunk_type_text['language_tag'] = $languagetag; + $thisfile_png_chunk_type_text['translated_keyword'] = $translatedkeyword; + + if ($thisfile_png_chunk_type_text['compression']) { + + switch ($thisfile_png_chunk_type_text['compression_method']) { + case 0: + $thisfile_png_chunk_type_text['text'] = gzuncompress($text); + break; + + default: + // unknown compression method + break; + } + + } else { + + $thisfile_png_chunk_type_text['text'] = $text; + + } + + if (isset($thisfile_png_chunk_type_text['text'])) { + $thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text']; + } + break; + + + case 'bKGD': // Background Color + $thisfile_png_chunk_type_text['header'] = $chunk; + switch ($thisfile_png['IHDR']['raw']['color_type']) { + case 0: + case 4: + $thisfile_png_chunk_type_text['background_gray'] = getid3_lib::BigEndian2Int($thisfile_png_chunk_type_text['header']['data']); + break; + + case 2: + case 6: + $thisfile_png_chunk_type_text['background_red'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth'])); + $thisfile_png_chunk_type_text['background_green'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 1 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth'])); + $thisfile_png_chunk_type_text['background_blue'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 2 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth'])); + break; + + case 3: + $thisfile_png_chunk_type_text['background_index'] = getid3_lib::BigEndian2Int($thisfile_png_chunk_type_text['header']['data']); + break; + + default: + break; + } + break; + + + case 'pHYs': // Physical Pixel Dimensions + $thisfile_png_chunk_type_text['header'] = $chunk; + $thisfile_png_chunk_type_text['pixels_per_unit_x'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0, 4)); + $thisfile_png_chunk_type_text['pixels_per_unit_y'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 4, 4)); + $thisfile_png_chunk_type_text['unit_specifier'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 8, 1)); + $thisfile_png_chunk_type_text['unit'] = $this->PNGpHYsUnitLookup($thisfile_png_chunk_type_text['unit_specifier']); + break; + + + case 'sBIT': // Significant Bits + $thisfile_png_chunk_type_text['header'] = $chunk; + switch ($thisfile_png['IHDR']['raw']['color_type']) { + case 0: + $thisfile_png_chunk_type_text['significant_bits_gray'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0, 1)); + break; + + case 2: + case 3: + $thisfile_png_chunk_type_text['significant_bits_red'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0, 1)); + $thisfile_png_chunk_type_text['significant_bits_green'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 1, 1)); + $thisfile_png_chunk_type_text['significant_bits_blue'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 2, 1)); + break; + + case 4: + $thisfile_png_chunk_type_text['significant_bits_gray'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0, 1)); + $thisfile_png_chunk_type_text['significant_bits_alpha'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 1, 1)); + break; + + case 6: + $thisfile_png_chunk_type_text['significant_bits_red'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0, 1)); + $thisfile_png_chunk_type_text['significant_bits_green'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 1, 1)); + $thisfile_png_chunk_type_text['significant_bits_blue'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 2, 1)); + $thisfile_png_chunk_type_text['significant_bits_alpha'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 3, 1)); + break; + + default: + break; + } + break; + + + case 'sPLT': // Suggested Palette + $thisfile_png_chunk_type_text['header'] = $chunk; + list($palettename, $otherdata) = explode("\x00", $thisfile_png_chunk_type_text['header']['data'], 2); + $thisfile_png_chunk_type_text['palette_name'] = $palettename; + $sPLToffset = 0; + $thisfile_png_chunk_type_text['sample_depth_bits'] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, 1)); + $sPLToffset += 1; + $thisfile_png_chunk_type_text['sample_depth_bytes'] = $thisfile_png_chunk_type_text['sample_depth_bits'] / 8; + $paletteCounter = 0; + while ($sPLToffset < strlen($otherdata)) { + $thisfile_png_chunk_type_text['red'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes'])); + $sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes']; + $thisfile_png_chunk_type_text['green'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes'])); + $sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes']; + $thisfile_png_chunk_type_text['blue'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes'])); + $sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes']; + $thisfile_png_chunk_type_text['alpha'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes'])); + $sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes']; + $thisfile_png_chunk_type_text['frequency'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, 2)); + $sPLToffset += 2; + $paletteCounter++; + } + break; + + + case 'hIST': // Palette Histogram + $thisfile_png_chunk_type_text['header'] = $chunk; + $hISTcounter = 0; + while ($hISTcounter < strlen($thisfile_png_chunk_type_text['header']['data'])) { + $thisfile_png_chunk_type_text[$hISTcounter] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], $hISTcounter / 2, 2)); + $hISTcounter += 2; + } + break; + + + case 'tIME': // Image Last-Modification Time + $thisfile_png_chunk_type_text['header'] = $chunk; + $thisfile_png_chunk_type_text['year'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0, 2)); + $thisfile_png_chunk_type_text['month'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 2, 1)); + $thisfile_png_chunk_type_text['day'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 3, 1)); + $thisfile_png_chunk_type_text['hour'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 4, 1)); + $thisfile_png_chunk_type_text['minute'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 5, 1)); + $thisfile_png_chunk_type_text['second'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 6, 1)); + $thisfile_png_chunk_type_text['unix'] = gmmktime($thisfile_png_chunk_type_text['hour'], $thisfile_png_chunk_type_text['minute'], $thisfile_png_chunk_type_text['second'], $thisfile_png_chunk_type_text['month'], $thisfile_png_chunk_type_text['day'], $thisfile_png_chunk_type_text['year']); + break; + + + case 'oFFs': // Image Offset + $thisfile_png_chunk_type_text['header'] = $chunk; + $thisfile_png_chunk_type_text['position_x'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0, 4), false, true); + $thisfile_png_chunk_type_text['position_y'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 4, 4), false, true); + $thisfile_png_chunk_type_text['unit_specifier'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 8, 1)); + $thisfile_png_chunk_type_text['unit'] = $this->PNGoFFsUnitLookup($thisfile_png_chunk_type_text['unit_specifier']); + break; + + + case 'pCAL': // Calibration Of Pixel Values + $thisfile_png_chunk_type_text['header'] = $chunk; + list($calibrationname, $otherdata) = explode("\x00", $thisfile_png_chunk_type_text['header']['data'], 2); + $thisfile_png_chunk_type_text['calibration_name'] = $calibrationname; + $pCALoffset = 0; + $thisfile_png_chunk_type_text['original_zero'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], $pCALoffset, 4), false, true); + $pCALoffset += 4; + $thisfile_png_chunk_type_text['original_max'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], $pCALoffset, 4), false, true); + $pCALoffset += 4; + $thisfile_png_chunk_type_text['equation_type'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], $pCALoffset, 1)); + $pCALoffset += 1; + $thisfile_png_chunk_type_text['equation_type_text'] = $this->PNGpCALequationTypeLookup($thisfile_png_chunk_type_text['equation_type']); + $thisfile_png_chunk_type_text['parameter_count'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], $pCALoffset, 1)); + $pCALoffset += 1; + $thisfile_png_chunk_type_text['parameters'] = explode("\x00", substr($thisfile_png_chunk_type_text['header']['data'], $pCALoffset)); + break; + + + case 'sCAL': // Physical Scale Of Image Subject + $thisfile_png_chunk_type_text['header'] = $chunk; + $thisfile_png_chunk_type_text['unit_specifier'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0, 1)); + $thisfile_png_chunk_type_text['unit'] = $this->PNGsCALUnitLookup($thisfile_png_chunk_type_text['unit_specifier']); + list($pixelwidth, $pixelheight) = explode("\x00", substr($thisfile_png_chunk_type_text['header']['data'], 1)); + $thisfile_png_chunk_type_text['pixel_width'] = $pixelwidth; + $thisfile_png_chunk_type_text['pixel_height'] = $pixelheight; + break; + + + case 'gIFg': // GIF Graphic Control Extension + $gIFgCounter = 0; + if (isset($thisfile_png_chunk_type_text) && is_array($thisfile_png_chunk_type_text)) { + $gIFgCounter = count($thisfile_png_chunk_type_text); + } + $thisfile_png_chunk_type_text[$gIFgCounter]['header'] = $chunk; + $thisfile_png_chunk_type_text[$gIFgCounter]['disposal_method'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 0, 1)); + $thisfile_png_chunk_type_text[$gIFgCounter]['user_input_flag'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 1, 1)); + $thisfile_png_chunk_type_text[$gIFgCounter]['delay_time'] = getid3_lib::BigEndian2Int(substr($thisfile_png_chunk_type_text['header']['data'], 2, 2)); + break; + + + case 'gIFx': // GIF Application Extension + $gIFxCounter = 0; + if (isset($thisfile_png_chunk_type_text) && is_array($thisfile_png_chunk_type_text)) { + $gIFxCounter = count($thisfile_png_chunk_type_text); + } + $thisfile_png_chunk_type_text[$gIFxCounter]['header'] = $chunk; + $thisfile_png_chunk_type_text[$gIFxCounter]['application_identifier'] = substr($thisfile_png_chunk_type_text['header']['data'], 0, 8); + $thisfile_png_chunk_type_text[$gIFxCounter]['authentication_code'] = substr($thisfile_png_chunk_type_text['header']['data'], 8, 3); + $thisfile_png_chunk_type_text[$gIFxCounter]['application_data'] = substr($thisfile_png_chunk_type_text['header']['data'], 11); + break; + + + case 'IDAT': // Image Data + $idatinformationfieldindex = 0; + if (isset($thisfile_png['IDAT']) && is_array($thisfile_png['IDAT'])) { + $idatinformationfieldindex = count($thisfile_png['IDAT']); + } + unset($chunk['data']); + $thisfile_png_chunk_type_text[$idatinformationfieldindex]['header'] = $chunk; + break; + + + case 'IEND': // Image Trailer + $thisfile_png_chunk_type_text['header'] = $chunk; + break; + + + default: + //unset($chunk['data']); + $thisfile_png_chunk_type_text['header'] = $chunk; + $ThisFileInfo['warning'][] = 'Unhandled chunk type: '.$chunk['type_text']; + break; + } + } + + return true; + } + + function PNGsRGBintentLookup($sRGB) { + static $PNGsRGBintentLookup = array( + 0 => 'Perceptual', + 1 => 'Relative colorimetric', + 2 => 'Saturation', + 3 => 'Absolute colorimetric' + ); + return (isset($PNGsRGBintentLookup[$sRGB]) ? $PNGsRGBintentLookup[$sRGB] : 'invalid'); + } + + function PNGcompressionMethodLookup($compressionmethod) { + static $PNGcompressionMethodLookup = array( + 0 => 'deflate/inflate' + ); + return (isset($PNGcompressionMethodLookup[$compressionmethod]) ? $PNGcompressionMethodLookup[$compressionmethod] : 'invalid'); + } + + function PNGpHYsUnitLookup($unitid) { + static $PNGpHYsUnitLookup = array( + 0 => 'unknown', + 1 => 'meter' + ); + return (isset($PNGpHYsUnitLookup[$unitid]) ? $PNGpHYsUnitLookup[$unitid] : 'invalid'); + } + + function PNGoFFsUnitLookup($unitid) { + static $PNGoFFsUnitLookup = array( + 0 => 'pixel', + 1 => 'micrometer' + ); + return (isset($PNGoFFsUnitLookup[$unitid]) ? $PNGoFFsUnitLookup[$unitid] : 'invalid'); + } + + function PNGpCALequationTypeLookup($equationtype) { + static $PNGpCALequationTypeLookup = array( + 0 => 'Linear mapping', + 1 => 'Base-e exponential mapping', + 2 => 'Arbitrary-base exponential mapping', + 3 => 'Hyperbolic mapping' + ); + return (isset($PNGpCALequationTypeLookup[$equationtype]) ? $PNGpCALequationTypeLookup[$equationtype] : 'invalid'); + } + + function PNGsCALUnitLookup($unitid) { + static $PNGsCALUnitLookup = array( + 0 => 'meter', + 1 => 'radian' + ); + return (isset($PNGsCALUnitLookup[$unitid]) ? $PNGsCALUnitLookup[$unitid] : 'invalid'); + } + + function IHDRcalculateBitsPerSample($color_type, $bit_depth) { + switch ($color_type) { + case 0: // Each pixel is a grayscale sample. + return $bit_depth; + break; + + case 2: // Each pixel is an R,G,B triple + return 3 * $bit_depth; + break; + + case 3: // Each pixel is a palette index; a PLTE chunk must appear. + return $bit_depth; + break; + + case 4: // Each pixel is a grayscale sample, followed by an alpha sample. + return 2 * $bit_depth; + break; + + case 6: // Each pixel is an R,G,B triple, followed by an alpha sample. + return 4 * $bit_depth; + break; + } + return false; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.graphic.tiff.php b/modules/id3/getid3/module.graphic.tiff.php new file mode 100644 index 00000000..ae57cd6a --- /dev/null +++ b/modules/id3/getid3/module.graphic.tiff.php @@ -0,0 +1,221 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.archive.tiff.php // +// module for analyzing TIFF files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_tiff +{ + + function getid3_tiff(&$fd, &$ThisFileInfo) { + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $TIFFheader = fread($fd, 4); + + switch (substr($TIFFheader, 0, 2)) { + case 'II': + $ThisFileInfo['tiff']['byte_order'] = 'Intel'; + break; + case 'MM': + $ThisFileInfo['tiff']['byte_order'] = 'Motorola'; + break; + default: + $ThisFileInfo['error'][] = 'Invalid TIFF byte order identifier ('.substr($TIFFheader, 0, 2).') at offset '.$ThisFileInfo['avdataoffset']; + return false; + break; + } + + $ThisFileInfo['fileformat'] = 'tiff'; + $ThisFileInfo['video']['dataformat'] = 'tiff'; + $ThisFileInfo['video']['lossless'] = true; + $ThisFileInfo['tiff']['ifd'] = array(); + $CurrentIFD = array(); + + $FieldTypeByteLength = array(1=>1, 2=>1, 3=>2, 4=>4, 5=>8); + + $nextIFDoffset = $this->TIFFendian2Int(fread($fd, 4), $ThisFileInfo['tiff']['byte_order']); + + while ($nextIFDoffset > 0) { + + $CurrentIFD['offset'] = $nextIFDoffset; + + fseek($fd, $ThisFileInfo['avdataoffset'] + $nextIFDoffset, SEEK_SET); + $CurrentIFD['fieldcount'] = $this->TIFFendian2Int(fread($fd, 2), $ThisFileInfo['tiff']['byte_order']); + + for ($i = 0; $i < $CurrentIFD['fieldcount']; $i++) { + $CurrentIFD['fields'][$i]['raw']['tag'] = $this->TIFFendian2Int(fread($fd, 2), $ThisFileInfo['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['raw']['type'] = $this->TIFFendian2Int(fread($fd, 2), $ThisFileInfo['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['raw']['length'] = $this->TIFFendian2Int(fread($fd, 4), $ThisFileInfo['tiff']['byte_order']); + $CurrentIFD['fields'][$i]['raw']['offset'] = fread($fd, 4); + + switch ($CurrentIFD['fields'][$i]['raw']['type']) { + case 1: // BYTE An 8-bit unsigned integer. + if ($CurrentIFD['fields'][$i]['raw']['length'] <= 4) { + $CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int(substr($CurrentIFD['fields'][$i]['raw']['offset'], 0, 1), $ThisFileInfo['tiff']['byte_order']); + } else { + $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $ThisFileInfo['tiff']['byte_order']); + } + break; + + case 2: // ASCII 8-bit bytes that store ASCII codes; the last byte must be null. + if ($CurrentIFD['fields'][$i]['raw']['length'] <= 4) { + $CurrentIFD['fields'][$i]['value'] = substr($CurrentIFD['fields'][$i]['raw']['offset'], 3); + } else { + $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $ThisFileInfo['tiff']['byte_order']); + } + break; + + case 3: // SHORT A 16-bit (2-byte) unsigned integer. + if ($CurrentIFD['fields'][$i]['raw']['length'] <= 2) { + $CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int(substr($CurrentIFD['fields'][$i]['raw']['offset'], 0, 2), $ThisFileInfo['tiff']['byte_order']); + } else { + $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $ThisFileInfo['tiff']['byte_order']); + } + break; + + case 4: // LONG A 32-bit (4-byte) unsigned integer. + if ($CurrentIFD['fields'][$i]['raw']['length'] <= 1) { + $CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $ThisFileInfo['tiff']['byte_order']); + } else { + $CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['offset'], $ThisFileInfo['tiff']['byte_order']); + } + break; + + case 5: // RATIONAL Two LONG_s: the first represents the numerator of a fraction, the second the denominator. + break; + } + } + + $ThisFileInfo['tiff']['ifd'][] = $CurrentIFD; + $CurrentIFD = array(); + $nextIFDoffset = $this->TIFFendian2Int(fread($fd, 4), $ThisFileInfo['tiff']['byte_order']); + + } + + foreach ($ThisFileInfo['tiff']['ifd'] as $IFDid => $IFDarray) { + foreach ($IFDarray['fields'] as $key => $fieldarray) { + switch ($fieldarray['raw']['tag']) { + case 256: // ImageWidth + case 257: // ImageLength + case 258: // BitsPerSample + case 259: // Compression + if (!isset($fieldarray['value'])) { + fseek($fd, $fieldarray['offset'], SEEK_SET); + $ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($fd, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]); + + } + break; + + case 270: // ImageDescription + case 271: // Make + case 272: // Model + case 305: // Software + case 306: // DateTime + case 315: // Artist + case 316: // HostComputer + if (isset($fieldarray['value'])) { + $ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = $fieldarray['value']; + } else { + fseek($fd, $fieldarray['offset'], SEEK_SET); + $ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = fread($fd, $fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]); + + } + break; + } + switch ($fieldarray['raw']['tag']) { + case 256: // ImageWidth + $ThisFileInfo['video']['resolution_x'] = $fieldarray['value']; + break; + + case 257: // ImageLength + $ThisFileInfo['video']['resolution_y'] = $fieldarray['value']; + break; + + case 258: // BitsPerSample + if (isset($fieldarray['value'])) { + $ThisFileInfo['video']['bits_per_sample'] = $fieldarray['value']; + } else { + $ThisFileInfo['video']['bits_per_sample'] = 0; + for ($i = 0; $i < $fieldarray['raw']['length']; $i++) { + $ThisFileInfo['video']['bits_per_sample'] += $this->TIFFendian2Int(substr($ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'], $i * $FieldTypeByteLength[$fieldarray['raw']['type']], $FieldTypeByteLength[$fieldarray['raw']['type']]), $ThisFileInfo['tiff']['byte_order']); + } + } + break; + + case 259: // Compression + $ThisFileInfo['video']['codec'] = $this->TIFFcompressionMethod($fieldarray['value']); + break; + + case 270: // ImageDescription + case 271: // Make + case 272: // Model + case 305: // Software + case 306: // DateTime + case 315: // Artist + case 316: // HostComputer + @$ThisFileInfo['tiff']['comments'][$this->TIFFcommentName($fieldarray['raw']['tag'])][] = $ThisFileInfo['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data']; + break; + + default: + break; + } + } + } + + return true; + } + + + function TIFFendian2Int($bytestring, $byteorder) { + if ($byteorder == 'Intel') { + return getid3_lib::LittleEndian2Int($bytestring); + } elseif ($byteorder == 'Motorola') { + return getid3_lib::BigEndian2Int($bytestring); + } + return false; + } + + function TIFFcompressionMethod($id) { + static $TIFFcompressionMethod = array(); + if (empty($TIFFcompressionMethod)) { + $TIFFcompressionMethod = array( + 1 => 'Uncompressed', + 2 => 'Huffman', + 3 => 'Fax - CCITT 3', + 5 => 'LZW', + 32773 => 'PackBits', + ); + } + return (isset($TIFFcompressionMethod[$id]) ? $TIFFcompressionMethod[$id] : 'unknown/invalid ('.$id.')'); + } + + function TIFFcommentName($id) { + static $TIFFcommentName = array(); + if (empty($TIFFcommentName)) { + $TIFFcommentName = array( + 270 => 'imagedescription', + 271 => 'make', + 272 => 'model', + 305 => 'software', + 306 => 'datetime', + 315 => 'artist', + 316 => 'hostcomputer', + ); + } + return (isset($TIFFcommentName[$id]) ? $TIFFcommentName[$id] : 'unknown/invalid ('.$id.')'); + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.misc.exe.php b/modules/id3/getid3/module.misc.exe.php new file mode 100644 index 00000000..8c6bfcf9 --- /dev/null +++ b/modules/id3/getid3/module.misc.exe.php @@ -0,0 +1,59 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.misc.exe.php // +// module for analyzing EXE files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_exe +{ + + function getid3_exe(&$fd, &$ThisFileInfo) { + + fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); + $EXEheader = fread($fd, 28); + + if (substr($EXEheader, 0, 2) != 'MZ') { + $ThisFileInfo['error'][] = 'Expecting "MZ" at offset '.$ThisFileInfo['avdataoffset'].', found "'.substr($EXEheader, 0, 2).'" instead.'; + return false; + } + + $ThisFileInfo['fileformat'] = 'exe'; + $ThisFileInfo['exe']['mz']['magic'] = 'MZ'; + + $ThisFileInfo['exe']['mz']['raw']['last_page_size'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 2, 2)); + $ThisFileInfo['exe']['mz']['raw']['page_count'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 4, 2)); + $ThisFileInfo['exe']['mz']['raw']['relocation_count'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 6, 2)); + $ThisFileInfo['exe']['mz']['raw']['header_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 8, 2)); + $ThisFileInfo['exe']['mz']['raw']['min_memory_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 10, 2)); + $ThisFileInfo['exe']['mz']['raw']['max_memory_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 12, 2)); + $ThisFileInfo['exe']['mz']['raw']['initial_ss'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 14, 2)); + $ThisFileInfo['exe']['mz']['raw']['initial_sp'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 16, 2)); + $ThisFileInfo['exe']['mz']['raw']['checksum'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 18, 2)); + $ThisFileInfo['exe']['mz']['raw']['cs_ip'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 20, 4)); + $ThisFileInfo['exe']['mz']['raw']['relocation_table_offset'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 24, 2)); + $ThisFileInfo['exe']['mz']['raw']['overlay_number'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 26, 2)); + + $ThisFileInfo['exe']['mz']['byte_size'] = (($ThisFileInfo['exe']['mz']['raw']['page_count'] - 1)) * 512 + $ThisFileInfo['exe']['mz']['raw']['last_page_size']; + $ThisFileInfo['exe']['mz']['header_size'] = $ThisFileInfo['exe']['mz']['raw']['header_paragraphs'] * 16; + $ThisFileInfo['exe']['mz']['memory_minimum'] = $ThisFileInfo['exe']['mz']['raw']['min_memory_paragraphs'] * 16; + $ThisFileInfo['exe']['mz']['memory_recommended'] = $ThisFileInfo['exe']['mz']['raw']['max_memory_paragraphs'] * 16; + +$ThisFileInfo['error'][] = 'EXE parsing not enabled in this version of getID3()'; +return false; + + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.misc.iso.php b/modules/id3/getid3/module.misc.iso.php new file mode 100644 index 00000000..86115793 --- /dev/null +++ b/modules/id3/getid3/module.misc.iso.php @@ -0,0 +1,386 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.misc.iso.php // +// module for analyzing ISO files // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_iso +{ + + function getid3_iso($fd, &$ThisFileInfo) { + $ThisFileInfo['fileformat'] = 'iso'; + + for ($i = 16; $i <= 19; $i++) { + fseek($fd, 2048 * $i, SEEK_SET); + $ISOheader = fread($fd, 2048); + if (substr($ISOheader, 1, 5) == 'CD001') { + switch (ord($ISOheader{0})) { + case 1: + $ThisFileInfo['iso']['primary_volume_descriptor']['offset'] = 2048 * $i; + $this->ParsePrimaryVolumeDescriptor($ISOheader, $ThisFileInfo); + break; + + case 2: + $ThisFileInfo['iso']['supplementary_volume_descriptor']['offset'] = 2048 * $i; + $this->ParseSupplementaryVolumeDescriptor($ISOheader, $ThisFileInfo); + break; + + default: + // skip + break; + } + } + } + + $this->ParsePathTable($fd, $ThisFileInfo); + + $ThisFileInfo['iso']['files'] = array(); + foreach ($ThisFileInfo['iso']['path_table']['directories'] as $directorynum => $directorydata) { + + $ThisFileInfo['iso']['directories'][$directorynum] = $this->ParseDirectoryRecord($fd, $directorydata, $ThisFileInfo); + + } + + return true; + + } + + + function ParsePrimaryVolumeDescriptor(&$ISOheader, &$ThisFileInfo) { + // ISO integer values are stored *BOTH* Little-Endian AND Big-Endian format!! + // ie 12345 == 0x3039 is stored as $39 $30 $30 $39 in a 4-byte field + + // shortcuts + $ThisFileInfo['iso']['primary_volume_descriptor']['raw'] = array(); + $thisfile_iso_primaryVD = &$ThisFileInfo['iso']['primary_volume_descriptor']; + $thisfile_iso_primaryVD_raw = &$thisfile_iso_primaryVD['raw']; + + $thisfile_iso_primaryVD_raw['volume_descriptor_type'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 0, 1)); + $thisfile_iso_primaryVD_raw['standard_identifier'] = substr($ISOheader, 1, 5); + if ($thisfile_iso_primaryVD_raw['standard_identifier'] != 'CD001') { + $ThisFileInfo['error'][] = 'Expected "CD001" at offset ('.($thisfile_iso_primaryVD['offset'] + 1).'), found "'.$thisfile_iso_primaryVD_raw['standard_identifier'].'" instead'; + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['iso']); + return false; + } + + + $thisfile_iso_primaryVD_raw['volume_descriptor_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 6, 1)); + //$thisfile_iso_primaryVD_raw['unused_1'] = substr($ISOheader, 7, 1); + $thisfile_iso_primaryVD_raw['system_identifier'] = substr($ISOheader, 8, 32); + $thisfile_iso_primaryVD_raw['volume_identifier'] = substr($ISOheader, 40, 32); + //$thisfile_iso_primaryVD_raw['unused_2'] = substr($ISOheader, 72, 8); + $thisfile_iso_primaryVD_raw['volume_space_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 80, 4)); + //$thisfile_iso_primaryVD_raw['unused_3'] = substr($ISOheader, 88, 32); + $thisfile_iso_primaryVD_raw['volume_set_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 120, 2)); + $thisfile_iso_primaryVD_raw['volume_sequence_number'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 124, 2)); + $thisfile_iso_primaryVD_raw['logical_block_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 128, 2)); + $thisfile_iso_primaryVD_raw['path_table_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 132, 4)); + $thisfile_iso_primaryVD_raw['path_table_l_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 140, 2)); + $thisfile_iso_primaryVD_raw['path_table_l_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 144, 2)); + $thisfile_iso_primaryVD_raw['path_table_m_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 148, 2)); + $thisfile_iso_primaryVD_raw['path_table_m_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 152, 2)); + $thisfile_iso_primaryVD_raw['root_directory_record'] = substr($ISOheader, 156, 34); + $thisfile_iso_primaryVD_raw['volume_set_identifier'] = substr($ISOheader, 190, 128); + $thisfile_iso_primaryVD_raw['publisher_identifier'] = substr($ISOheader, 318, 128); + $thisfile_iso_primaryVD_raw['data_preparer_identifier'] = substr($ISOheader, 446, 128); + $thisfile_iso_primaryVD_raw['application_identifier'] = substr($ISOheader, 574, 128); + $thisfile_iso_primaryVD_raw['copyright_file_identifier'] = substr($ISOheader, 702, 37); + $thisfile_iso_primaryVD_raw['abstract_file_identifier'] = substr($ISOheader, 739, 37); + $thisfile_iso_primaryVD_raw['bibliographic_file_identifier'] = substr($ISOheader, 776, 37); + $thisfile_iso_primaryVD_raw['volume_creation_date_time'] = substr($ISOheader, 813, 17); + $thisfile_iso_primaryVD_raw['volume_modification_date_time'] = substr($ISOheader, 830, 17); + $thisfile_iso_primaryVD_raw['volume_expiration_date_time'] = substr($ISOheader, 847, 17); + $thisfile_iso_primaryVD_raw['volume_effective_date_time'] = substr($ISOheader, 864, 17); + $thisfile_iso_primaryVD_raw['file_structure_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 881, 1)); + //$thisfile_iso_primaryVD_raw['unused_4'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 882, 1)); + $thisfile_iso_primaryVD_raw['application_data'] = substr($ISOheader, 883, 512); + //$thisfile_iso_primaryVD_raw['unused_5'] = substr($ISOheader, 1395, 653); + + $thisfile_iso_primaryVD['system_identifier'] = trim($thisfile_iso_primaryVD_raw['system_identifier']); + $thisfile_iso_primaryVD['volume_identifier'] = trim($thisfile_iso_primaryVD_raw['volume_identifier']); + $thisfile_iso_primaryVD['volume_set_identifier'] = trim($thisfile_iso_primaryVD_raw['volume_set_identifier']); + $thisfile_iso_primaryVD['publisher_identifier'] = trim($thisfile_iso_primaryVD_raw['publisher_identifier']); + $thisfile_iso_primaryVD['data_preparer_identifier'] = trim($thisfile_iso_primaryVD_raw['data_preparer_identifier']); + $thisfile_iso_primaryVD['application_identifier'] = trim($thisfile_iso_primaryVD_raw['application_identifier']); + $thisfile_iso_primaryVD['copyright_file_identifier'] = trim($thisfile_iso_primaryVD_raw['copyright_file_identifier']); + $thisfile_iso_primaryVD['abstract_file_identifier'] = trim($thisfile_iso_primaryVD_raw['abstract_file_identifier']); + $thisfile_iso_primaryVD['bibliographic_file_identifier'] = trim($thisfile_iso_primaryVD_raw['bibliographic_file_identifier']); + $thisfile_iso_primaryVD['volume_creation_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_creation_date_time']); + $thisfile_iso_primaryVD['volume_modification_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_modification_date_time']); + $thisfile_iso_primaryVD['volume_expiration_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_expiration_date_time']); + $thisfile_iso_primaryVD['volume_effective_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_effective_date_time']); + + if (($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048) > $ThisFileInfo['filesize']) { + $ThisFileInfo['error'][] = 'Volume Space Size ('.($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048).' bytes) is larger than the file size ('.$ThisFileInfo['filesize'].' bytes) (truncated file?)'; + } + + return true; + } + + + function ParseSupplementaryVolumeDescriptor(&$ISOheader, &$ThisFileInfo) { + // ISO integer values are stored Both-Endian format!! + // ie 12345 == 0x3039 is stored as $39 $30 $30 $39 in a 4-byte field + + // shortcuts + $ThisFileInfo['iso']['supplementary_volume_descriptor']['raw'] = array(); + $thisfile_iso_supplementaryVD = &$ThisFileInfo['iso']['supplementary_volume_descriptor']; + $thisfile_iso_supplementaryVD_raw = &$thisfile_iso_supplementaryVD['raw']; + + $thisfile_iso_supplementaryVD_raw['volume_descriptor_type'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 0, 1)); + $thisfile_iso_supplementaryVD_raw['standard_identifier'] = substr($ISOheader, 1, 5); + if ($thisfile_iso_supplementaryVD_raw['standard_identifier'] != 'CD001') { + $ThisFileInfo['error'][] = 'Expected "CD001" at offset ('.($thisfile_iso_supplementaryVD['offset'] + 1).'), found "'.$thisfile_iso_supplementaryVD_raw['standard_identifier'].'" instead'; + unset($ThisFileInfo['fileformat']); + unset($ThisFileInfo['iso']); + return false; + } + + $thisfile_iso_supplementaryVD_raw['volume_descriptor_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 6, 1)); + //$thisfile_iso_supplementaryVD_raw['unused_1'] = substr($ISOheader, 7, 1); + $thisfile_iso_supplementaryVD_raw['system_identifier'] = substr($ISOheader, 8, 32); + $thisfile_iso_supplementaryVD_raw['volume_identifier'] = substr($ISOheader, 40, 32); + //$thisfile_iso_supplementaryVD_raw['unused_2'] = substr($ISOheader, 72, 8); + $thisfile_iso_supplementaryVD_raw['volume_space_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 80, 4)); + if ($thisfile_iso_supplementaryVD_raw['volume_space_size'] == 0) { + // Supplementary Volume Descriptor not used + //unset($thisfile_iso_supplementaryVD); + //return false; + } + + //$thisfile_iso_supplementaryVD_raw['unused_3'] = substr($ISOheader, 88, 32); + $thisfile_iso_supplementaryVD_raw['volume_set_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 120, 2)); + $thisfile_iso_supplementaryVD_raw['volume_sequence_number'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 124, 2)); + $thisfile_iso_supplementaryVD_raw['logical_block_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 128, 2)); + $thisfile_iso_supplementaryVD_raw['path_table_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 132, 4)); + $thisfile_iso_supplementaryVD_raw['path_table_l_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 140, 2)); + $thisfile_iso_supplementaryVD_raw['path_table_l_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 144, 2)); + $thisfile_iso_supplementaryVD_raw['path_table_m_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 148, 2)); + $thisfile_iso_supplementaryVD_raw['path_table_m_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 152, 2)); + $thisfile_iso_supplementaryVD_raw['root_directory_record'] = substr($ISOheader, 156, 34); + $thisfile_iso_supplementaryVD_raw['volume_set_identifier'] = substr($ISOheader, 190, 128); + $thisfile_iso_supplementaryVD_raw['publisher_identifier'] = substr($ISOheader, 318, 128); + $thisfile_iso_supplementaryVD_raw['data_preparer_identifier'] = substr($ISOheader, 446, 128); + $thisfile_iso_supplementaryVD_raw['application_identifier'] = substr($ISOheader, 574, 128); + $thisfile_iso_supplementaryVD_raw['copyright_file_identifier'] = substr($ISOheader, 702, 37); + $thisfile_iso_supplementaryVD_raw['abstract_file_identifier'] = substr($ISOheader, 739, 37); + $thisfile_iso_supplementaryVD_raw['bibliographic_file_identifier'] = substr($ISOheader, 776, 37); + $thisfile_iso_supplementaryVD_raw['volume_creation_date_time'] = substr($ISOheader, 813, 17); + $thisfile_iso_supplementaryVD_raw['volume_modification_date_time'] = substr($ISOheader, 830, 17); + $thisfile_iso_supplementaryVD_raw['volume_expiration_date_time'] = substr($ISOheader, 847, 17); + $thisfile_iso_supplementaryVD_raw['volume_effective_date_time'] = substr($ISOheader, 864, 17); + $thisfile_iso_supplementaryVD_raw['file_structure_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 881, 1)); + //$thisfile_iso_supplementaryVD_raw['unused_4'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 882, 1)); + $thisfile_iso_supplementaryVD_raw['application_data'] = substr($ISOheader, 883, 512); + //$thisfile_iso_supplementaryVD_raw['unused_5'] = substr($ISOheader, 1395, 653); + + $thisfile_iso_supplementaryVD['system_identifier'] = trim($thisfile_iso_supplementaryVD_raw['system_identifier']); + $thisfile_iso_supplementaryVD['volume_identifier'] = trim($thisfile_iso_supplementaryVD_raw['volume_identifier']); + $thisfile_iso_supplementaryVD['volume_set_identifier'] = trim($thisfile_iso_supplementaryVD_raw['volume_set_identifier']); + $thisfile_iso_supplementaryVD['publisher_identifier'] = trim($thisfile_iso_supplementaryVD_raw['publisher_identifier']); + $thisfile_iso_supplementaryVD['data_preparer_identifier'] = trim($thisfile_iso_supplementaryVD_raw['data_preparer_identifier']); + $thisfile_iso_supplementaryVD['application_identifier'] = trim($thisfile_iso_supplementaryVD_raw['application_identifier']); + $thisfile_iso_supplementaryVD['copyright_file_identifier'] = trim($thisfile_iso_supplementaryVD_raw['copyright_file_identifier']); + $thisfile_iso_supplementaryVD['abstract_file_identifier'] = trim($thisfile_iso_supplementaryVD_raw['abstract_file_identifier']); + $thisfile_iso_supplementaryVD['bibliographic_file_identifier'] = trim($thisfile_iso_supplementaryVD_raw['bibliographic_file_identifier']); + $thisfile_iso_supplementaryVD['volume_creation_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_creation_date_time']); + $thisfile_iso_supplementaryVD['volume_modification_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_modification_date_time']); + $thisfile_iso_supplementaryVD['volume_expiration_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_expiration_date_time']); + $thisfile_iso_supplementaryVD['volume_effective_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_effective_date_time']); + + if (($thisfile_iso_supplementaryVD_raw['volume_space_size'] * $thisfile_iso_supplementaryVD_raw['logical_block_size']) > $ThisFileInfo['filesize']) { + $ThisFileInfo['error'][] = 'Volume Space Size ('.($thisfile_iso_supplementaryVD_raw['volume_space_size'] * $thisfile_iso_supplementaryVD_raw['logical_block_size']).' bytes) is larger than the file size ('.$ThisFileInfo['filesize'].' bytes) (truncated file?)'; + } + + return true; + } + + + function ParsePathTable($fd, &$ThisFileInfo) { + if (!isset($ThisFileInfo['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location']) && !isset($ThisFileInfo['iso']['primary_volume_descriptor']['raw']['path_table_l_location'])) { + return false; + } + if (isset($ThisFileInfo['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location'])) { + $PathTableLocation = $ThisFileInfo['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location']; + $PathTableSize = $ThisFileInfo['iso']['supplementary_volume_descriptor']['raw']['path_table_size']; + $TextEncoding = 'UTF-16BE'; // Big-Endian Unicode + } else { + $PathTableLocation = $ThisFileInfo['iso']['primary_volume_descriptor']['raw']['path_table_l_location']; + $PathTableSize = $ThisFileInfo['iso']['primary_volume_descriptor']['raw']['path_table_size']; + $TextEncoding = 'ISO-8859-1'; // Latin-1 + } + + if (($PathTableLocation * 2048) > $ThisFileInfo['filesize']) { + $ThisFileInfo['error'][] = 'Path Table Location specifies an offset ('.($PathTableLocation * 2048).') beyond the end-of-file ('.$ThisFileInfo['filesize'].')'; + return false; + } + + $ThisFileInfo['iso']['path_table']['offset'] = $PathTableLocation * 2048; + fseek($fd, $ThisFileInfo['iso']['path_table']['offset'], SEEK_SET); + $ThisFileInfo['iso']['path_table']['raw'] = fread($fd, $PathTableSize); + + $offset = 0; + $pathcounter = 1; + while ($offset < $PathTableSize) { + // shortcut + $ThisFileInfo['iso']['path_table']['directories'][$pathcounter] = array(); + $thisfile_iso_pathtable_directories_current = &$ThisFileInfo['iso']['path_table']['directories'][$pathcounter]; + + $thisfile_iso_pathtable_directories_current['length'] = getid3_lib::LittleEndian2Int(substr($ThisFileInfo['iso']['path_table']['raw'], $offset, 1)); + $offset += 1; + $thisfile_iso_pathtable_directories_current['extended_length'] = getid3_lib::LittleEndian2Int(substr($ThisFileInfo['iso']['path_table']['raw'], $offset, 1)); + $offset += 1; + $thisfile_iso_pathtable_directories_current['location_logical'] = getid3_lib::LittleEndian2Int(substr($ThisFileInfo['iso']['path_table']['raw'], $offset, 4)); + $offset += 4; + $thisfile_iso_pathtable_directories_current['parent_directory'] = getid3_lib::LittleEndian2Int(substr($ThisFileInfo['iso']['path_table']['raw'], $offset, 2)); + $offset += 2; + $thisfile_iso_pathtable_directories_current['name'] = substr($ThisFileInfo['iso']['path_table']['raw'], $offset, $thisfile_iso_pathtable_directories_current['length']); + $offset += $thisfile_iso_pathtable_directories_current['length'] + ($thisfile_iso_pathtable_directories_current['length'] % 2); + + $thisfile_iso_pathtable_directories_current['name_ascii'] = getid3_lib::iconv_fallback($TextEncoding, $ThisFileInfo['encoding'], $thisfile_iso_pathtable_directories_current['name']); + + $thisfile_iso_pathtable_directories_current['location_bytes'] = $thisfile_iso_pathtable_directories_current['location_logical'] * 2048; + if ($pathcounter == 1) { + $thisfile_iso_pathtable_directories_current['full_path'] = '/'; + } else { + $thisfile_iso_pathtable_directories_current['full_path'] = $ThisFileInfo['iso']['path_table']['directories'][$thisfile_iso_pathtable_directories_current['parent_directory']]['full_path'].$thisfile_iso_pathtable_directories_current['name_ascii'].'/'; + } + $FullPathArray[] = $thisfile_iso_pathtable_directories_current['full_path']; + + $pathcounter++; + } + + return true; + } + + + function ParseDirectoryRecord(&$fd, $directorydata, &$ThisFileInfo) { + if (isset($ThisFileInfo['iso']['supplementary_volume_descriptor'])) { + $TextEncoding = 'UTF-16BE'; // Big-Endian Unicode + } else { + $TextEncoding = 'ISO-8859-1'; // Latin-1 + } + + fseek($fd, $directorydata['location_bytes'], SEEK_SET); + $DirectoryRecordData = fread($fd, 1); + + while (ord($DirectoryRecordData{0}) > 33) { + + $DirectoryRecordData .= fread($fd, ord($DirectoryRecordData{0}) - 1); + + $ThisDirectoryRecord['raw']['length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 0, 1)); + $ThisDirectoryRecord['raw']['extended_attribute_length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 1, 1)); + $ThisDirectoryRecord['raw']['offset_logical'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 2, 4)); + $ThisDirectoryRecord['raw']['filesize'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 10, 4)); + $ThisDirectoryRecord['raw']['recording_date_time'] = substr($DirectoryRecordData, 18, 7); + $ThisDirectoryRecord['raw']['file_flags'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 25, 1)); + $ThisDirectoryRecord['raw']['file_unit_size'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 26, 1)); + $ThisDirectoryRecord['raw']['interleave_gap_size'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 27, 1)); + $ThisDirectoryRecord['raw']['volume_sequence_number'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 28, 2)); + $ThisDirectoryRecord['raw']['file_identifier_length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 32, 1)); + $ThisDirectoryRecord['raw']['file_identifier'] = substr($DirectoryRecordData, 33, $ThisDirectoryRecord['raw']['file_identifier_length']); + + $ThisDirectoryRecord['file_identifier_ascii'] = getid3_lib::iconv_fallback($TextEncoding, $ThisFileInfo['encoding'], $ThisDirectoryRecord['raw']['file_identifier']); + + $ThisDirectoryRecord['filesize'] = $ThisDirectoryRecord['raw']['filesize']; + $ThisDirectoryRecord['offset_bytes'] = $ThisDirectoryRecord['raw']['offset_logical'] * 2048; + $ThisDirectoryRecord['file_flags']['hidden'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x01); + $ThisDirectoryRecord['file_flags']['directory'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x02); + $ThisDirectoryRecord['file_flags']['associated'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x04); + $ThisDirectoryRecord['file_flags']['extended'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x08); + $ThisDirectoryRecord['file_flags']['permissions'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x10); + $ThisDirectoryRecord['file_flags']['multiple'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x80); + $ThisDirectoryRecord['recording_timestamp'] = $this->ISOtime2UNIXtime($ThisDirectoryRecord['raw']['recording_date_time']); + + if ($ThisDirectoryRecord['file_flags']['directory']) { + $ThisDirectoryRecord['filename'] = $directorydata['full_path']; + } else { + $ThisDirectoryRecord['filename'] = $directorydata['full_path'].$this->ISOstripFilenameVersion($ThisDirectoryRecord['file_identifier_ascii']); + $ThisFileInfo['iso']['files'] = getid3_lib::array_merge_clobber($ThisFileInfo['iso']['files'], getid3_lib::CreateDeepArray($ThisDirectoryRecord['filename'], '/', $ThisDirectoryRecord['filesize'])); + } + + $DirectoryRecord[] = $ThisDirectoryRecord; + $DirectoryRecordData = fread($fd, 1); + } + + return $DirectoryRecord; + } + + function ISOstripFilenameVersion($ISOfilename) { + // convert 'filename.ext;1' to 'filename.ext' + if (!strstr($ISOfilename, ';')) { + return $ISOfilename; + } else { + return substr($ISOfilename, 0, strpos($ISOfilename, ';')); + } + } + + function ISOtimeText2UNIXtime($ISOtime) { + + $UNIXyear = (int) substr($ISOtime, 0, 4); + $UNIXmonth = (int) substr($ISOtime, 4, 2); + $UNIXday = (int) substr($ISOtime, 6, 2); + $UNIXhour = (int) substr($ISOtime, 8, 2); + $UNIXminute = (int) substr($ISOtime, 10, 2); + $UNIXsecond = (int) substr($ISOtime, 12, 2); + + if (!$UNIXyear) { + return false; + } + return mktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear); + } + + function ISOtime2UNIXtime($ISOtime) { + // Represented by seven bytes: + // 1: Number of years since 1900 + // 2: Month of the year from 1 to 12 + // 3: Day of the Month from 1 to 31 + // 4: Hour of the day from 0 to 23 + // 5: Minute of the hour from 0 to 59 + // 6: second of the minute from 0 to 59 + // 7: Offset from Greenwich Mean Time in number of 15 minute intervals from -48 (West) to +52 (East) + + $UNIXyear = ord($ISOtime{0}) + 1900; + $UNIXmonth = ord($ISOtime{1}); + $UNIXday = ord($ISOtime{2}); + $UNIXhour = ord($ISOtime{3}); + $UNIXminute = ord($ISOtime{4}); + $UNIXsecond = ord($ISOtime{5}); + $GMToffset = $this->TwosCompliment2Decimal(ord($ISOtime{5})); + + return mktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear); + } + + function TwosCompliment2Decimal($BinaryValue) { + // http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html + // First check if the number is negative or positive by looking at the sign bit. + // If it is positive, simply convert it to decimal. + // If it is negative, make it positive by inverting the bits and adding one. + // Then, convert the result to decimal. + // The negative of this number is the value of the original binary. + + if ($BinaryValue & 0x80) { + + // negative number + return (0 - ((~$BinaryValue & 0xFF) + 1)); + } else { + // positive number + return $BinaryValue; + } + } + + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.tag.apetag.php b/modules/id3/getid3/module.tag.apetag.php new file mode 100644 index 00000000..6ea80109 --- /dev/null +++ b/modules/id3/getid3/module.tag.apetag.php @@ -0,0 +1,284 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.tag.apetag.php // +// module for analyzing APE tags // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + +class getid3_apetag +{ + + function getid3_apetag(&$fd, &$ThisFileInfo, $overrideendoffset=0) { + $id3v1tagsize = 128; + $apetagheadersize = 32; + $lyrics3tagsize = 10; + + if ($overrideendoffset == 0) { + + fseek($fd, 0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END); + $APEfooterID3v1 = fread($fd, $id3v1tagsize + $apetagheadersize + $lyrics3tagsize); + + //if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) { + if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') { + + // APE tag found before ID3v1 + $ThisFileInfo['ape']['tag_offset_end'] = $ThisFileInfo['filesize'] - $id3v1tagsize; + + //} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) { + } elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') { + + // APE tag found, no ID3v1 + $ThisFileInfo['ape']['tag_offset_end'] = $ThisFileInfo['filesize']; + + } + + } else { + + fseek($fd, $overrideendoffset - $apetagheadersize, SEEK_SET); + if (fread($fd, 8) == 'APETAGEX') { + $ThisFileInfo['ape']['tag_offset_end'] = $overrideendoffset; + } + + } + if (!isset($ThisFileInfo['ape']['tag_offset_end'])) { + + // APE tag not found + unset($ThisFileInfo['ape']); + return false; + + } + + // shortcut + $thisfile_ape = &$ThisFileInfo['ape']; + + fseek($fd, $thisfile_ape['tag_offset_end'] - $apetagheadersize, SEEK_SET); + $APEfooterData = fread($fd, 32); + if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) { + $ThisFileInfo['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end']; + return false; + } + + if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) { + fseek($fd, $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize, SEEK_SET); + $thisfile_ape['tag_offset_start'] = ftell($fd); + $APEtagData = fread($fd, $thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize); + } else { + $thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize']; + fseek($fd, $thisfile_ape['tag_offset_start'], SEEK_SET); + $APEtagData = fread($fd, $thisfile_ape['footer']['raw']['tagsize']); + } + $ThisFileInfo['avdataend'] = $thisfile_ape['tag_offset_start']; + + if (isset($ThisFileInfo['id3v1']['tag_offset_start']) && ($ThisFileInfo['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) { + $ThisFileInfo['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data'; + unset($ThisFileInfo['id3v1']); + foreach ($ThisFileInfo['warning'] as $key => $value) { + if ($value == 'Some ID3v1 fields do not use NULL characters for padding') { + unset($ThisFileInfo['warning'][$key]); + sort($ThisFileInfo['warning']); + break; + } + } + } + + $offset = 0; + if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) { + if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) { + $offset += $apetagheadersize; + } else { + $ThisFileInfo['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start']; + return false; + } + } + + // shortcut + $ThisFileInfo['replay_gain'] = array(); + $thisfile_replaygain = &$ThisFileInfo['replay_gain']; + + for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) { + $value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4)); + $offset += 4; + $item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4)); + $offset += 4; + if (strstr(substr($APEtagData, $offset), "\x00") === false) { + $ThisFileInfo['error'][] = 'Cannot find null-byte (0x00) seperator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset); + return false; + } + $ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset; + $item_key = strtolower(substr($APEtagData, $offset, $ItemKeyLength)); + + // shortcut + $thisfile_ape['items'][$item_key] = array(); + $thisfile_ape_items_current = &$thisfile_ape['items'][$item_key]; + + $offset += ($ItemKeyLength + 1); // skip 0x00 terminator + $thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size); + $offset += $value_size; + + $thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags); + switch ($thisfile_ape_items_current['flags']['item_contents_raw']) { + case 0: // UTF-8 + case 3: // Locator (URL, filename, etc), UTF-8 encoded + $thisfile_ape_items_current['data'] = explode("\x00", trim($thisfile_ape_items_current['data'])); + break; + + default: // binary data + break; + } + + switch (strtolower($item_key)) { + case 'replaygain_track_gain': + $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + $thisfile_replaygain['track']['originator'] = 'unspecified'; + break; + + case 'replaygain_track_peak': + $thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + $thisfile_replaygain['track']['originator'] = 'unspecified'; + if ($thisfile_replaygain['track']['peak'] <= 0) { + $ThisFileInfo['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + } + break; + + case 'replaygain_album_gain': + $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + $thisfile_replaygain['album']['originator'] = 'unspecified'; + break; + + case 'replaygain_album_peak': + $thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero! + $thisfile_replaygain['album']['originator'] = 'unspecified'; + if ($thisfile_replaygain['album']['peak'] <= 0) { + $ThisFileInfo['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")'; + } + break; + + case 'mp3gain_undo': + list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]); + $thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left); + $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right); + $thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false); + break; + + case 'mp3gain_minmax': + list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]); + $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min); + $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max); + break; + + case 'mp3gain_album_minmax': + list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]); + $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min); + $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max); + break; + + case 'tracknumber': + foreach ($thisfile_ape_items_current['data'] as $comment) { + $thisfile_ape['comments']['track'][] = $comment; + } + break; + + default: + foreach ($thisfile_ape_items_current['data'] as $comment) { + $thisfile_ape['comments'][strtolower($item_key)][] = $comment; + } + break; + } + + } + if (empty($thisfile_replaygain)) { + unset($ThisFileInfo['replay_gain']); + } + + return true; + } + + function parseAPEheaderFooter($APEheaderFooterData) { + // http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html + + // shortcut + $headerfooterinfo['raw'] = array(); + $headerfooterinfo_raw = &$headerfooterinfo['raw']; + + $headerfooterinfo_raw['footer_tag'] = substr($APEheaderFooterData, 0, 8); + if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') { + return false; + } + $headerfooterinfo_raw['version'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 8, 4)); + $headerfooterinfo_raw['tagsize'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4)); + $headerfooterinfo_raw['tag_items'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4)); + $headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4)); + $headerfooterinfo_raw['reserved'] = substr($APEheaderFooterData, 24, 8); + + $headerfooterinfo['tag_version'] = $headerfooterinfo_raw['version'] / 1000; + if ($headerfooterinfo['tag_version'] >= 2) { + $headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']); + } + return $headerfooterinfo; + } + + function parseAPEtagFlags($rawflagint) { + // "Note: APE Tags 1.0 do not use any of the APE Tag flags. + // All are set to zero on creation and ignored on reading." + // http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html + $flags['header'] = (bool) ($rawflagint & 0x80000000); + $flags['footer'] = (bool) ($rawflagint & 0x40000000); + $flags['this_is_header'] = (bool) ($rawflagint & 0x20000000); + $flags['item_contents_raw'] = ($rawflagint & 0x00000006) >> 1; + $flags['read_only'] = (bool) ($rawflagint & 0x00000001); + + $flags['item_contents'] = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']); + + return $flags; + } + + function APEcontentTypeFlagLookup($contenttypeid) { + static $APEcontentTypeFlagLookup = array( + 0 => 'utf-8', + 1 => 'binary', + 2 => 'external', + 3 => 'reserved' + ); + return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid'); + } + + function APEtagItemIsUTF8Lookup($itemkey) { + static $APEtagItemIsUTF8Lookup = array( + 'title', + 'subtitle', + 'artist', + 'album', + 'debut album', + 'publisher', + 'conductor', + 'track', + 'composer', + 'comment', + 'copyright', + 'publicationright', + 'file', + 'year', + 'record date', + 'record location', + 'genre', + 'media', + 'related', + 'isrc', + 'abstract', + 'language', + 'bibliography' + ); + return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup); + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.tag.id3v1.php b/modules/id3/getid3/module.tag.id3v1.php new file mode 100644 index 00000000..dd9b47b1 --- /dev/null +++ b/modules/id3/getid3/module.tag.id3v1.php @@ -0,0 +1,356 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// module.tag.id3v1.php // +// module for analyzing ID3v1 tags // +// dependencies: NONE // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_id3v1 +{ + + function getid3_id3v1(&$fd, &$ThisFileInfo) { + + fseek($fd, -256, SEEK_END); + $preid3v1 = fread($fd, 128); + $id3v1tag = fread($fd, 128); + + if (substr($id3v1tag, 0, 3) == 'TAG') { + + $ThisFileInfo['avdataend'] = $ThisFileInfo['filesize'] - 128; + + $ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30)); + $ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30)); + $ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30)); + $ParsedID3v1['year'] = $this->cutfield(substr($id3v1tag, 93, 4)); + $ParsedID3v1['comment'] = substr($id3v1tag, 97, 30); // can't remove nulls yet, track detection depends on them + $ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1)); + + // If second-last byte of comment field is null and last byte of comment field is non-null + // then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number + if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) { + $ParsedID3v1['track'] = ord(substr($ParsedID3v1['comment'], 29, 1)); + $ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28); + } + $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']); + + $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']); + if (!empty($ParsedID3v1['genre'])) { + unset($ParsedID3v1['genreid']); + } + if (empty($ParsedID3v1['genre']) || (@$ParsedID3v1['genre'] == 'Unknown')) { + unset($ParsedID3v1['genre']); + } + + foreach ($ParsedID3v1 as $key => $value) { + $ParsedID3v1['comments'][$key][0] = $value; + } + + // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces + $GoodFormatID3v1tag = $this->GenerateID3v1Tag( + $ParsedID3v1['title'], + $ParsedID3v1['artist'], + $ParsedID3v1['album'], + $ParsedID3v1['year'], + $this->LookupGenreID(@$ParsedID3v1['genre']), + $ParsedID3v1['comment'], + @$ParsedID3v1['track']); + $ParsedID3v1['padding_valid'] = true; + if ($id3v1tag !== $GoodFormatID3v1tag) { + $ParsedID3v1['padding_valid'] = false; + $ThisFileInfo['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding'; + } + + $ParsedID3v1['tag_offset_end'] = $ThisFileInfo['filesize']; + $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128; + + $ThisFileInfo['id3v1'] = $ParsedID3v1; + } + + if (substr($preid3v1, 0, 3) == 'TAG') { + // The way iTunes handles tags is, well, brain-damaged. + // It completely ignores v1 if ID3v2 is present. + // This goes as far as adding a new v1 tag *even if there already is one* + + // A suspected double-ID3v1 tag has been detected, but it could be that + // the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag + if (substr($preid3v1, 96, 8) == 'APETAGEX') { + // an APE tag footer was found before the last ID3v1, assume false "TAG" synch + } elseif (substr($preid3v1, 119, 6) == 'LYRICS') { + // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch + } else { + // APE and Lyrics3 footers not found - assume double ID3v1 + $ThisFileInfo['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes'; + $ThisFileInfo['avdataend'] -= 128; + } + } + + return true; + } + + function cutfield($str) { + return trim(substr($str, 0, strcspn($str, "\x00"))); + } + + function ArrayOfGenres($allowSCMPXextended=false) { + static $GenreLookup = array( + 0 => 'Blues', + 1 => 'Classic Rock', + 2 => 'Country', + 3 => 'Dance', + 4 => 'Disco', + 5 => 'Funk', + 6 => 'Grunge', + 7 => 'Hip-Hop', + 8 => 'Jazz', + 9 => 'Metal', + 10 => 'New Age', + 11 => 'Oldies', + 12 => 'Other', + 13 => 'Pop', + 14 => 'R&B', + 15 => 'Rap', + 16 => 'Reggae', + 17 => 'Rock', + 18 => 'Techno', + 19 => 'Industrial', + 20 => 'Alternative', + 21 => 'Ska', + 22 => 'Death Metal', + 23 => 'Pranks', + 24 => 'Soundtrack', + 25 => 'Euro-Techno', + 26 => 'Ambient', + 27 => 'Trip-Hop', + 28 => 'Vocal', + 29 => 'Jazz+Funk', + 30 => 'Fusion', + 31 => 'Trance', + 32 => 'Classical', + 33 => 'Instrumental', + 34 => 'Acid', + 35 => 'House', + 36 => 'Game', + 37 => 'Sound Clip', + 38 => 'Gospel', + 39 => 'Noise', + 40 => 'Alt. Rock', + 41 => 'Bass', + 42 => 'Soul', + 43 => 'Punk', + 44 => 'Space', + 45 => 'Meditative', + 46 => 'Instrumental Pop', + 47 => 'Instrumental Rock', + 48 => 'Ethnic', + 49 => 'Gothic', + 50 => 'Darkwave', + 51 => 'Techno-Industrial', + 52 => 'Electronic', + 53 => 'Pop-Folk', + 54 => 'Eurodance', + 55 => 'Dream', + 56 => 'Southern Rock', + 57 => 'Comedy', + 58 => 'Cult', + 59 => 'Gangsta Rap', + 60 => 'Top 40', + 61 => 'Christian Rap', + 62 => 'Pop/Funk', + 63 => 'Jungle', + 64 => 'Native American', + 65 => 'Cabaret', + 66 => 'New Wave', + 67 => 'Psychedelic', + 68 => 'Rave', + 69 => 'Showtunes', + 70 => 'Trailer', + 71 => 'Lo-Fi', + 72 => 'Tribal', + 73 => 'Acid Punk', + 74 => 'Acid Jazz', + 75 => 'Polka', + 76 => 'Retro', + 77 => 'Musical', + 78 => 'Rock & Roll', + 79 => 'Hard Rock', + 80 => 'Folk', + 81 => 'Folk/Rock', + 82 => 'National Folk', + 83 => 'Swing', + 84 => 'Fast-Fusion', + 85 => 'Bebob', + 86 => 'Latin', + 87 => 'Revival', + 88 => 'Celtic', + 89 => 'Bluegrass', + 90 => 'Avantgarde', + 91 => 'Gothic Rock', + 92 => 'Progressive Rock', + 93 => 'Psychedelic Rock', + 94 => 'Symphonic Rock', + 95 => 'Slow Rock', + 96 => 'Big Band', + 97 => 'Chorus', + 98 => 'Easy Listening', + 99 => 'Acoustic', + 100 => 'Humour', + 101 => 'Speech', + 102 => 'Chanson', + 103 => 'Opera', + 104 => 'Chamber Music', + 105 => 'Sonata', + 106 => 'Symphony', + 107 => 'Booty Bass', + 108 => 'Primus', + 109 => 'Porn Groove', + 110 => 'Satire', + 111 => 'Slow Jam', + 112 => 'Club', + 113 => 'Tango', + 114 => 'Samba', + 115 => 'Folklore', + 116 => 'Ballad', + 117 => 'Power Ballad', + 118 => 'Rhythmic Soul', + 119 => 'Freestyle', + 120 => 'Duet', + 121 => 'Punk Rock', + 122 => 'Drum Solo', + 123 => 'A Cappella', + 124 => 'Euro-House', + 125 => 'Dance Hall', + 126 => 'Goa', + 127 => 'Drum & Bass', + 128 => 'Club-House', + 129 => 'Hardcore', + 130 => 'Terror', + 131 => 'Indie', + 132 => 'BritPop', + 133 => 'Negerpunk', + 134 => 'Polsk Punk', + 135 => 'Beat', + 136 => 'Christian Gangsta Rap', + 137 => 'Heavy Metal', + 138 => 'Black Metal', + 139 => 'Crossover', + 140 => 'Contemporary Christian', + 141 => 'Christian Rock', + 142 => 'Merengue', + 143 => 'Salsa', + 144 => 'Trash Metal', + 145 => 'Anime', + 146 => 'JPop', + 147 => 'Synthpop', + + 255 => 'Unknown', + + 'CR' => 'Cover', + 'RX' => 'Remix' + ); + + static $GenreLookupSCMPX = array(); + if ($allowSCMPXextended && empty($GenreLookupSCMPX)) { + $GenreLookupSCMPX = $GenreLookup; + // http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended + // Extended ID3v1 genres invented by SCMPX + // Note that 255 "Japanese Anime" conflicts with standard "Unknown" + $GenreLookupSCMPX[240] = 'Sacred'; + $GenreLookupSCMPX[241] = 'Northern Europe'; + $GenreLookupSCMPX[242] = 'Irish & Scottish'; + $GenreLookupSCMPX[243] = 'Scotland'; + $GenreLookupSCMPX[244] = 'Ethnic Europe'; + $GenreLookupSCMPX[245] = 'Enka'; + $GenreLookupSCMPX[246] = 'Children\'s Song'; + $GenreLookupSCMPX[247] = 'Japanese Sky'; + $GenreLookupSCMPX[248] = 'Japanese Heavy Rock'; + $GenreLookupSCMPX[249] = 'Japanese Doom Rock'; + $GenreLookupSCMPX[250] = 'Japanese J-POP'; + $GenreLookupSCMPX[251] = 'Japanese Seiyu'; + $GenreLookupSCMPX[252] = 'Japanese Ambient Techno'; + $GenreLookupSCMPX[253] = 'Japanese Moemoe'; + $GenreLookupSCMPX[254] = 'Japanese Tokusatsu'; + //$GenreLookupSCMPX[255] = 'Japanese Anime'; + } + + return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup); + } + + function LookupGenreName($genreid, $allowSCMPXextended=true) { + switch ($genreid) { + case 'RX': + case 'CR': + break; + default: + $genreid = intval($genreid); // to handle 3 or '3' or '03' + break; + } + $GenreLookup = getid3_id3v1::ArrayOfGenres($allowSCMPXextended); + return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false); + } + + function LookupGenreID($genre, $allowSCMPXextended=false) { + $GenreLookup = getid3_id3v1::ArrayOfGenres($allowSCMPXextended); + $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre)); + foreach ($GenreLookup as $key => $value) { + foreach ($GenreLookup as $key => $value) { + if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) { + return $key; + } + } + return false; + } + return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false); + } + + function StandardiseID3v1GenreName($OriginalGenre) { + if (($GenreID = getid3_id3v1::LookupGenreID($OriginalGenre)) !== false) { + return getid3_id3v1::LookupGenreName($GenreID); + } + return $OriginalGenre; + } + + function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') { + $ID3v1Tag = 'TAG'; + $ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT); + $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT); + $ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT); + $ID3v1Tag .= str_pad(trim(substr($year, 0, 4)), 4, "\x00", STR_PAD_LEFT); + if (!empty($track) && ($track > 0) && ($track <= 255)) { + $ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT); + $ID3v1Tag .= "\x00"; + if (gettype($track) == 'string') { + $track = (int) $track; + } + $ID3v1Tag .= chr($track); + } else { + $ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT); + } + if (($genreid < 0) || ($genreid > 147)) { + $genreid = 255; // 'unknown' genre + } + switch (gettype($genreid)) { + case 'string': + case 'integer': + $ID3v1Tag .= chr(intval($genreid)); + break; + default: + $ID3v1Tag .= chr(255); // 'unknown' genre + break; + } + + return $ID3v1Tag; + } + +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/module.tag.id3v2.php b/modules/id3/getid3/module.tag.id3v2.php new file mode 100644 index 00000000..c855bf21 --- /dev/null +++ b/modules/id3/getid3/module.tag.id3v2.php @@ -0,0 +1,3040 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +/// // +// module.tag.id3v2.php // +// module for analyzing ID3v2 tags // +// dependencies: module.tag.id3v1.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true); + +class getid3_id3v2 +{ + + function getid3_id3v2(&$fd, &$ThisFileInfo, $StartingOffset=0) { + // Overall tag structure: + // +-----------------------------+ + // | Header (10 bytes) | + // +-----------------------------+ + // | Extended Header | + // | (variable length, OPTIONAL) | + // +-----------------------------+ + // | Frames (variable length) | + // +-----------------------------+ + // | Padding | + // | (variable length, OPTIONAL) | + // +-----------------------------+ + // | Footer (10 bytes, OPTIONAL) | + // +-----------------------------+ + + // Header + // ID3v2/file identifier "ID3" + // ID3v2 version $04 00 + // ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x) + // ID3v2 size 4 * %0xxxxxxx + + + // shortcuts + $ThisFileInfo['id3v2']['header'] = true; + $thisfile_id3v2 = &$ThisFileInfo['id3v2']; + $thisfile_id3v2['flags'] = array(); + $thisfile_id3v2_flags = &$thisfile_id3v2['flags']; + + + fseek($fd, $StartingOffset, SEEK_SET); + $header = fread($fd, 10); + if (substr($header, 0, 3) == 'ID3') { + + $thisfile_id3v2['majorversion'] = ord($header{3}); + $thisfile_id3v2['minorversion'] = ord($header{4}); + + // shortcut + $id3v2_majorversion = &$thisfile_id3v2['majorversion']; + + } else { + + unset($ThisFileInfo['id3v2']); + return false; + + } + + if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists) + + $ThisFileInfo['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']; + return false; + + } + + $id3_flags = ord($header{5}); + switch ($id3v2_majorversion) { + case 2: + // %ab000000 in v2.2 + $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation + $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression + break; + + case 3: + // %abc00000 in v2.3 + $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation + $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header + $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator + break; + + case 4: + // %abcd0000 in v2.4 + $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation + $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header + $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator + $thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present + break; + } + + $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length + + $thisfile_id3v2['tag_offset_start'] = $StartingOffset; + $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength']; + + // Extended Header + if (isset($thisfile_id3v2_flags['exthead']) && $thisfile_id3v2_flags['exthead']) { + // Extended header size 4 * %0xxxxxxx + // Number of flag bytes $01 + // Extended Flags $xx + // Where the 'Extended header size' is the size of the whole extended header, stored as a 32 bit synchsafe integer. + $extheader = fread ($fd, 4); + $thisfile_id3v2['extheaderlength'] = getid3_lib::BigEndian2Int($extheader, 1); + + // The extended flags field, with its size described by 'number of flag bytes', is defined as: + // %0bcd0000 + // b - Tag is an update + // Flag data length $00 + // c - CRC data present + // Flag data length $05 + // Total frame CRC 5 * %0xxxxxxx + // d - Tag restrictions + // Flag data length $01 + $extheaderflagbytes = fread ($fd, 1); + $extheaderflags = fread ($fd, $extheaderflagbytes); + $id3_exthead_flags = getid3_lib::BigEndian2Bin(substr($header, 5, 1)); + $thisfile_id3v2['exthead_flags']['update'] = substr($id3_exthead_flags, 1, 1); + $thisfile_id3v2['exthead_flags']['CRC'] = substr($id3_exthead_flags, 2, 1); + if ($thisfile_id3v2['exthead_flags']['CRC']) { + $extheaderrawCRC = fread ($fd, 5); + $thisfile_id3v2['exthead_flags']['CRC'] = getid3_lib::BigEndian2Int($extheaderrawCRC, 1); + } + $thisfile_id3v2['exthead_flags']['restrictions'] = substr($id3_exthead_flags, 3, 1); + if ($thisfile_id3v2['exthead_flags']['restrictions']) { + // Restrictions %ppqrrstt + $extheaderrawrestrictions = fread ($fd, 1); + $thisfile_id3v2['exthead_flags']['restrictions_tagsize'] = (bindec('11000000') & ord($extheaderrawrestrictions)) >> 6; // p - Tag size restrictions + $thisfile_id3v2['exthead_flags']['restrictions_textenc'] = (bindec('00100000') & ord($extheaderrawrestrictions)) >> 5; // q - Text encoding restrictions + $thisfile_id3v2['exthead_flags']['restrictions_textsize'] = (bindec('00011000') & ord($extheaderrawrestrictions)) >> 3; // r - Text fields size restrictions + $thisfile_id3v2['exthead_flags']['restrictions_imgenc'] = (bindec('00000100') & ord($extheaderrawrestrictions)) >> 2; // s - Image encoding restrictions + $thisfile_id3v2['exthead_flags']['restrictions_imgsize'] = (bindec('00000011') & ord($extheaderrawrestrictions)) >> 0; // t - Image size restrictions + } + } // end extended header + + + + // create 'encoding' key - used by getid3::HandleAllTags() + // in ID3v2 every field can have it's own encoding type + // so force everything to UTF-8 so it can be handled consistantly + $thisfile_id3v2['encoding'] = 'UTF-8'; + + + // Frames + + // All ID3v2 frames consists of one frame header followed by one or more + // fields containing the actual information. The header is always 10 + // bytes and laid out as follows: + // + // Frame ID $xx xx xx xx (four characters) + // Size 4 * %0xxxxxxx + // Flags $xx xx + + $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header + if (isset($thisfile_id3v2['extheaderlength'])) { + $sizeofframes -= $thisfile_id3v2['extheaderlength']; + } + if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) { + $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio + } + if ($sizeofframes > 0) { + + $framedata = fread($fd, $sizeofframes); // read all frames from file into $framedata variable + + // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x) + if (isset($thisfile_id3v2_flags['unsynch']) && $thisfile_id3v2_flags['unsynch'] && ($id3v2_majorversion <= 3)) { + $framedata = $this->DeUnsynchronise($framedata); + } + // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead + // of on tag level, making it easier to skip frames, increasing the streamability + // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that + // there exists an unsynchronised frame, while the new unsynchronisation flag in + // the frame header [S:4.1.2] indicates unsynchronisation. + + $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header + while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse + if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) { + // insufficient room left in ID3v2 header for actual data - must be padding + $thisfile_id3v2['padding']['start'] = $framedataoffset; + $thisfile_id3v2['padding']['length'] = strlen($framedata); + $thisfile_id3v2['padding']['valid'] = true; + for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) { + if ($framedata{$i} != "\x00") { + $thisfile_id3v2['padding']['valid'] = false; + $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; + $ThisFileInfo['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; + break; + } + } + break; // skip rest of ID3v2 header + } + if ($id3v2_majorversion == 2) { + // Frame ID $xx xx xx (three characters) + // Size $xx xx xx (24-bit integer) + // Flags $xx xx + + $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header + $framedata = substr($framedata, 6); // and leave the rest in $framedata + $frame_name = substr($frame_header, 0, 3); + $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0); + $frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs + + } elseif ($id3v2_majorversion > 2) { + + // Frame ID $xx xx xx xx (four characters) + // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+) + // Flags $xx xx + + $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header + $framedata = substr($framedata, 10); // and leave the rest in $framedata + + $frame_name = substr($frame_header, 0, 4); + if ($id3v2_majorversion == 3) { + $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer + } else { // ID3v2.4+ + $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value) + } + + if ($frame_size < (strlen($framedata) + 4)) { + $nextFrameID = substr($framedata, $frame_size, 4); + if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) { + // next frame is OK + } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) { + // MP3ext known broken frames - "ok" for the purposes of this test + } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) { + $ThisFileInfo['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of Helium2 (www.helium2.com) is a known culprit of this. Tag has been parsed as ID3v2.3'; + $id3v2_majorversion = 3; + $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer + } + } + + + $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2)); + } + + if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) { + // padding encountered + + $thisfile_id3v2['padding']['start'] = $framedataoffset; + $thisfile_id3v2['padding']['length'] = strlen($framedata); + $thisfile_id3v2['padding']['valid'] = true; + for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) { + if ($framedata{$i} != "\x00") { + $thisfile_id3v2['padding']['valid'] = false; + $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i; + $ThisFileInfo['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)'; + break; + } + } + break; // skip rest of ID3v2 header + } + + if ($frame_name == 'COM ') { + $ThisFileInfo['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]'; + $frame_name = 'COMM'; + } + if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) { + + unset($parsedFrame); + $parsedFrame['frame_name'] = $frame_name; + $parsedFrame['frame_flags_raw'] = $frame_flags; + $parsedFrame['data'] = substr($framedata, 0, $frame_size); + $parsedFrame['datalength'] = getid3_lib::CastAsInt($frame_size); + $parsedFrame['dataoffset'] = $framedataoffset; + + $this->ParseID3v2Frame($parsedFrame, $ThisFileInfo); + $thisfile_id3v2[$frame_name][] = $parsedFrame; + + $framedata = substr($framedata, $frame_size); + + } else { // invalid frame length or FrameID + + if ($frame_size <= strlen($framedata)) { + + if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) { + + // next frame is valid, just skip the current frame + $framedata = substr($framedata, $frame_size); + $ThisFileInfo['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.'; + + } else { + + // next frame is invalid too, abort processing + unset($framedata); + $ThisFileInfo['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.'; + + } + + } elseif ($frame_size == strlen($framedata)) { + + // this is the last frame, just skip + $ThisFileInfo['warning'][] = 'This was the last ID3v2 frame.'; + + } else { + + // next frame is invalid too, abort processing + unset($framedata); + $ThisFileInfo['warning'][] = 'Invalid ID3v2 frame size, aborting.'; + + } + if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) { + + switch ($frame_name) { + case "\x00\x00".'MP': + case "\x00".'MP3': + case ' MP3': + case 'MP3e': + case "\x00".'MP': + case ' MP': + case 'MP3': + $ThisFileInfo['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]'; + break; + + default: + $ThisFileInfo['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).'; + break; + } + + } elseif ($frame_size > strlen($framedata)){ + + $ThisFileInfo['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.strlen($framedata).')).'; + + } else { + + $ThisFileInfo['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).'; + + } + + } + $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion)); + + } + + } + + + // Footer + + // The footer is a copy of the header, but with a different identifier. + // ID3v2 identifier "3DI" + // ID3v2 version $04 00 + // ID3v2 flags %abcd0000 + // ID3v2 size 4 * %0xxxxxxx + + if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) { + $footer = fread ($fd, 10); + if (substr($footer, 0, 3) == '3DI') { + $thisfile_id3v2['footer'] = true; + $thisfile_id3v2['majorversion_footer'] = ord($footer{3}); + $thisfile_id3v2['minorversion_footer'] = ord($footer{4}); + } + if ($thisfile_id3v2['majorversion_footer'] <= 4) { + $id3_flags = ord(substr($footer{5})); + $thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80); + $thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40); + $thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20); + $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10); + + $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1); + } + } // end footer + + if (isset($thisfile_id3v2['comments']['genre'])) { + foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) { + unset($thisfile_id3v2['comments']['genre'][$key]); + $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], $this->ParseID3v2GenreString($value)); + } + } + + if (isset($thisfile_id3v2['comments']['track'])) { + foreach ($thisfile_id3v2['comments']['track'] as $key => $value) { + if (strstr($value, '/')) { + list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]); + } + } + } + + + // Set avdataoffset + $ThisFileInfo['avdataoffset'] = $thisfile_id3v2['headerlength']; + if (isset($thisfile_id3v2['footer'])) { + $ThisFileInfo['avdataoffset'] += 10; + } + + return true; + } + + + function ParseID3v2GenreString($genrestring) { + // Parse genres into arrays of genreName and genreID + // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)' + // ID3v2.4.x: '21' $00 'Eurodisco' $00 + + $genrestring = trim($genrestring); + $returnarray = array(); + if (strpos($genrestring, "\x00") !== false) { + $unprocessed = trim($genrestring); // trailing nulls will cause an infinite loop. + $genrestring = ''; + while (strpos($unprocessed, "\x00") !== false) { + // convert null-seperated v2.4-format into v2.3 ()-seperated format + $endpos = strpos($unprocessed, "\x00"); + $genrestring .= '('.substr($unprocessed, 0, $endpos).')'; + $unprocessed = substr($unprocessed, $endpos + 1); + } + unset($unprocessed); + } + if (getid3_id3v1::LookupGenreID($genrestring)) { + + $returnarray['genre'][] = $genrestring; + + } else { + + while (strpos($genrestring, '(') !== false) { + + $startpos = strpos($genrestring, '('); + $endpos = strpos($genrestring, ')'); + if (substr($genrestring, $startpos + 1, 1) == '(') { + $genrestring = substr($genrestring, 0, $startpos).substr($genrestring, $startpos + 1); + $endpos--; + } + $element = substr($genrestring, $startpos + 1, $endpos - ($startpos + 1)); + $genrestring = substr($genrestring, 0, $startpos).substr($genrestring, $endpos + 1); + if (getid3_id3v1::LookupGenreName($element)) { // $element is a valid genre id/abbreviation + + if (empty($returnarray['genre']) || !in_array(getid3_id3v1::LookupGenreName($element), $returnarray['genre'])) { // avoid duplicate entires + $returnarray['genre'][] = getid3_id3v1::LookupGenreName($element); + } + + } else { + + if (empty($returnarray['genre']) || !in_array($element, $returnarray['genre'])) { // avoid duplicate entires + $returnarray['genre'][] = $element; + } + + } + } + } + if ($genrestring) { + if (empty($returnarray['genre']) || !in_array($genrestring, $returnarray['genre'])) { // avoid duplicate entires + $returnarray['genre'][] = $genrestring; + } + } + + return $returnarray; + } + + + function ParseID3v2Frame(&$parsedFrame, &$ThisFileInfo) { + + // shortcuts + $id3v2_majorversion = $ThisFileInfo['id3v2']['majorversion']; + + $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']); + if (empty($parsedFrame['framenamelong'])) { + unset($parsedFrame['framenamelong']); + } + $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']); + if (empty($parsedFrame['framenameshort'])) { + unset($parsedFrame['framenameshort']); + } + + if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard + if ($id3v2_majorversion == 3) { + // Frame Header Flags + // %abc00000 %ijk00000 + $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation + $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation + $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only + $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression + $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption + $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity + + } elseif ($id3v2_majorversion == 4) { + // Frame Header Flags + // %0abc0000 %0h00kmnp + $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation + $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation + $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only + $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity + $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression + $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption + $parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation + $parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator + + // Frame-level de-unsynchronisation - ID3v2.4 + if ($parsedFrame['flags']['Unsynchronisation']) { + $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']); + } + } + + // Frame-level de-compression + if ($parsedFrame['flags']['compression']) { + $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4)); + if (!function_exists('gzuncompress')) { + $ThisFileInfo['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"'; + } elseif ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { + $parsedFrame['data'] = $decompresseddata; + } else { + $ThisFileInfo['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"'; + } + } + } + + if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) { + + $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion'; + switch ($parsedFrame['frame_name']) { + case 'WCOM': + $warning .= ' (this is known to happen with files tagged by RioPort)'; + break; + + default: + break; + } + $ThisFileInfo['warning'][] = $warning; + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier + // There may be more than one 'UFID' frame in a tag, + // but only one with the same 'Owner identifier'. + // <Header for 'Unique file identifier', ID: 'UFID'> + // Owner identifier <text string> $00 + // Identifier <up to 64 bytes binary data> + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00"); + $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos); + $parsedFrame['ownerid'] = $frame_idstring; + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame + // There may be more than one 'TXXX' frame in each tag, + // but only one with the same description. + // <Header for 'User defined text information frame', ID: 'TXXX'> + // Text encoding $xx + // Description <text string according to encoding> $00 (00) + // Value <text string according to encoding> + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['description'] = $frame_description; + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data'])); + } + unset($parsedFrame['data']); + + + } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame + // There may only be one text information frame of its kind in an tag. + // <Header for 'Text information frame', ID: 'T000' - 'TZZZ', + // excluding 'TXXX' described in 4.2.6.> + // Text encoding $xx + // Information <text string(s) according to encoding> + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); + } + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) { // 4.3.2 WXX User defined URL link frame + // There may be more than one 'WXXX' frame in each tag, + // but only one with the same description + // <Header for 'User defined URL link frame', ID: 'WXXX'> + // Text encoding $xx + // Description <text string according to encoding> $00 (00) + // URL <text string> + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding)); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + if ($frame_terminatorpos) { + // there are null bytes after the data - this is not according to spec + // only use data up to first null byte + $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos); + } else { + // no null bytes following data, just use all data + $frame_urldata = (string) $parsedFrame['data']; + } + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['url'] = $frame_urldata; + $parsedFrame['description'] = $frame_description; + if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { + $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['url']); + } + unset($parsedFrame['data']); + + + } elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames + // There may only be one URL link frame of its kind in a tag, + // except when stated otherwise in the frame description + // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX' + // described in 4.3.2.> + // URL <text string> + + $parsedFrame['url'] = trim($parsedFrame['data']); + if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { + $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url']; + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only) + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only) + // There may only be one 'IPL' frame in each tag + // <Header for 'User defined URL link frame', ID: 'IPL'> + // Text encoding $xx + // People list strings <textstrings> + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']); + + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); + } + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier + // There may only be one 'MCDI' frame in each tag + // <Header for 'Music CD identifier', ID: 'MCDI'> + // CD TOC <binary data> + + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; + } + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5 ETCO Event timing codes + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes + // There may only be one 'ETCO' frame in each tag + // <Header for 'Event timing codes', ID: 'ETCO'> + // Time stamp format $xx + // Where time stamp format is: + // $01 (32-bit value) MPEG frames from beginning of file + // $02 (32-bit value) milliseconds from beginning of file + // Followed by a list of key events in the following format: + // Type of event $xx + // Time stamp $xx (xx ...) + // The 'Time stamp' is set to zero if directly at the beginning of the sound + // or after the previous event. All events MUST be sorted in chronological order. + + $frame_offset = 0; + $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + + while ($frame_offset < strlen($parsedFrame['data'])) { + $parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++, 1); + $parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']); + $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6 MLLT MPEG location lookup table + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table + // There may only be one 'MLLT' frame in each tag + // <Header for 'Location lookup table', ID: 'MLLT'> + // MPEG frames between reference $xx xx + // Bytes between reference $xx xx xx + // Milliseconds between reference $xx xx xx + // Bits for bytes deviation $xx + // Bits for milliseconds dev. $xx + // Then for every reference the following data is included; + // Deviation in bytes %xxx.... + // Deviation in milliseconds %xxx.... + + $frame_offset = 0; + $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2)); + $parsedFrame['bytesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3)); + $parsedFrame['msbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3)); + $parsedFrame['bitsforbytesdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1)); + $parsedFrame['bitsformsdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1)); + $parsedFrame['data'] = substr($parsedFrame['data'], 10); + while ($frame_offset < strlen($parsedFrame['data'])) { + $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); + } + $reference_counter = 0; + while (strlen($deviationbitstream) > 0) { + $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation'])); + $parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation'])); + $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']); + $reference_counter++; + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes + // There may only be one 'SYTC' frame in each tag + // <Header for 'Synchronised tempo codes', ID: 'SYTC'> + // Time stamp format $xx + // Tempo data <binary data> + // Where time stamp format is: + // $01 (32-bit value) MPEG frames from beginning of file + // $02 (32-bit value) milliseconds from beginning of file + + $frame_offset = 0; + $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $timestamp_counter = 0; + while ($frame_offset < strlen($parsedFrame['data'])) { + $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ($parsedFrame[$timestamp_counter]['tempo'] == 255) { + $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1)); + } + $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $timestamp_counter++; + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8 USLT Unsynchronised lyric/text transcription + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription + // There may be more than one 'Unsynchronised lyrics/text transcription' frame + // in each tag, but only one with the same language and content descriptor. + // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'> + // Text encoding $xx + // Language $xx xx xx + // Content descriptor <text string according to encoding> $00 (00) + // Lyrics/text <full text string according to encoding> + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['data'] = $parsedFrame['data']; + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + $parsedFrame['description'] = $frame_description; + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9 SYLT Synchronised lyric/text + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text + // There may be more than one 'SYLT' frame in each tag, + // but only one with the same language and content descriptor. + // <Header for 'Synchronised lyrics/text', ID: 'SYLT'> + // Text encoding $xx + // Language $xx xx xx + // Time stamp format $xx + // $01 (32-bit value) MPEG frames from beginning of file + // $02 (32-bit value) milliseconds from beginning of file + // Content type $xx + // Content descriptor <text string according to encoding> $00 (00) + // Terminated text to be synced (typically a syllable) + // Sync identifier (terminator to above string) $00 (00) + // Time stamp $xx (xx ...) + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']); + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + + $timestampindex = 0; + $frame_remainingdata = substr($parsedFrame['data'], $frame_offset); + while (strlen($frame_remainingdata)) { + $frame_offset = 0; + $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding)); + if ($frame_terminatorpos === false) { + $frame_remainingdata = ''; + } else { + if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset); + + $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) { + // timestamp probably omitted for first data item + } else { + $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4)); + $frame_remainingdata = substr($frame_remainingdata, 4); + } + $timestampindex++; + } + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10 COMM Comments + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) { // 4.11 COM Comments + // There may be more than one comment frame in each tag, + // but only one with the same language and content descriptor. + // <Header for 'Comment', ID: 'COMM'> + // Text encoding $xx + // Language $xx xx xx + // Short content descrip. <text string according to encoding> $00 (00) + // The actual text <full text string according to encoding> + + if (strlen($parsedFrame['data']) < 5) { + + $ThisFileInfo['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']; + + } else { + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + $parsedFrame['description'] = $frame_description; + $parsedFrame['data'] = $frame_text; + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); + } + + } + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only) + // There may be more than one 'RVA2' frame in each tag, + // but only one with the same identification string + // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'> + // Identification <text string> $00 + // The 'identification' string is used to identify the situation and/or + // device where this adjustment should apply. The following is then + // repeated for every channel: + // Type of channel $xx + // Volume adjustment $xx xx + // Bits representing peak $xx + // Peak volume $xx (xx ...) + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00"); + $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos); + if (ord($frame_idstring) === 0) { + $frame_idstring = ''; + } + $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); + $parsedFrame['description'] = $frame_idstring; + while (strlen($frame_remainingdata)) { + $frame_offset = 0; + $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1)); + $parsedFrame[$frame_channeltypeid]['channeltypeid'] = $frame_channeltypeid; + $parsedFrame[$frame_channeltypeid]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid); + $parsedFrame[$frame_channeltypeid]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed + $frame_offset += 2; + $parsedFrame[$frame_channeltypeid]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1)); + $frame_bytespeakvolume = ceil($parsedFrame[$frame_channeltypeid]['bitspeakvolume'] / 8); + $parsedFrame[$frame_channeltypeid]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume)); + $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume); + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12 RVAD Relative volume adjustment (ID3v2.3 only) + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only) + // There may only be one 'RVA' frame in each tag + // <Header for 'Relative volume adjustment', ID: 'RVA'> + // ID3v2.2 => Increment/decrement %000000ba + // ID3v2.3 => Increment/decrement %00fedcba + // Bits used for volume descr. $xx + // Relative volume change, right $xx xx (xx ...) // a + // Relative volume change, left $xx xx (xx ...) // b + // Peak volume right $xx xx (xx ...) + // Peak volume left $xx xx (xx ...) + // ID3v2.3 only, optional (not present in ID3v2.2): + // Relative volume change, right back $xx xx (xx ...) // c + // Relative volume change, left back $xx xx (xx ...) // d + // Peak volume right back $xx xx (xx ...) + // Peak volume left back $xx xx (xx ...) + // ID3v2.3 only, optional (not present in ID3v2.2): + // Relative volume change, center $xx xx (xx ...) // e + // Peak volume center $xx xx (xx ...) + // ID3v2.3 only, optional (not present in ID3v2.2): + // Relative volume change, bass $xx xx (xx ...) // f + // Peak volume bass $xx xx (xx ...) + + $frame_offset = 0; + $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1); + $parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1); + $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8); + $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['right'] === false) { + $parsedFrame['volumechange']['right'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['left'] === false) { + $parsedFrame['volumechange']['left'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + if ($id3v2_majorversion == 3) { + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); + if (strlen($parsedFrame['data']) > 0) { + $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1); + $parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1); + $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['rightrear'] === false) { + $parsedFrame['volumechange']['rightrear'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['leftrear'] === false) { + $parsedFrame['volumechange']['leftrear'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + } + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); + if (strlen($parsedFrame['data']) > 0) { + $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1); + $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['center'] === false) { + $parsedFrame['volumechange']['center'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + } + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset); + if (strlen($parsedFrame['data']) > 0) { + $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1); + $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + if ($parsedFrame['incdec']['bass'] === false) { + $parsedFrame['volumechange']['bass'] *= -1; + } + $frame_offset += $frame_bytesvolume; + $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume)); + $frame_offset += $frame_bytesvolume; + } + } + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only) + // There may be more than one 'EQU2' frame in each tag, + // but only one with the same identification string + // <Header of 'Equalisation (2)', ID: 'EQU2'> + // Interpolation method $xx + // $00 Band + // $01 Linear + // Identification <text string> $00 + // The following is then repeated for every adjustment point + // Frequency $xx xx + // Volume adjustment $xx xx + + $frame_offset = 0; + $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_idstring) === 0) { + $frame_idstring = ''; + } + $parsedFrame['description'] = $frame_idstring; + $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00")); + while (strlen($frame_remainingdata)) { + $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2; + $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true); + $frame_remainingdata = substr($frame_remainingdata, 4); + } + $parsedFrame['interpolationmethod'] = $frame_interpolationmethod; + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12 EQUA Equalisation (ID3v2.3 only) + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) { // 4.13 EQU Equalisation (ID3v2.2 only) + // There may only be one 'EQUA' frame in each tag + // <Header for 'Relative volume adjustment', ID: 'EQU'> + // Adjustment bits $xx + // This is followed by 2 bytes + ('adjustment bits' rounded up to the + // nearest byte) for every equalisation band in the following format, + // giving a frequency range of 0 - 32767Hz: + // Increment/decrement %x (MSB of the Frequency) + // Frequency (lower 15 bits) + // Adjustment $xx (xx ...) + + $frame_offset = 0; + $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1); + $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8); + + $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset); + while (strlen($frame_remainingdata) > 0) { + $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2)); + $frame_incdec = (bool) substr($frame_frequencystr, 0, 1); + $frame_frequency = bindec(substr($frame_frequencystr, 1, 15)); + $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec; + $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes)); + if ($parsedFrame[$frame_frequency]['incdec'] === false) { + $parsedFrame[$frame_frequency]['adjustment'] *= -1; + } + $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes); + } + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13 RVRB Reverb + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) { // 4.14 REV Reverb + // There may only be one 'RVRB' frame in each tag. + // <Header for 'Reverb', ID: 'RVRB'> + // Reverb left (ms) $xx xx + // Reverb right (ms) $xx xx + // Reverb bounces, left $xx + // Reverb bounces, right $xx + // Reverb feedback, left to left $xx + // Reverb feedback, left to right $xx + // Reverb feedback, right to right $xx + // Reverb feedback, right to left $xx + // Premix left to right $xx + // Premix right to left $xx + + $frame_offset = 0; + $parsedFrame['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14 APIC Attached picture + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) { // 4.15 PIC Attached picture + // There may be several pictures attached to one file, + // each in their individual 'APIC' frame, but only one + // with the same content descriptor + // <Header for 'Attached picture', ID: 'APIC'> + // Text encoding $xx + // ID3v2.3+ => MIME type <text string> $00 + // ID3v2.2 => Image format $xx xx xx + // Picture type $xx + // Description <text string according to encoding> $00 (00) + // Picture data <binary data> + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + + if ($id3v2_majorversion == 2) { + $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3); + if (strtolower($frame_imagetype) == 'ima') { + // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted + // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net) + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_mimetype) === 0) { + $frame_mimetype = ''; + } + $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype))); + if ($frame_imagetype == 'JPEG') { + $frame_imagetype = 'JPG'; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + } else { + $frame_offset += 3; + } + } + if ($id3v2_majorversion > 2) { + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_mimetype) === 0) { + $frame_mimetype = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + } + + $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + if ($id3v2_majorversion == 2) { + $parsedFrame['imagetype'] = $frame_imagetype; + } else { + $parsedFrame['mime'] = $frame_mimetype; + } + $parsedFrame['picturetypeid'] = $frame_picturetype; + $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype); + $parsedFrame['description'] = $frame_description; + $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding))); + + $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data']); + if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) { + $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]); + if ($imagechunkcheck[0]) { + $parsedFrame['image_width'] = $imagechunkcheck[0]; + } + if ($imagechunkcheck[1]) { + $parsedFrame['image_height'] = $imagechunkcheck[1]; + } + $parsedFrame['image_bytes'] = strlen($parsedFrame['data']); + } + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object + // There may be more than one 'GEOB' frame in each tag, + // but only one with the same content descriptor + // <Header for 'General encapsulated object', ID: 'GEOB'> + // Text encoding $xx + // MIME type <text string> $00 + // Filename <text string according to encoding> $00 (00) + // Content description <text string according to encoding> $00 (00) + // Encapsulated object <binary data> + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_mimetype) === 0) { + $frame_mimetype = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_filename) === 0) { + $frame_filename = ''; + } + $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + + $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset); + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['mime'] = $frame_mimetype; + $parsedFrame['filename'] = $frame_filename; + $parsedFrame['description'] = $frame_description; + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16 PCNT Play counter + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) { // 4.17 CNT Play counter + // There may only be one 'PCNT' frame in each tag. + // When the counter reaches all one's, one byte is inserted in + // front of the counter thus making the counter eight bits bigger + // <Header for 'Play counter', ID: 'PCNT'> + // Counter $xx xx xx xx (xx ...) + + $parsedFrame['data'] = getid3_lib::BigEndian2Int($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17 POPM Popularimeter + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter + // There may be more than one 'POPM' frame in each tag, + // but only one with the same email address + // <Header for 'Popularimeter', ID: 'POPM'> + // Email to user <text string> $00 + // Rating $xx + // Counter $xx xx xx xx (xx ...) + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_emailaddress) === 0) { + $frame_emailaddress = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); + $parsedFrame['email'] = $frame_emailaddress; + $parsedFrame['rating'] = $frame_rating; + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18 RBUF Recommended buffer size + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) { // 4.19 BUF Recommended buffer size + // There may only be one 'RBUF' frame in each tag + // <Header for 'Recommended buffer size', ID: 'RBUF'> + // Buffer size $xx xx xx + // Embedded info flag %0000000x + // Offset to next tag $xx xx xx xx + + $frame_offset = 0; + $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3)); + $frame_offset += 3; + + $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1); + $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20 Encrypted meta frame (ID3v2.2 only) + // There may be more than one 'CRM' frame in a tag, + // but only one with the same 'owner identifier' + // <Header for 'Encrypted meta frame', ID: 'CRM'> + // Owner identifier <textstring> $00 (00) + // Content/explanation <textstring> $00 (00) + // Encrypted datablock <binary data> + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + $parsedFrame['description'] = $frame_description; + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19 AENC Audio encryption + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) { // 4.21 CRA Audio encryption + // There may be more than one 'AENC' frames in a tag, + // but only one with the same 'Owner identifier' + // <Header for 'Audio encryption', ID: 'AENC'> + // Owner identifier <text string> $00 + // Preview start $xx xx + // Preview length $xx xx + // Encryption info <binary data> + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_ownerid) === 0) { + $frame_ownerid == ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset); + unset($parsedFrame['data']); + + + } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20 LINK Linked information + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information + // There may be more than one 'LINK' frame in a tag, + // but only one with the same contents + // <Header for 'Linked information', ID: 'LINK'> + // ID3v2.3+ => Frame identifier $xx xx xx xx + // ID3v2.2 => Frame identifier $xx xx xx + // URL <text string> $00 + // ID and additional data <text string(s)> + + $frame_offset = 0; + if ($id3v2_majorversion == 2) { + $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + } else { + $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4); + $frame_offset += 4; + } + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_url) === 0) { + $frame_url = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + $parsedFrame['url'] = $frame_url; + + $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset); + if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) { + $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['url']); + } + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only) + // There may only be one 'POSS' frame in each tag + // <Head for 'Position synchronisation', ID: 'POSS'> + // Time stamp format $xx + // Position $xx (xx ...) + + $frame_offset = 0; + $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['position'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset)); + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22 USER Terms of use (ID3v2.3+ only) + // There may be more than one 'Terms of use' frame in a tag, + // but only one with the same 'Language' + // <Header for 'Terms of use frame', ID: 'USER'> + // Text encoding $xx + // Language $xx xx xx + // The actual text <text string according to encoding> + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $frame_language = substr($parsedFrame['data'], $frame_offset, 3); + $frame_offset += 3; + $parsedFrame['language'] = $frame_language; + $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false); + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $ThisFileInfo['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $ThisFileInfo['id3v2']['encoding'], $parsedFrame['data']); + } + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23 OWNE Ownership frame (ID3v2.3+ only) + // There may only be one 'OWNE' frame in a tag + // <Header for 'Ownership frame', ID: 'OWNE'> + // Text encoding $xx + // Price paid <text string> $00 + // Date of purch. <text string> + // Seller <text string according to encoding> + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3); + $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']); + $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3); + + $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8); + if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) { + $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4)); + } + $frame_offset += 8; + + $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset); + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24 COMR Commercial frame (ID3v2.3+ only) + // There may be more than one 'commercial frame' in a tag, + // but no two may be identical + // <Header for 'Commercial frame', ID: 'COMR'> + // Text encoding $xx + // Price string <text string> $00 + // Valid until <text string> + // Contact URL <text string> $00 + // Received as $xx + // Name of seller <text string according to encoding> $00 (00) + // Description <text string according to encoding> $00 (00) + // Picture MIME type <string> $00 + // Seller logo <binary data> + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $ThisFileInfo['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'; + } + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + $frame_rawpricearray = explode('/', $frame_pricestring); + foreach ($frame_rawpricearray as $key => $val) { + $frame_currencyid = substr($val, 0, 3); + $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid); + $parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3); + } + + $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8); + $frame_offset += 8; + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_sellername) === 0) { + $frame_sellername = ''; + } + $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + + $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset); + if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) { + $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00 + } + $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_description) === 0) { + $frame_description = ''; + } + $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)); + + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset); + + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding); + + $parsedFrame['pricevaliduntil'] = $frame_datestring; + $parsedFrame['contacturl'] = $frame_contacturl; + $parsedFrame['receivedasid'] = $frame_receivedasid; + $parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid); + $parsedFrame['sellername'] = $frame_sellername; + $parsedFrame['description'] = $frame_description; + $parsedFrame['mime'] = $frame_mimetype; + $parsedFrame['logo'] = $frame_sellerlogo; + unset($parsedFrame['data']); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25 ENCR Encryption method registration (ID3v2.3+ only) + // There may be several 'ENCR' frames in a tag, + // but only one containing the same symbol + // and only one containing the same owner identifier + // <Header for 'Encryption method registration', ID: 'ENCR'> + // Owner identifier <text string> $00 + // Method symbol $xx + // Encryption data <binary data> + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_ownerid) === 0) { + $frame_ownerid = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26 GRID Group identification registration (ID3v2.3+ only) + + // There may be several 'GRID' frames in a tag, + // but only one containing the same symbol + // and only one containing the same owner identifier + // <Header for 'Group ID registration', ID: 'GRID'> + // Owner identifier <text string> $00 + // Group symbol $xx + // Group dependent data <binary data> + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_ownerid) === 0) { + $frame_ownerid = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27 PRIV Private frame (ID3v2.3+ only) + // The tag may contain more than one 'PRIV' frame + // but only with different contents + // <Header for 'Private frame', ID: 'PRIV'> + // Owner identifier <text string> $00 + // The private data <binary data> + + $frame_offset = 0; + $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset); + $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); + if (ord($frame_ownerid) === 0) { + $frame_ownerid = ''; + } + $frame_offset = $frame_terminatorpos + strlen("\x00"); + + $parsedFrame['ownerid'] = $frame_ownerid; + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28 SIGN Signature frame (ID3v2.4+ only) + // There may be more than one 'signature frame' in a tag, + // but no two may be identical + // <Header for 'Signature frame', ID: 'SIGN'> + // Group symbol $xx + // Signature <binary data> + + $frame_offset = 0; + $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset); + + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29 SEEK Seek frame (ID3v2.4+ only) + // There may only be one 'seek frame' in a tag + // <Header for 'Seek frame', ID: 'SEEK'> + // Minimum offset to next tag $xx xx xx xx + + $frame_offset = 0; + $parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + + + } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30 ASPI Audio seek point index (ID3v2.4+ only) + // There may only be one 'audio seek point index' frame in a tag + // <Header for 'Seek Point Index', ID: 'ASPI'> + // Indexed data start (S) $xx xx xx xx + // Indexed data length (L) $xx xx xx xx + // Number of index points (N) $xx xx + // Bits per index point (b) $xx + // Then for every index point the following data is included: + // Fraction at index (Fi) $xx (xx) + + $frame_offset = 0; + $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8); + for ($i = 0; $i < $frame_indexpoints; $i++) { + $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint)); + $frame_offset += $frame_bytesperpoint; + } + unset($parsedFrame['data']); + + } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment + // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html + // There may only be one 'RGAD' frame in a tag + // <Header for 'Replay Gain Adjustment', ID: 'RGAD'> + // Peak Amplitude $xx $xx $xx $xx + // Radio Replay Gain Adjustment %aaabbbcd %dddddddd + // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd + // a - name code + // b - originator code + // c - sign bit + // d - replay gain adjustment + + $frame_offset = 0; + $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4)); + $frame_offset += 4; + $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2)); + $frame_offset += 2; + $parsedFrame['raw']['track']['name'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3)); + $parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3)); + $parsedFrame['raw']['track']['signbit'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1)); + $parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9)); + $parsedFrame['raw']['album']['name'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3)); + $parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3)); + $parsedFrame['raw']['album']['signbit'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1)); + $parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9)); + $parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']); + $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']); + $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']); + $parsedFrame['album']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']); + $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']); + $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']); + + $ThisFileInfo['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude']; + $ThisFileInfo['replay_gain']['track']['originator'] = $parsedFrame['track']['originator']; + $ThisFileInfo['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment']; + $ThisFileInfo['replay_gain']['album']['originator'] = $parsedFrame['album']['originator']; + $ThisFileInfo['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment']; + + unset($parsedFrame['data']); + + } + + return true; + } + + + function DeUnsynchronise($data) { + return str_replace("\xFF\x00", "\xFF", $data); + } + + function LookupCurrencyUnits($currencyid) { + + $begin = __LINE__; + + /** This is not a comment! + + + AED Dirhams + AFA Afghanis + ALL Leke + AMD Drams + ANG Guilders + AOA Kwanza + ARS Pesos + ATS Schillings + AUD Dollars + AWG Guilders + AZM Manats + BAM Convertible Marka + BBD Dollars + BDT Taka + BEF Francs + BGL Leva + BHD Dinars + BIF Francs + BMD Dollars + BND Dollars + BOB Bolivianos + BRL Brazil Real + BSD Dollars + BTN Ngultrum + BWP Pulas + BYR Rubles + BZD Dollars + CAD Dollars + CDF Congolese Francs + CHF Francs + CLP Pesos + CNY Yuan Renminbi + COP Pesos + CRC Colones + CUP Pesos + CVE Escudos + CYP Pounds + CZK Koruny + DEM Deutsche Marks + DJF Francs + DKK Kroner + DOP Pesos + DZD Algeria Dinars + EEK Krooni + EGP Pounds + ERN Nakfa + ESP Pesetas + ETB Birr + EUR Euro + FIM Markkaa + FJD Dollars + FKP Pounds + FRF Francs + GBP Pounds + GEL Lari + GGP Pounds + GHC Cedis + GIP Pounds + GMD Dalasi + GNF Francs + GRD Drachmae + GTQ Quetzales + GYD Dollars + HKD Dollars + HNL Lempiras + HRK Kuna + HTG Gourdes + HUF Forints + IDR Rupiahs + IEP Pounds + ILS New Shekels + IMP Pounds + INR Rupees + IQD Dinars + IRR Rials + ISK Kronur + ITL Lire + JEP Pounds + JMD Dollars + JOD Dinars + JPY Yen + KES Shillings + KGS Soms + KHR Riels + KMF Francs + KPW Won + KWD Dinars + KYD Dollars + KZT Tenge + LAK Kips + LBP Pounds + LKR Rupees + LRD Dollars + LSL Maloti + LTL Litai + LUF Francs + LVL Lati + LYD Dinars + MAD Dirhams + MDL Lei + MGF Malagasy Francs + MKD Denars + MMK Kyats + MNT Tugriks + MOP Patacas + MRO Ouguiyas + MTL Liri + MUR Rupees + MVR Rufiyaa + MWK Kwachas + MXN Pesos + MYR Ringgits + MZM Meticais + NAD Dollars + NGN Nairas + NIO Gold Cordobas + NLG Guilders + NOK Krone + NPR Nepal Rupees + NZD Dollars + OMR Rials + PAB Balboa + PEN Nuevos Soles + PGK Kina + PHP Pesos + PKR Rupees + PLN Zlotych + PTE Escudos + PYG Guarani + QAR Rials + ROL Lei + RUR Rubles + RWF Rwanda Francs + SAR Riyals + SBD Dollars + SCR Rupees + SDD Dinars + SEK Kronor + SGD Dollars + SHP Pounds + SIT Tolars + SKK Koruny + SLL Leones + SOS Shillings + SPL Luigini + SRG Guilders + STD Dobras + SVC Colones + SYP Pounds + SZL Emalangeni + THB Baht + TJR Rubles + TMM Manats + TND Dinars + TOP Pa'anga + TRL Liras + TTD Dollars + TVD Tuvalu Dollars + TWD New Dollars + TZS Shillings + UAH Hryvnia + UGX Shillings + USD Dollars + UYU Pesos + UZS Sums + VAL Lire + VEB Bolivares + VND Dong + VUV Vatu + WST Tala + XAF Francs + XAG Ounces + XAU Ounces + XCD Dollars + XDR Special Drawing Rights + XPD Ounces + XPF Francs + XPT Ounces + YER Rials + YUM New Dinars + ZAR Rand + ZMK Kwacha + ZWD Zimbabwe Dollars + + */ + + return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units'); + } + + + function LookupCurrencyCountry($currencyid) { + + $begin = __LINE__; + + /** This is not a comment! + + AED United Arab Emirates + AFA Afghanistan + ALL Albania + AMD Armenia + ANG Netherlands Antilles + AOA Angola + ARS Argentina + ATS Austria + AUD Australia + AWG Aruba + AZM Azerbaijan + BAM Bosnia and Herzegovina + BBD Barbados + BDT Bangladesh + BEF Belgium + BGL Bulgaria + BHD Bahrain + BIF Burundi + BMD Bermuda + BND Brunei Darussalam + BOB Bolivia + BRL Brazil + BSD Bahamas + BTN Bhutan + BWP Botswana + BYR Belarus + BZD Belize + CAD Canada + CDF Congo/Kinshasa + CHF Switzerland + CLP Chile + CNY China + COP Colombia + CRC Costa Rica + CUP Cuba + CVE Cape Verde + CYP Cyprus + CZK Czech Republic + DEM Germany + DJF Djibouti + DKK Denmark + DOP Dominican Republic + DZD Algeria + EEK Estonia + EGP Egypt + ERN Eritrea + ESP Spain + ETB Ethiopia + EUR Euro Member Countries + FIM Finland + FJD Fiji + FKP Falkland Islands (Malvinas) + FRF France + GBP United Kingdom + GEL Georgia + GGP Guernsey + GHC Ghana + GIP Gibraltar + GMD Gambia + GNF Guinea + GRD Greece + GTQ Guatemala + GYD Guyana + HKD Hong Kong + HNL Honduras + HRK Croatia + HTG Haiti + HUF Hungary + IDR Indonesia + IEP Ireland (Eire) + ILS Israel + IMP Isle of Man + INR India + IQD Iraq + IRR Iran + ISK Iceland + ITL Italy + JEP Jersey + JMD Jamaica + JOD Jordan + JPY Japan + KES Kenya + KGS Kyrgyzstan + KHR Cambodia + KMF Comoros + KPW Korea + KWD Kuwait + KYD Cayman Islands + KZT Kazakstan + LAK Laos + LBP Lebanon + LKR Sri Lanka + LRD Liberia + LSL Lesotho + LTL Lithuania + LUF Luxembourg + LVL Latvia + LYD Libya + MAD Morocco + MDL Moldova + MGF Madagascar + MKD Macedonia + MMK Myanmar (Burma) + MNT Mongolia + MOP Macau + MRO Mauritania + MTL Malta + MUR Mauritius + MVR Maldives (Maldive Islands) + MWK Malawi + MXN Mexico + MYR Malaysia + MZM Mozambique + NAD Namibia + NGN Nigeria + NIO Nicaragua + NLG Netherlands (Holland) + NOK Norway + NPR Nepal + NZD New Zealand + OMR Oman + PAB Panama + PEN Peru + PGK Papua New Guinea + PHP Philippines + PKR Pakistan + PLN Poland + PTE Portugal + PYG Paraguay + QAR Qatar + ROL Romania + RUR Russia + RWF Rwanda + SAR Saudi Arabia + SBD Solomon Islands + SCR Seychelles + SDD Sudan + SEK Sweden + SGD Singapore + SHP Saint Helena + SIT Slovenia + SKK Slovakia + SLL Sierra Leone + SOS Somalia + SPL Seborga + SRG Suriname + STD São Tome and Principe + SVC El Salvador + SYP Syria + SZL Swaziland + THB Thailand + TJR Tajikistan + TMM Turkmenistan + TND Tunisia + TOP Tonga + TRL Turkey + TTD Trinidad and Tobago + TVD Tuvalu + TWD Taiwan + TZS Tanzania + UAH Ukraine + UGX Uganda + USD United States of America + UYU Uruguay + UZS Uzbekistan + VAL Vatican City + VEB Venezuela + VND Viet Nam + VUV Vanuatu + WST Samoa + XAF Communauté Financière Africaine + XAG Silver + XAU Gold + XCD East Caribbean + XDR International Monetary Fund + XPD Palladium + XPF Comptoirs Français du Pacifique + XPT Platinum + YER Yemen + YUM Yugoslavia + ZAR South Africa + ZMK Zambia + ZWD Zimbabwe + + */ + + return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country'); + } + + + + function LanguageLookup($languagecode, $casesensitive=false) { + + if (!$casesensitive) { + $languagecode = strtolower($languagecode); + } + + // http://www.id3.org/id3v2.4.0-structure.txt + // [4. ID3v2 frame overview] + // The three byte language field, present in several frames, is used to + // describe the language of the frame's content, according to ISO-639-2 + // [ISO-639-2]. The language should be represented in lower case. If the + // language is not known the string "XXX" should be used. + + + // ISO 639-2 - http://www.id3.org/iso639-2.html + + $begin = __LINE__; + + /** This is not a comment! + + XXX unknown + xxx unknown + aar Afar + abk Abkhazian + ace Achinese + ach Acoli + ada Adangme + afa Afro-Asiatic (Other) + afh Afrihili + afr Afrikaans + aka Akan + akk Akkadian + alb Albanian + ale Aleut + alg Algonquian Languages + amh Amharic + ang English, Old (ca. 450-1100) + apa Apache Languages + ara Arabic + arc Aramaic + arm Armenian + arn Araucanian + arp Arapaho + art Artificial (Other) + arw Arawak + asm Assamese + ath Athapascan Languages + ava Avaric + ave Avestan + awa Awadhi + aym Aymara + aze Azerbaijani + bad Banda + bai Bamileke Languages + bak Bashkir + bal Baluchi + bam Bambara + ban Balinese + baq Basque + bas Basa + bat Baltic (Other) + bej Beja + bel Byelorussian + bem Bemba + ben Bengali + ber Berber (Other) + bho Bhojpuri + bih Bihari + bik Bikol + bin Bini + bis Bislama + bla Siksika + bnt Bantu (Other) + bod Tibetan + bra Braj + bre Breton + bua Buriat + bug Buginese + bul Bulgarian + bur Burmese + cad Caddo + cai Central American Indian (Other) + car Carib + cat Catalan + cau Caucasian (Other) + ceb Cebuano + cel Celtic (Other) + ces Czech + cha Chamorro + chb Chibcha + che Chechen + chg Chagatai + chi Chinese + chm Mari + chn Chinook jargon + cho Choctaw + chr Cherokee + chu Church Slavic + chv Chuvash + chy Cheyenne + cop Coptic + cor Cornish + cos Corsican + cpe Creoles and Pidgins, English-based (Other) + cpf Creoles and Pidgins, French-based (Other) + cpp Creoles and Pidgins, Portuguese-based (Other) + cre Cree + crp Creoles and Pidgins (Other) + cus Cushitic (Other) + cym Welsh + cze Czech + dak Dakota + dan Danish + del Delaware + deu German + din Dinka + div Divehi + doi Dogri + dra Dravidian (Other) + dua Duala + dum Dutch, Middle (ca. 1050-1350) + dut Dutch + dyu Dyula + dzo Dzongkha + efi Efik + egy Egyptian (Ancient) + eka Ekajuk + ell Greek, Modern (1453-) + elx Elamite + eng English + enm English, Middle (ca. 1100-1500) + epo Esperanto + esk Eskimo (Other) + esl Spanish + est Estonian + eus Basque + ewe Ewe + ewo Ewondo + fan Fang + fao Faroese + fas Persian + fat Fanti + fij Fijian + fin Finnish + fiu Finno-Ugrian (Other) + fon Fon + fra French + fre French + frm French, Middle (ca. 1400-1600) + fro French, Old (842- ca. 1400) + fry Frisian + ful Fulah + gaa Ga + gae Gaelic (Scots) + gai Irish + gay Gayo + gdh Gaelic (Scots) + gem Germanic (Other) + geo Georgian + ger German + gez Geez + gil Gilbertese + glg Gallegan + gmh German, Middle High (ca. 1050-1500) + goh German, Old High (ca. 750-1050) + gon Gondi + got Gothic + grb Grebo + grc Greek, Ancient (to 1453) + gre Greek, Modern (1453-) + grn Guarani + guj Gujarati + hai Haida + hau Hausa + haw Hawaiian + heb Hebrew + her Herero + hil Hiligaynon + him Himachali + hin Hindi + hmo Hiri Motu + hun Hungarian + hup Hupa + hye Armenian + iba Iban + ibo Igbo + ice Icelandic + ijo Ijo + iku Inuktitut + ilo Iloko + ina Interlingua (International Auxiliary language Association) + inc Indic (Other) + ind Indonesian + ine Indo-European (Other) + ine Interlingue + ipk Inupiak + ira Iranian (Other) + iri Irish + iro Iroquoian uages + isl Icelandic + ita Italian + jav Javanese + jaw Javanese + jpn Japanese + jpr Judeo-Persian + jrb Judeo-Arabic + kaa Kara-Kalpak + kab Kabyle + kac Kachin + kal Greenlandic + kam Kamba + kan Kannada + kar Karen + kas Kashmiri + kat Georgian + kau Kanuri + kaw Kawi + kaz Kazakh + kha Khasi + khi Khoisan (Other) + khm Khmer + kho Khotanese + kik Kikuyu + kin Kinyarwanda + kir Kirghiz + kok Konkani + kom Komi + kon Kongo + kor Korean + kpe Kpelle + kro Kru + kru Kurukh + kua Kuanyama + kum Kumyk + kur Kurdish + kus Kusaie + kut Kutenai + lad Ladino + lah Lahnda + lam Lamba + lao Lao + lat Latin + lav Latvian + lez Lezghian + lin Lingala + lit Lithuanian + lol Mongo + loz Lozi + ltz Letzeburgesch + lub Luba-Katanga + lug Ganda + lui Luiseno + lun Lunda + luo Luo (Kenya and Tanzania) + mac Macedonian + mad Madurese + mag Magahi + mah Marshall + mai Maithili + mak Macedonian + mak Makasar + mal Malayalam + man Mandingo + mao Maori + map Austronesian (Other) + mar Marathi + mas Masai + max Manx + may Malay + men Mende + mga Irish, Middle (900 - 1200) + mic Micmac + min Minangkabau + mis Miscellaneous (Other) + mkh Mon-Kmer (Other) + mlg Malagasy + mlt Maltese + mni Manipuri + mno Manobo Languages + moh Mohawk + mol Moldavian + mon Mongolian + mos Mossi + mri Maori + msa Malay + mul Multiple Languages + mun Munda Languages + mus Creek + mwr Marwari + mya Burmese + myn Mayan Languages + nah Aztec + nai North American Indian (Other) + nau Nauru + nav Navajo + nbl Ndebele, South + nde Ndebele, North + ndo Ndongo + nep Nepali + new Newari + nic Niger-Kordofanian (Other) + niu Niuean + nla Dutch + nno Norwegian (Nynorsk) + non Norse, Old + nor Norwegian + nso Sotho, Northern + nub Nubian Languages + nya Nyanja + nym Nyamwezi + nyn Nyankole + nyo Nyoro + nzi Nzima + oci Langue d'Oc (post 1500) + oji Ojibwa + ori Oriya + orm Oromo + osa Osage + oss Ossetic + ota Turkish, Ottoman (1500 - 1928) + oto Otomian Languages + paa Papuan-Australian (Other) + pag Pangasinan + pal Pahlavi + pam Pampanga + pan Panjabi + pap Papiamento + pau Palauan + peo Persian, Old (ca 600 - 400 B.C.) + per Persian + phn Phoenician + pli Pali + pol Polish + pon Ponape + por Portuguese + pra Prakrit uages + pro Provencal, Old (to 1500) + pus Pushto + que Quechua + raj Rajasthani + rar Rarotongan + roa Romance (Other) + roh Rhaeto-Romance + rom Romany + ron Romanian + rum Romanian + run Rundi + rus Russian + sad Sandawe + sag Sango + sah Yakut + sai South American Indian (Other) + sal Salishan Languages + sam Samaritan Aramaic + san Sanskrit + sco Scots + scr Serbo-Croatian + sel Selkup + sem Semitic (Other) + sga Irish, Old (to 900) + shn Shan + sid Sidamo + sin Singhalese + sio Siouan Languages + sit Sino-Tibetan (Other) + sla Slavic (Other) + slk Slovak + slo Slovak + slv Slovenian + smi Sami Languages + smo Samoan + sna Shona + snd Sindhi + sog Sogdian + som Somali + son Songhai + sot Sotho, Southern + spa Spanish + sqi Albanian + srd Sardinian + srr Serer + ssa Nilo-Saharan (Other) + ssw Siswant + ssw Swazi + suk Sukuma + sun Sudanese + sus Susu + sux Sumerian + sve Swedish + swa Swahili + swe Swedish + syr Syriac + tah Tahitian + tam Tamil + tat Tatar + tel Telugu + tem Timne + ter Tereno + tgk Tajik + tgl Tagalog + tha Thai + tib Tibetan + tig Tigre + tir Tigrinya + tiv Tivi + tli Tlingit + tmh Tamashek + tog Tonga (Nyasa) + ton Tonga (Tonga Islands) + tru Truk + tsi Tsimshian + tsn Tswana + tso Tsonga + tuk Turkmen + tum Tumbuka + tur Turkish + tut Altaic (Other) + twi Twi + tyv Tuvinian + uga Ugaritic + uig Uighur + ukr Ukrainian + umb Umbundu + und Undetermined + urd Urdu + uzb Uzbek + vai Vai + ven Venda + vie Vietnamese + vol Volapük + vot Votic + wak Wakashan Languages + wal Walamo + war Waray + was Washo + wel Welsh + wen Sorbian Languages + wol Wolof + xho Xhosa + yao Yao + yap Yap + yid Yiddish + yor Yoruba + zap Zapotec + zen Zenaga + zha Zhuang + zho Chinese + zul Zulu + zun Zuni + + */ + + return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode'); + } + + + function ETCOEventLookup($index) { + if (($index >= 0x17) && ($index <= 0xDF)) { + return 'reserved for future use'; + } + if (($index >= 0xE0) && ($index <= 0xEF)) { + return 'not predefined synch 0-F'; + } + if (($index >= 0xF0) && ($index <= 0xFC)) { + return 'reserved for future use'; + } + + static $EventLookup = array( + 0x00 => 'padding (has no meaning)', + 0x01 => 'end of initial silence', + 0x02 => 'intro start', + 0x03 => 'main part start', + 0x04 => 'outro start', + 0x05 => 'outro end', + 0x06 => 'verse start', + 0x07 => 'refrain start', + 0x08 => 'interlude start', + 0x09 => 'theme start', + 0x0A => 'variation start', + 0x0B => 'key change', + 0x0C => 'time change', + 0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)', + 0x0E => 'sustained noise', + 0x0F => 'sustained noise end', + 0x10 => 'intro end', + 0x11 => 'main part end', + 0x12 => 'verse end', + 0x13 => 'refrain end', + 0x14 => 'theme end', + 0x15 => 'profanity', + 0x16 => 'profanity end', + 0xFD => 'audio end (start of silence)', + 0xFE => 'audio file ends', + 0xFF => 'one more byte of events follows' + ); + + return (isset($EventLookup[$index]) ? $EventLookup[$index] : ''); + } + + function SYTLContentTypeLookup($index) { + static $SYTLContentTypeLookup = array( + 0x00 => 'other', + 0x01 => 'lyrics', + 0x02 => 'text transcription', + 0x03 => 'movement/part name', // (e.g. 'Adagio') + 0x04 => 'events', // (e.g. 'Don Quijote enters the stage') + 0x05 => 'chord', // (e.g. 'Bb F Fsus') + 0x06 => 'trivia/\'pop up\' information', + 0x07 => 'URLs to webpages', + 0x08 => 'URLs to images' + ); + + return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : ''); + } + + function APICPictureTypeLookup($index, $returnarray=false) { + static $APICPictureTypeLookup = array( + 0x00 => 'Other', + 0x01 => '32x32 pixels \'file icon\' (PNG only)', + 0x02 => 'Other file icon', + 0x03 => 'Cover (front)', + 0x04 => 'Cover (back)', + 0x05 => 'Leaflet page', + 0x06 => 'Media (e.g. label side of CD)', + 0x07 => 'Lead artist/lead performer/soloist', + 0x08 => 'Artist/performer', + 0x09 => 'Conductor', + 0x0A => 'Band/Orchestra', + 0x0B => 'Composer', + 0x0C => 'Lyricist/text writer', + 0x0D => 'Recording Location', + 0x0E => 'During recording', + 0x0F => 'During performance', + 0x10 => 'Movie/video screen capture', + 0x11 => 'A bright coloured fish', + 0x12 => 'Illustration', + 0x13 => 'Band/artist logotype', + 0x14 => 'Publisher/Studio logotype' + ); + if ($returnarray) { + return $APICPictureTypeLookup; + } + return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : ''); + } + + function COMRReceivedAsLookup($index) { + static $COMRReceivedAsLookup = array( + 0x00 => 'Other', + 0x01 => 'Standard CD album with other songs', + 0x02 => 'Compressed audio on CD', + 0x03 => 'File over the Internet', + 0x04 => 'Stream over the Internet', + 0x05 => 'As note sheets', + 0x06 => 'As note sheets in a book with other sheets', + 0x07 => 'Music on other media', + 0x08 => 'Non-musical merchandise' + ); + + return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : ''); + } + + function RVA2ChannelTypeLookup($index) { + static $RVA2ChannelTypeLookup = array( + 0x00 => 'Other', + 0x01 => 'Master volume', + 0x02 => 'Front right', + 0x03 => 'Front left', + 0x04 => 'Back right', + 0x05 => 'Back left', + 0x06 => 'Front centre', + 0x07 => 'Back centre', + 0x08 => 'Subwoofer' + ); + + return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : ''); + } + + function FrameNameLongLookup($framename) { + + $begin = __LINE__; + + /** This is not a comment! + + AENC Audio encryption + APIC Attached picture + ASPI Audio seek point index + BUF Recommended buffer size + CNT Play counter + COM Comments + COMM Comments + COMR Commercial frame + CRA Audio encryption + CRM Encrypted meta frame + ENCR Encryption method registration + EQU Equalisation + EQU2 Equalisation (2) + EQUA Equalisation + ETC Event timing codes + ETCO Event timing codes + GEO General encapsulated object + GEOB General encapsulated object + GRID Group identification registration + IPL Involved people list + IPLS Involved people list + LINK Linked information + LNK Linked information + MCDI Music CD identifier + MCI Music CD Identifier + MLL MPEG location lookup table + MLLT MPEG location lookup table + OWNE Ownership frame + PCNT Play counter + PIC Attached picture + POP Popularimeter + POPM Popularimeter + POSS Position synchronisation frame + PRIV Private frame + RBUF Recommended buffer size + REV Reverb + RVA Relative volume adjustment + RVA2 Relative volume adjustment (2) + RVAD Relative volume adjustment + RVRB Reverb + SEEK Seek frame + SIGN Signature frame + SLT Synchronised lyric/text + STC Synced tempo codes + SYLT Synchronised lyric/text + SYTC Synchronised tempo codes + TAL Album/Movie/Show title + TALB Album/Movie/Show title + TBP BPM (Beats Per Minute) + TBPM BPM (beats per minute) + TCM Composer + TCO Content type + TCOM Composer + TCON Content type + TCOP Copyright message + TCR Copyright message + TDA Date + TDAT Date + TDEN Encoding time + TDLY Playlist delay + TDOR Original release time + TDRC Recording time + TDRL Release time + TDTG Tagging time + TDY Playlist delay + TEN Encoded by + TENC Encoded by + TEXT Lyricist/Text writer + TFLT File type + TFT File type + TIM Time + TIME Time + TIPL Involved people list + TIT1 Content group description + TIT2 Title/songname/content description + TIT3 Subtitle/Description refinement + TKE Initial key + TKEY Initial key + TLA Language(s) + TLAN Language(s) + TLE Length + TLEN Length + TMCL Musician credits list + TMED Media type + TMOO Mood + TMT Media type + TOA Original artist(s)/performer(s) + TOAL Original album/movie/show title + TOF Original filename + TOFN Original filename + TOL Original Lyricist(s)/text writer(s) + TOLY Original lyricist(s)/text writer(s) + TOPE Original artist(s)/performer(s) + TOR Original release year + TORY Original release year + TOT Original album/Movie/Show title + TOWN File owner/licensee + TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group + TP2 Band/Orchestra/Accompaniment + TP3 Conductor/Performer refinement + TP4 Interpreted, remixed, or otherwise modified by + TPA Part of a set + TPB Publisher + TPE1 Lead performer(s)/Soloist(s) + TPE2 Band/orchestra/accompaniment + TPE3 Conductor/performer refinement + TPE4 Interpreted, remixed, or otherwise modified by + TPOS Part of a set + TPRO Produced notice + TPUB Publisher + TRC ISRC (International Standard Recording Code) + TRCK Track number/Position in set + TRD Recording dates + TRDA Recording dates + TRK Track number/Position in set + TRSN Internet radio station name + TRSO Internet radio station owner + TSI Size + TSIZ Size + TSOA Album sort order + TSOP Performer sort order + TSOT Title sort order + TSRC ISRC (international standard recording code) + TSS Software/hardware and settings used for encoding + TSSE Software/Hardware and settings used for encoding + TSST Set subtitle + TT1 Content group description + TT2 Title/Songname/Content description + TT3 Subtitle/Description refinement + TXT Lyricist/text writer + TXX User defined text information frame + TXXX User defined text information frame + TYE Year + TYER Year + UFI Unique file identifier + UFID Unique file identifier + ULT Unsychronised lyric/text transcription + USER Terms of use + USLT Unsynchronised lyric/text transcription + WAF Official audio file webpage + WAR Official artist/performer webpage + WAS Official audio source webpage + WCM Commercial information + WCOM Commercial information + WCOP Copyright/Legal information + WCP Copyright/Legal information + WOAF Official audio file webpage + WOAR Official artist/performer webpage + WOAS Official audio source webpage + WORS Official Internet radio station homepage + WPAY Payment + WPB Publishers official webpage + WPUB Publishers official webpage + WXX User defined URL link frame + WXXX User defined URL link frame + TFEA Featured Artist + TSTU Recording Studio + rgad Replay Gain Adjustment + + */ + + return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long'); + + // Last three: + // from Helium2 [www.helium2.com] + // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html + } + + + function FrameNameShortLookup($framename) { + + $begin = __LINE__; + + /** This is not a comment! + + COM comment + COMM comment + TAL album + TALB album + TBP bpm + TBPM bpm + TCM composer + TCO genre + TCOM composer + TCON genre + TCOP copyright + TCR copyright + TEN encoded_by + TENC encoded_by + TEXT lyricist + TIT1 description + TIT2 title + TIT3 subtitle + TLA language + TLAN language + TLE length + TLEN length + TMOO mood + TOA original_artist + TOAL original_album + TOF original_filename + TOFN original_filename + TOL original_lyricist + TOLY original_lyricist + TOPE original_artist + TOT original_album + TP1 artist + TP2 band + TP3 conductor + TP4 remixer + TPB publisher + TPE1 artist + TPE2 band + TPE3 conductor + TPE4 remixer + TPUB publisher + TRC isrc + TRCK track + TRK track + TSI size + TSIZ size + TSRC isrc + TSS encoder_settings + TSSE encoder_settings + TSST subtitle + TT1 description + TT2 title + TT3 subtitle + TXT lyricist + TXX text + TXXX text + TYE year + TYER year + UFI unique_file_identifier + UFID unique_file_identifier + ULT unsychronised_lyric + USER terms_of_use + USLT unsynchronised lyric + WAF url_file + WAR url_artist + WAS url_source + WCOP copyright + WCP copyright + WOAF url_file + WOAR url_artist + WOAS url_source + WORS url_station + WPB url_publisher + WPUB url_publisher + WXX url_user + WXXX url_user + TFEA featured_artist + TSTU studio + + */ + + return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short'); + } + + function TextEncodingTerminatorLookup($encoding) { + // http://www.id3.org/id3v2.4.0-structure.txt + // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings: + // $00 ISO-8859-1. Terminated with $00. + // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00. + // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00. + // $03 UTF-8 encoded Unicode. Terminated with $00. + + static $TextEncodingTerminatorLookup = array(0=>"\x00", 1=>"\x00\x00", 2=>"\x00\x00", 3=>"\x00", 255=>"\x00\x00"); + + return @$TextEncodingTerminatorLookup[$encoding]; + } + + function TextEncodingNameLookup($encoding) { + // http://www.id3.org/id3v2.4.0-structure.txt + static $TextEncodingNameLookup = array(0=>'ISO-8859-1', 1=>'UTF-16', 2=>'UTF-16BE', 3=>'UTF-8', 255=>'UTF-16BE'); + return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1'); + } + + function IsValidID3v2FrameName($framename, $id3v2majorversion) { + switch ($id3v2majorversion) { + case 2: + return ereg('[A-Z][A-Z0-9]{2}', $framename); + break; + + case 3: + case 4: + return ereg('[A-Z][A-Z0-9]{3}', $framename); + break; + } + return false; + } + + function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) { + for ($i = 0; $i < strlen($numberstring); $i++) { + if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) { + if (($numberstring{$i} == '.') && $allowdecimal) { + // allowed + } elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) { + // allowed + } else { + return false; + } + } + } + return true; + } + + function IsValidDateStampString($datestamp) { + if (strlen($datestamp) != 8) { + return false; + } + if (!$this->IsANumber($datestamp, false)) { + return false; + } + $year = substr($datestamp, 0, 4); + $month = substr($datestamp, 4, 2); + $day = substr($datestamp, 6, 2); + if (($year == 0) || ($month == 0) || ($day == 0)) { + return false; + } + if ($month > 12) { + return false; + } + if ($day > 31) { + return false; + } + if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) { + return false; + } + if (($day > 29) && ($month == 2)) { + return false; + } + return true; + } + + function ID3v2HeaderLength($majorversion) { + return (($majorversion == 2) ? 6 : 10); + } + +} + +?> diff --git a/modules/id3/getid3/module.tag.lyrics3.php b/modules/id3/getid3/module.tag.lyrics3.php new file mode 100644 index 00000000..e735d6d9 --- /dev/null +++ b/modules/id3/getid3/module.tag.lyrics3.php @@ -0,0 +1,271 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +/// // +// module.tag.lyrics3.php // +// module for analyzing Lyrics3 tags // +// dependencies: module.tag.apetag.php (optional) // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_lyrics3 +{ + + function getid3_lyrics3(&$fd, &$ThisFileInfo) { + // http://www.volweb.cz/str/tags.htm + + fseek($fd, (0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - LYRICSEND - [Lyrics3size] + $lyrics3_id3v1 = fread($fd, 128 + 9 + 6); + $lyrics3lsz = substr($lyrics3_id3v1, 0, 6); // Lyrics3size + $lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200 + $id3v1tag = substr($lyrics3_id3v1, 15, 128); // ID3v1 + + if ($lyrics3end == 'LYRICSEND') { + // Lyrics3v1, ID3v1, no APE + + $lyrics3size = 5100; + $lyrics3offset = $ThisFileInfo['filesize'] - 128 - $lyrics3size; + $lyrics3version = 1; + + } elseif ($lyrics3end == 'LYRICS200') { + // Lyrics3v2, ID3v1, no APE + + // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' + $lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); + $lyrics3offset = $ThisFileInfo['filesize'] - 128 - $lyrics3size; + $lyrics3version = 2; + + } elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICSEND')) { + // Lyrics3v1, no ID3v1, no APE + + $lyrics3size = 5100; + $lyrics3offset = $ThisFileInfo['filesize'] - $lyrics3size; + $lyrics3version = 1; + $lyrics3offset = $ThisFileInfo['filesize'] - $lyrics3size; + + } elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICS200')) { + + // Lyrics3v2, no ID3v1, no APE + + $lyrics3size = strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' + $lyrics3offset = $ThisFileInfo['filesize'] - $lyrics3size; + $lyrics3version = 2; + + } else { + + if (isset($ThisFileInfo['ape']['tag_offset_start']) && ($ThisFileInfo['ape']['tag_offset_start'] > 15)) { + + fseek($fd, $ThisFileInfo['ape']['tag_offset_start'] - 15, SEEK_SET); + $lyrics3lsz = fread($fd, 6); + $lyrics3end = fread($fd, 9); + + if ($lyrics3end == 'LYRICSEND') { + // Lyrics3v1, APE, maybe ID3v1 + + $lyrics3size = 5100; + $lyrics3offset = $ThisFileInfo['ape']['tag_offset_start'] - $lyrics3size; + $ThisFileInfo['avdataend'] = $lyrics3offset; + $lyrics3version = 1; + $ThisFileInfo['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability'; + + } elseif ($lyrics3end == 'LYRICS200') { + // Lyrics3v2, APE, maybe ID3v1 + + $lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200' + $lyrics3offset = $ThisFileInfo['ape']['tag_offset_start'] - $lyrics3size; + $lyrics3version = 2; + $ThisFileInfo['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability'; + + } + + } + + } + + if (isset($lyrics3offset)) { + $ThisFileInfo['avdataend'] = $lyrics3offset; + $this->getLyrics3Data($ThisFileInfo, $fd, $lyrics3offset, $lyrics3version, $lyrics3size); + + if (!isset($ThisFileInfo['ape'])) { + $GETID3_ERRORARRAY = &$ThisFileInfo['warning']; + if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, false)) { + $tag = new getid3_apetag($fd, $ThisFileInfo, $ThisFileInfo['lyrics3']['tag_offset_start']); + } + } + + } + + return true; + } + + function getLyrics3Data(&$ThisFileInfo, &$fd, $endoffset, $version, $length) { + // http://www.volweb.cz/str/tags.htm + + fseek($fd, $endoffset, SEEK_SET); + if ($length <= 0) { + return false; + } + $rawdata = fread($fd, $length); + + if (substr($rawdata, 0, 11) != 'LYRICSBEGIN') { + if (strpos($rawdata, 'LYRICSBEGIN') !== false) { + + $ThisFileInfo['warning'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version; + $ThisFileInfo['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN'); + $ParsedLyrics3['tag_offset_start'] = $ThisFileInfo['avdataend']; + $rawdata = substr($rawdata, strpos($rawdata, 'LYRICSBEGIN')); + $length = strlen($rawdata); + + } else { + + $ThisFileInfo['error'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead'; + return false; + + } + + } + + $ParsedLyrics3['raw']['lyrics3version'] = $version; + $ParsedLyrics3['raw']['lyrics3tagsize'] = $length; + $ParsedLyrics3['tag_offset_start'] = $endoffset; + $ParsedLyrics3['tag_offset_end'] = $endoffset + $length; + + switch ($version) { + + case 1: + if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICSEND') { + $ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9)); + $this->Lyrics3LyricsTimestampParse($ParsedLyrics3); + } else { + $ThisFileInfo['error'][] = '"LYRICSEND" expected at '.(ftell($fd) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; + return false; + } + break; + + case 2: + if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICS200') { + $ParsedLyrics3['raw']['unparsed'] = substr($rawdata, 11, strlen($rawdata) - 11 - 9 - 6); // LYRICSBEGIN + LYRICS200 + LSZ + $rawdata = $ParsedLyrics3['raw']['unparsed']; + while (strlen($rawdata) > 0) { + $fieldname = substr($rawdata, 0, 3); + $fieldsize = (int) substr($rawdata, 3, 5); + $ParsedLyrics3['raw'][$fieldname] = substr($rawdata, 8, $fieldsize); + $rawdata = substr($rawdata, 3 + 5 + $fieldsize); + } + + if (isset($ParsedLyrics3['raw']['IND'])) { + $i = 0; + $flagnames = array('lyrics', 'timestamps', 'inhibitrandom'); + foreach ($flagnames as $flagname) { + if (strlen($ParsedLyrics3['raw']['IND']) > ++$i) { + $ParsedLyrics3['flags'][$flagname] = $this->IntString2Bool(substr($ParsedLyrics3['raw']['IND'], $i, 1)); + } + } + } + + $fieldnametranslation = array('ETT'=>'title', 'EAR'=>'artist', 'EAL'=>'album', 'INF'=>'comment', 'AUT'=>'author'); + foreach ($fieldnametranslation as $key => $value) { + if (isset($ParsedLyrics3['raw'][$key])) { + $ParsedLyrics3['comments'][$value][] = trim($ParsedLyrics3['raw'][$key]); + } + } + + if (isset($ParsedLyrics3['raw']['IMG'])) { + $imagestrings = explode("\r\n", $ParsedLyrics3['raw']['IMG']); + foreach ($imagestrings as $key => $imagestring) { + if (strpos($imagestring, '||') !== false) { + $imagearray = explode('||', $imagestring); + $ParsedLyrics3['images'][$key]['filename'] = $imagearray[0]; + $ParsedLyrics3['images'][$key]['description'] = $imagearray[1]; + $ParsedLyrics3['images'][$key]['timestamp'] = $this->Lyrics3Timestamp2Seconds($imagearray[2]); + } + } + } + if (isset($ParsedLyrics3['raw']['LYR'])) { + $this->Lyrics3LyricsTimestampParse($ParsedLyrics3); + } + } else { + $ThisFileInfo['error'][] = '"LYRICS200" expected at '.(ftell($fd) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead'; + return false; + } + break; + + default: + $ThisFileInfo['error'][] = 'Cannot process Lyrics3 version '.$version.' (only v1 and v2)'; + return false; + break; + } + + + if (isset($ThisFileInfo['id3v1']['tag_offset_start']) && ($ThisFileInfo['id3v1']['tag_offset_start'] < $ParsedLyrics3['tag_offset_end'])) { + $ThisFileInfo['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data'; + unset($ThisFileInfo['id3v1']); + foreach ($ThisFileInfo['warning'] as $key => $value) { + if ($value == 'Some ID3v1 fields do not use NULL characters for padding') { + unset($ThisFileInfo['warning'][$key]); + sort($ThisFileInfo['warning']); + break; + } + } + } + + $ThisFileInfo['lyrics3'] = $ParsedLyrics3; + + return true; + } + + function Lyrics3Timestamp2Seconds($rawtimestamp) { + if (ereg('^\\[([0-9]{2}):([0-9]{2})\\]$', $rawtimestamp, $regs)) { + return (int) (($regs[1] * 60) + $regs[2]); + } + return false; + } + + function Lyrics3LyricsTimestampParse(&$Lyrics3data) { + $lyricsarray = explode("\r\n", $Lyrics3data['raw']['LYR']); + foreach ($lyricsarray as $key => $lyricline) { + $regs = array(); + unset($thislinetimestamps); + while (ereg('^(\\[[0-9]{2}:[0-9]{2}\\])', $lyricline, $regs)) { + $thislinetimestamps[] = $this->Lyrics3Timestamp2Seconds($regs[0]); + $lyricline = str_replace($regs[0], '', $lyricline); + } + $notimestamplyricsarray[$key] = $lyricline; + if (isset($thislinetimestamps) && is_array($thislinetimestamps)) { + sort($thislinetimestamps); + foreach ($thislinetimestamps as $timestampkey => $timestamp) { + if (isset($Lyrics3data['synchedlyrics'][$timestamp])) { + // timestamps only have a 1-second resolution, it's possible that multiple lines + // could have the same timestamp, if so, append + $Lyrics3data['synchedlyrics'][$timestamp] .= "\r\n".$lyricline; + } else { + $Lyrics3data['synchedlyrics'][$timestamp] = $lyricline; + } + } + } + } + $Lyrics3data['unsynchedlyrics'] = implode("\r\n", $notimestamplyricsarray); + if (isset($Lyrics3data['synchedlyrics']) && is_array($Lyrics3data['synchedlyrics'])) { + ksort($Lyrics3data['synchedlyrics']); + } + return true; + } + + function IntString2Bool($char) { + if ($char == '1') { + return true; + } elseif ($char == '0') { + return false; + } + return null; + } +} + + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/write.apetag.php b/modules/id3/getid3/write.apetag.php new file mode 100644 index 00000000..5fdf91ea --- /dev/null +++ b/modules/id3/getid3/write.apetag.php @@ -0,0 +1,227 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// write.apetag.php // +// module for writing APE tags // +// dependencies: module.tag.apetag.php // +// /// +///////////////////////////////////////////////////////////////// + + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true); + +class getid3_write_apetag +{ + + var $filename; + var $tag_data; + var $always_preserve_replaygain = true; // ReplayGain / MP3gain tags will be copied from old tag even if not passed in data + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + + function getid3_write_apetag() { + return true; + } + + function WriteAPEtag() { + // NOTE: All data passed to this function must be UTF-8 format + + $getID3 = new getID3; + $ThisFileInfo = $getID3->analyze($this->filename); + + if (isset($ThisFileInfo['ape']['tag_offset_start']) && isset($ThisFileInfo['lyrics3']['tag_offset_end'])) { + if ($ThisFileInfo['ape']['tag_offset_start'] >= $ThisFileInfo['lyrics3']['tag_offset_end']) { + // Current APE tag between Lyrics3 and ID3v1/EOF + // This break Lyrics3 functionality + if (!$this->DeleteAPEtag()) { + return false; + } + $ThisFileInfo = $getID3->analyze($this->filename); + } + } + + if ($this->always_preserve_replaygain) { + $ReplayGainTagsToPreserve = array('mp3gain_minmax', 'mp3gain_album_minmax', 'mp3gain_undo', 'replaygain_track_peak', 'replaygain_track_gain', 'replaygain_album_peak', 'replaygain_album_gain'); + foreach ($ReplayGainTagsToPreserve as $rg_key) { + if (isset($ThisFileInfo['ape']['items'][strtolower($rg_key)]['data'][0]) && !isset($this->tag_data[strtoupper($rg_key)][0])) { + $this->tag_data[strtoupper($rg_key)][0] = $ThisFileInfo['ape']['items'][strtolower($rg_key)]['data'][0]; + } + } + } + + if ($APEtag = $this->GenerateAPEtag()) { + if ($fp = @fopen($this->filename, 'a+b')) { + $oldignoreuserabort = ignore_user_abort(true); + flock($fp, LOCK_EX); + + $PostAPEdataOffset = $ThisFileInfo['avdataend']; + if (isset($ThisFileInfo['ape']['tag_offset_end'])) { + $PostAPEdataOffset = max($PostAPEdataOffset, $ThisFileInfo['ape']['tag_offset_end']); + } + if (isset($ThisFileInfo['lyrics3']['tag_offset_start'])) { + $PostAPEdataOffset = max($PostAPEdataOffset, $ThisFileInfo['lyrics3']['tag_offset_start']); + } + fseek($fp, $PostAPEdataOffset, SEEK_SET); + $PostAPEdata = ''; + if ($ThisFileInfo['filesize'] > $PostAPEdataOffset) { + $PostAPEdata = fread($fp, $ThisFileInfo['filesize'] - $PostAPEdataOffset); + } + + fseek($fp, $PostAPEdataOffset, SEEK_SET); + if (isset($ThisFileInfo['ape']['tag_offset_start'])) { + fseek($fp, $ThisFileInfo['ape']['tag_offset_start'], SEEK_SET); + } + ftruncate($fp, ftell($fp)); + fwrite($fp, $APEtag, strlen($APEtag)); + if (!empty($PostAPEdata)) { + fwrite($fp, $PostAPEdata, strlen($PostAPEdata)); + } + flock($fp, LOCK_UN); + fclose($fp); + ignore_user_abort($oldignoreuserabort); + return true; + + } + return false; + } + return false; + } + + function DeleteAPEtag() { + $getID3 = new getID3; + $ThisFileInfo = $getID3->analyze($this->filename); + if (isset($ThisFileInfo['ape']['tag_offset_start']) && isset($ThisFileInfo['ape']['tag_offset_end'])) { + if ($fp = @fopen($this->filename, 'a+b')) { + + flock($fp, LOCK_EX); + $oldignoreuserabort = ignore_user_abort(true); + + fseek($fp, $ThisFileInfo['ape']['tag_offset_end'], SEEK_SET); + $DataAfterAPE = ''; + if ($ThisFileInfo['filesize'] > $ThisFileInfo['ape']['tag_offset_end']) { + $DataAfterAPE = fread($fp, $ThisFileInfo['filesize'] - $ThisFileInfo['ape']['tag_offset_end']); + } + + ftruncate($fp, $ThisFileInfo['ape']['tag_offset_start']); + fseek($fp, $ThisFileInfo['ape']['tag_offset_start'], SEEK_SET); + + if (!empty($DataAfterAPE)) { + fwrite($fp, $DataAfterAPE, strlen($DataAfterAPE)); + } + + flock($fp, LOCK_UN); + fclose($fp); + ignore_user_abort($oldignoreuserabort); + + return true; + + } + } + return false; + } + + + function GenerateAPEtag() { + // NOTE: All data passed to this function must be UTF-8 format + + $items = array(); + if (!is_array($this->tag_data)) { + return false; + } + foreach ($this->tag_data as $key => $arrayofvalues) { + if (!is_array($arrayofvalues)) { + return false; + } + + $valuestring = ''; + foreach ($arrayofvalues as $value) { + $valuestring .= str_replace("\x00", '', $value)."\x00"; + } + $valuestring = rtrim($valuestring, "\x00"); + + // Length of the assigned value in bytes + $tagitem = getid3_lib::LittleEndian2String(strlen($valuestring), 4); + + //$tagitem .= $this->GenerateAPEtagFlags(true, true, false, 0, false); + $tagitem .= "\x00\x00\x00\x00"; + + $tagitem .= $this->CleanAPEtagItemKey($key)."\x00"; + $tagitem .= $valuestring; + + $items[] = $tagitem; + + } + + return $this->GenerateAPEtagHeaderFooter($items, true).implode('', $items).$this->GenerateAPEtagHeaderFooter($items, false); + } + + function GenerateAPEtagHeaderFooter(&$items, $isheader=false) { + $tagdatalength = 0; + foreach ($items as $itemdata) { + $tagdatalength += strlen($itemdata); + } + + $APEheader = 'APETAGEX'; + $APEheader .= getid3_lib::LittleEndian2String(2000, 4); + $APEheader .= getid3_lib::LittleEndian2String(32 + $tagdatalength, 4); + $APEheader .= getid3_lib::LittleEndian2String(count($items), 4); + $APEheader .= $this->GenerateAPEtagFlags(true, true, $isheader, 0, false); + $APEheader .= str_repeat("\x00", 8); + + return $APEheader; + } + + function GenerateAPEtagFlags($header=true, $footer=true, $isheader=false, $encodingid=0, $readonly=false) { + $APEtagFlags = array_fill(0, 4, 0); + if ($header) { + $APEtagFlags[0] |= 0x80; // Tag contains a header + } + if (!$footer) { + $APEtagFlags[0] |= 0x40; // Tag contains no footer + } + if ($isheader) { + $APEtagFlags[0] |= 0x20; // This is the header, not the footer + } + + // 0: Item contains text information coded in UTF-8 + // 1: Item contains binary information °) + // 2: Item is a locator of external stored information °°) + // 3: reserved + $APEtagFlags[3] |= ($encodingid << 1); + + if ($readonly) { + $APEtagFlags[3] |= 0x01; // Tag or Item is Read Only + } + + return chr($APEtagFlags[3]).chr($APEtagFlags[2]).chr($APEtagFlags[1]).chr($APEtagFlags[0]); + } + + function CleanAPEtagItemKey($itemkey) { + $itemkey = eregi_replace("[^\x20-\x7E]", '', $itemkey); + + // http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html + switch (strtoupper($itemkey)) { + case 'EAN/UPC': + case 'ISBN': + case 'LC': + case 'ISRC': + $itemkey = strtoupper($itemkey); + break; + + default: + $itemkey = ucwords($itemkey); + break; + } + return $itemkey; + + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/write.id3v1.php b/modules/id3/getid3/write.id3v1.php new file mode 100644 index 00000000..21252148 --- /dev/null +++ b/modules/id3/getid3/write.id3v1.php @@ -0,0 +1,104 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// write.id3v1.php // +// module for writing ID3v1 tags // +// dependencies: module.tag.id3v1.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true); + +class getid3_write_id3v1 +{ + var $filename; + var $tag_data; + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + + function getid3_write_id3v1() { + return true; + } + + function WriteID3v1() { + // File MUST be writeable - CHMOD(646) at least + if (is_writeable($this->filename)) { + if ($fp_source = @fopen($this->filename, 'r+b')) { + + fseek($fp_source, -128, SEEK_END); + if (fread($fp_source, 3) == 'TAG') { + fseek($fp_source, -128, SEEK_END); // overwrite existing ID3v1 tag + } else { + fseek($fp_source, 0, SEEK_END); // append new ID3v1 tag + } + + $new_id3v1_tag_data = getid3_id3v1::GenerateID3v1Tag( + @$this->tag_data['title'], + @$this->tag_data['artist'], + @$this->tag_data['album'], + @$this->tag_data['year'], + @$this->tag_data['genreid'], + @$this->tag_data['comment'], + @$this->tag_data['track']); + fwrite($fp_source, $new_id3v1_tag_data, 128); + fclose($fp_source); + return true; + + } else { + $this->errors[] = 'Could not open '.$this->filename.' mode "r+b"'; + return false; + } + } + $this->errors[] = 'File is not writeable: '.$this->filename; + return false; + } + + function FixID3v1Padding() { + // ID3v1 data is supposed to be padded with NULL characters, but some taggers incorrectly use spaces + // This function rewrites the ID3v1 tag with correct padding + + // Initialize getID3 engine + $getID3 = new getID3; + $ThisFileInfo = $getID3->analyze($this->filename); + if (isset($ThisFileInfo['tags']['id3v1'])) { + foreach ($ThisFileInfo['tags']['id3v1'] as $key => $value) { + $id3v1data[$key] = implode(',', $value); + } + $this->tag_data = $id3v1data; + return $this->WriteID3v1(); + } + return false; + } + + function RemoveID3v1() { + // File MUST be writeable - CHMOD(646) at least + if (is_writeable($this->filename)) { + if ($fp_source = @fopen($this->filename, 'r+b')) { + + fseek($fp_source, -128, SEEK_END); + if (fread($fp_source, 3) == 'TAG') { + ftruncate($fp_source, filesize($this->filename) - 128); + } else { + // no ID3v1 tag to begin with - do nothing + } + fclose($fp_source); + return true; + + } else { + $this->errors[] = 'Could not open '.$this->filename.' mode "r+b"'; + } + } else { + $this->errors[] = $this->filename.' is not writeable'; + } + return false; + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/write.id3v2.php b/modules/id3/getid3/write.id3v2.php new file mode 100644 index 00000000..4f15a722 --- /dev/null +++ b/modules/id3/getid3/write.id3v2.php @@ -0,0 +1,2028 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +/// // +// write.id3v2.php // +// module for writing ID3v2 tags // +// dependencies: module.tag.id3v2.php // +// /// +///////////////////////////////////////////////////////////////// + +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true); + +class getid3_write_id3v2 +{ + var $filename; + var $tag_data; + var $paddedlength = 4096; // minimum length of ID3v2 tag in bytes + var $majorversion = 3; // ID3v2 major version (2, 3 (recommended), 4) + var $minorversion = 0; // ID3v2 minor version - always 0 + var $merge_existing_data = false; // if true, merge new data with existing tags; if false, delete old tag data and only write new tags + var $id3v2_default_encodingid = 0; // default text encoding (ISO-8859-1) if not explicitly passed + var $id3v2_use_unsynchronisation = false; // the specs say it should be TRUE, but most other ID3v2-aware programs are broken if unsynchronization is used, so by default don't use it. + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + + function getid3_write_id3v2() { + return true; + } + + function WriteID3v2() { + // File MUST be writeable - CHMOD(646) at least. It's best if the + // directory is also writeable, because that method is both faster and less susceptible to errors. + + if (is_writeable($this->filename) || (!file_exists($this->filename) && is_writeable(dirname($this->filename)))) { + // Initialize getID3 engine + $getID3 = new getID3; + $OldThisFileInfo = $getID3->analyze($this->filename); + if ($this->merge_existing_data) { + // merge with existing data + if (!empty($OldThisFileInfo['id3v2'])) { + $this->tag_data = $this->array_join_merge($OldThisFileInfo['id3v2'], $this->tag_data); + } + } + $this->paddedlength = max(@$OldThisFileInfo['id3v2']['headerlength'], $this->paddedlength); + + if ($NewID3v2Tag = $this->GenerateID3v2Tag()) { + if (file_exists($this->filename) && is_writeable($this->filename) && isset($OldThisFileInfo['id3v2']['headerlength']) && ($OldThisFileInfo['id3v2']['headerlength'] == strlen($NewID3v2Tag))) { + + // best and fastest method - insert-overwrite existing tag (padded to length of old tag if neccesary) + if (file_exists($this->filename)) { + + ob_start(); + if ($fp = fopen($this->filename, 'r+b')) { + rewind($fp); + fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag)); + fclose($fp); + } else { + $this->errors[] = 'Could not open '.$this->filename.' mode "r+b" - '.strip_tags(ob_get_contents()); + } + ob_end_clean(); + + } else { + + ob_start(); + if ($fp = fopen($this->filename, 'wb')) { + rewind($fp); + fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag)); + fclose($fp); + } else { + $this->errors[] = 'Could not open '.$this->filename.' mode "wb" - '.strip_tags(ob_get_contents()); + } + ob_end_clean(); + + } + + } else { + + if ($tempfilename = tempnam('*', 'getID3')) { + ob_start(); + if ($fp_source = fopen($this->filename, 'rb')) { + if ($fp_temp = fopen($tempfilename, 'wb')) { + + fwrite($fp_temp, $NewID3v2Tag, strlen($NewID3v2Tag)); + + rewind($fp_source); + if (!empty($OldThisFileInfo['avdataoffset'])) { + fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET); + } + + while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE)) { + fwrite($fp_temp, $buffer, strlen($buffer)); + } + + fclose($fp_temp); + fclose($fp_source); + copy($tempfilename, $this->filename); + unlink($tempfilename); + return true; + + } else { + + $this->errors[] = 'Could not open '.$tempfilename.' mode "wb" - '.strip_tags(ob_get_contents()); + + } + fclose($fp_source); + + } else { + + $this->errors[] = 'Could not open '.$this->filename.' mode "rb" - '.strip_tags(ob_get_contents()); + + } + ob_end_clean(); + } + return false; + + } + + } else { + + $this->errors[] = '$this->GenerateID3v2Tag() failed'; + + } + + if (!empty($this->errors)) { + return false; + } + return true; + } else { + $this->errors[] = '!is_writeable('.$this->filename.')'; + } + return false; + } + + function RemoveID3v2() { + + // File MUST be writeable - CHMOD(646) at least. It's best if the + // directory is also writeable, because that method is both faster and less susceptible to errors. + if (is_writeable(dirname($this->filename))) { + + // preferred method - only one copying operation, minimal chance of corrupting + // original file if script is interrupted, but required directory to be writeable + if ($fp_source = @fopen($this->filename, 'rb')) { + // Initialize getID3 engine + $getID3 = new getID3; + $OldThisFileInfo = $getID3->analyze($this->filename); + rewind($fp_source); + if ($OldThisFileInfo['avdataoffset'] !== false) { + fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET); + } + if ($fp_temp = @fopen($this->filename.'getid3tmp', 'w+b')) { + while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE)) { + fwrite($fp_temp, $buffer, strlen($buffer)); + } + fclose($fp_temp); + } else { + $this->errors[] = 'Could not open '.$this->filename.'getid3tmp mode "w+b"'; + } + fclose($fp_source); + } else { + $this->errors[] = 'Could not open '.$this->filename.' mode "rb"'; + } + if (file_exists($this->filename)) { + unlink($this->filename); + } + rename($this->filename.'getid3tmp', $this->filename); + + } elseif (is_writable($this->filename)) { + + // less desirable alternate method - double-copies the file, overwrites original file + // and could corrupt source file if the script is interrupted or an error occurs. + if ($fp_source = @fopen($this->filename, 'rb')) { + // Initialize getID3 engine + $getID3 = new getID3; + $OldThisFileInfo = $getID3->analyze($this->filename); + rewind($fp_source); + if ($OldThisFileInfo['avdataoffset'] !== false) { + fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET); + } + if ($fp_temp = tmpfile()) { + while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE)) { + fwrite($fp_temp, $buffer, strlen($buffer)); + } + fclose($fp_source); + if ($fp_source = @fopen($this->filename, 'wb')) { + rewind($fp_temp); + while ($buffer = fread($fp_temp, GETID3_FREAD_BUFFER_SIZE)) { + fwrite($fp_source, $buffer, strlen($buffer)); + } + fseek($fp_temp, -128, SEEK_END); + fclose($fp_source); + } else { + $this->errors[] = 'Could not open '.$this->filename.' mode "wb"'; + } + fclose($fp_temp); + } else { + $this->errors[] = 'Could not create tmpfile()'; + } + } else { + $this->errors[] = 'Could not open '.$this->filename.' mode "rb"'; + } + + } else { + + $this->errors[] = 'Directory and file both not writeable'; + + } + + if (!empty($this->errors)) { + return false; + } + return true; + } + + + function GenerateID3v2TagFlags($flags) { + switch ($this->majorversion) { + case 4: + // %abcd0000 + $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation + $flag .= (@$flags['extendedheader'] ? '1' : '0'); // b - Extended header + $flag .= (@$flags['experimental'] ? '1' : '0'); // c - Experimental indicator + $flag .= (@$flags['footer'] ? '1' : '0'); // d - Footer present + $flag .= '0000'; + break; + + case 3: + // %abc00000 + $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation + $flag .= (@$flags['extendedheader'] ? '1' : '0'); // b - Extended header + $flag .= (@$flags['experimental'] ? '1' : '0'); // c - Experimental indicator + $flag .= '00000'; + break; + + case 2: + // %ab000000 + $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation + $flag .= (@$flags['compression'] ? '1' : '0'); // b - Compression + $flag .= '000000'; + break; + + default: + return false; + break; + } + return chr(bindec($flag)); + } + + + function GenerateID3v2FrameFlags($TagAlter=false, $FileAlter=false, $ReadOnly=false, $Compression=false, $Encryption=false, $GroupingIdentity=false, $Unsynchronisation=false, $DataLengthIndicator=false) { + switch ($this->majorversion) { + case 4: + // %0abc0000 %0h00kmnp + $flag1 = '0'; + $flag1 .= $TagAlter ? '1' : '0'; // a - Tag alter preservation (true == discard) + $flag1 .= $FileAlter ? '1' : '0'; // b - File alter preservation (true == discard) + $flag1 .= $ReadOnly ? '1' : '0'; // c - Read only (true == read only) + $flag1 .= '0000'; + + $flag2 = '0'; + $flag2 .= $GroupingIdentity ? '1' : '0'; // h - Grouping identity (true == contains group information) + $flag2 .= '00'; + $flag2 .= $Compression ? '1' : '0'; // k - Compression (true == compressed) + $flag2 .= $Encryption ? '1' : '0'; // m - Encryption (true == encrypted) + $flag2 .= $Unsynchronisation ? '1' : '0'; // n - Unsynchronisation (true == unsynchronised) + $flag2 .= $DataLengthIndicator ? '1' : '0'; // p - Data length indicator (true == data length indicator added) + break; + + case 3: + // %abc00000 %ijk00000 + $flag1 = $TagAlter ? '1' : '0'; // a - Tag alter preservation (true == discard) + $flag1 .= $FileAlter ? '1' : '0'; // b - File alter preservation (true == discard) + $flag1 .= $ReadOnly ? '1' : '0'; // c - Read only (true == read only) + $flag1 .= '00000'; + + $flag2 = $Compression ? '1' : '0'; // i - Compression (true == compressed) + $flag2 .= $Encryption ? '1' : '0'; // j - Encryption (true == encrypted) + $flag2 .= $GroupingIdentity ? '1' : '0'; // k - Grouping identity (true == contains group information) + $flag2 .= '00000'; + break; + + default: + return false; + break; + + } + return chr(bindec($flag1)).chr(bindec($flag2)); + } + + function GenerateID3v2FrameData($frame_name, $source_data_array) { + if (!getid3_id3v2::IsValidID3v2FrameName($frame_name, $this->majorversion)) { + return false; + } + $framedata = ''; + + if (($this->majorversion < 3) || ($this->majorversion > 4)) { + + $this->errors[] = 'Only ID3v2.3 and ID3v2.4 are supported in GenerateID3v2FrameData()'; + + } else { // $this->majorversion 3 or 4 + + switch ($frame_name) { + case 'UFID': + // 4.1 UFID Unique file identifier + // Owner identifier <text string> $00 + // Identifier <up to 64 bytes binary data> + if (strlen($source_data_array['data']) > 64) { + $this->errors[] = 'Identifier not allowed to be longer than 64 bytes in '.$frame_name.' (supplied data was '.strlen($source_data_array['data']).' bytes long)'; + } else { + $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00"; + $framedata .= substr($source_data_array['data'], 0, 64); // max 64 bytes - truncate anything longer + } + break; + + case 'TXXX': + // 4.2.2 TXXX User defined text information frame + // Text encoding $xx + // Description <text string according to encoding> $00 (00) + // Value <text string according to encoding> + $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); + if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion)) { + $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; + } else { + $framedata .= chr($source_data_array['encodingid']); + $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); + $framedata .= $source_data_array['data']; + } + break; + + case 'WXXX': + // 4.3.2 WXXX User defined URL link frame + // Text encoding $xx + // Description <text string according to encoding> $00 (00) + // URL <text string> + $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); + if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion)) { + $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; + } elseif (!isset($source_data_array['data']) || !$this->IsValidURL($source_data_array['data'], false, false)) { + $this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')'; + } else { + $framedata .= chr($source_data_array['encodingid']); + $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); + $framedata .= $source_data_array['data']; + } + break; + + case 'IPLS': + // 4.4 IPLS Involved people list (ID3v2.3 only) + // Text encoding $xx + // People list strings <textstrings> + $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); + if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion)) { + $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; + } else { + $framedata .= chr($source_data_array['encodingid']); + $framedata .= $source_data_array['data']; + } + break; + + case 'MCDI': + // 4.4 MCDI Music CD identifier + // CD TOC <binary data> + $framedata .= $source_data_array['data']; + break; + + case 'ETCO': + // 4.5 ETCO Event timing codes + // Time stamp format $xx + // Where time stamp format is: + // $01 (32-bit value) MPEG frames from beginning of file + // $02 (32-bit value) milliseconds from beginning of file + // Followed by a list of key events in the following format: + // Type of event $xx + // Time stamp $xx (xx ...) + // The 'Time stamp' is set to zero if directly at the beginning of the sound + // or after the previous event. All events MUST be sorted in chronological order. + if (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1)) { + $this->errors[] = 'Invalid Time Stamp Format byte in '.$frame_name.' ('.$source_data_array['timestampformat'].')'; + } else { + $framedata .= chr($source_data_array['timestampformat']); + foreach ($source_data_array as $key => $val) { + if (!$this->ID3v2IsValidETCOevent($val['typeid'])) { + $this->errors[] = 'Invalid Event Type byte in '.$frame_name.' ('.$val['typeid'].')'; + } elseif (($key != 'timestampformat') && ($key != 'flags')) { + if (($val['timestamp'] > 0) && ($previousETCOtimestamp >= $val['timestamp'])) { + // The 'Time stamp' is set to zero if directly at the beginning of the sound + // or after the previous event. All events MUST be sorted in chronological order. + $this->errors[] = 'Out-of-order timestamp in '.$frame_name.' ('.$val['timestamp'].') for Event Type ('.$val['typeid'].')'; + } else { + $framedata .= chr($val['typeid']); + $framedata .= getid3_lib::BigEndian2String($val['timestamp'], 4, false); + } + } + } + } + break; + + case 'MLLT': + // 4.6 MLLT MPEG location lookup table + // MPEG frames between reference $xx xx + // Bytes between reference $xx xx xx + // Milliseconds between reference $xx xx xx + // Bits for bytes deviation $xx + // Bits for milliseconds dev. $xx + // Then for every reference the following data is included; + // Deviation in bytes %xxx.... + // Deviation in milliseconds %xxx.... + if (($source_data_array['framesbetweenreferences'] > 0) && ($source_data_array['framesbetweenreferences'] <= 65535)) { + $framedata .= getid3_lib::BigEndian2String($source_data_array['framesbetweenreferences'], 2, false); + } else { + $this->errors[] = 'Invalid MPEG Frames Between References in '.$frame_name.' ('.$source_data_array['framesbetweenreferences'].')'; + } + if (($source_data_array['bytesbetweenreferences'] > 0) && ($source_data_array['bytesbetweenreferences'] <= 16777215)) { + $framedata .= getid3_lib::BigEndian2String($source_data_array['bytesbetweenreferences'], 3, false); + } else { + $this->errors[] = 'Invalid bytes Between References in '.$frame_name.' ('.$source_data_array['bytesbetweenreferences'].')'; + } + if (($source_data_array['msbetweenreferences'] > 0) && ($source_data_array['msbetweenreferences'] <= 16777215)) { + $framedata .= getid3_lib::BigEndian2String($source_data_array['msbetweenreferences'], 3, false); + } else { + $this->errors[] = 'Invalid Milliseconds Between References in '.$frame_name.' ('.$source_data_array['msbetweenreferences'].')'; + } + if (!$this->IsWithinBitRange($source_data_array['bitsforbytesdeviation'], 8, false)) { + if (($source_data_array['bitsforbytesdeviation'] % 4) == 0) { + $framedata .= chr($source_data_array['bitsforbytesdeviation']); + } else { + $this->errors[] = 'Bits For Bytes Deviation in '.$frame_name.' ('.$source_data_array['bitsforbytesdeviation'].') must be a multiple of 4.'; + } + } else { + $this->errors[] = 'Invalid Bits For Bytes Deviation in '.$frame_name.' ('.$source_data_array['bitsforbytesdeviation'].')'; + } + if (!$this->IsWithinBitRange($source_data_array['bitsformsdeviation'], 8, false)) { + if (($source_data_array['bitsformsdeviation'] % 4) == 0) { + $framedata .= chr($source_data_array['bitsformsdeviation']); + } else { + $this->errors[] = 'Bits For Milliseconds Deviation in '.$frame_name.' ('.$source_data_array['bitsforbytesdeviation'].') must be a multiple of 4.'; + } + } else { + $this->errors[] = 'Invalid Bits For Milliseconds Deviation in '.$frame_name.' ('.$source_data_array['bitsformsdeviation'].')'; + } + foreach ($source_data_array as $key => $val) { + if (($key != 'framesbetweenreferences') && ($key != 'bytesbetweenreferences') && ($key != 'msbetweenreferences') && ($key != 'bitsforbytesdeviation') && ($key != 'bitsformsdeviation') && ($key != 'flags')) { + $unwrittenbitstream .= str_pad(getid3_lib::Dec2Bin($val['bytedeviation']), $source_data_array['bitsforbytesdeviation'], '0', STR_PAD_LEFT); + $unwrittenbitstream .= str_pad(getid3_lib::Dec2Bin($val['msdeviation']), $source_data_array['bitsformsdeviation'], '0', STR_PAD_LEFT); + } + } + for ($i = 0; $i < strlen($unwrittenbitstream); $i += 8) { + $highnibble = bindec(substr($unwrittenbitstream, $i, 4)) << 4; + $lownibble = bindec(substr($unwrittenbitstream, $i + 4, 4)); + $framedata .= chr($highnibble & $lownibble); + } + break; + + case 'SYTC': + // 4.7 SYTC Synchronised tempo codes + // Time stamp format $xx + // Tempo data <binary data> + // Where time stamp format is: + // $01 (32-bit value) MPEG frames from beginning of file + // $02 (32-bit value) milliseconds from beginning of file + if (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1)) { + $this->errors[] = 'Invalid Time Stamp Format byte in '.$frame_name.' ('.$source_data_array['timestampformat'].')'; + } else { + $framedata .= chr($source_data_array['timestampformat']); + foreach ($source_data_array as $key => $val) { + if (!$this->ID3v2IsValidETCOevent($val['typeid'])) { + $this->errors[] = 'Invalid Event Type byte in '.$frame_name.' ('.$val['typeid'].')'; + } elseif (($key != 'timestampformat') && ($key != 'flags')) { + if (($val['tempo'] < 0) || ($val['tempo'] > 510)) { + $this->errors[] = 'Invalid Tempo (max = 510) in '.$frame_name.' ('.$val['tempo'].') at timestamp ('.$val['timestamp'].')'; + } else { + if ($val['tempo'] > 255) { + $framedata .= chr(255); + $val['tempo'] -= 255; + } + $framedata .= chr($val['tempo']); + $framedata .= getid3_lib::BigEndian2String($val['timestamp'], 4, false); + } + } + } + } + break; + + case 'USLT': + // 4.8 USLT Unsynchronised lyric/text transcription + // Text encoding $xx + // Language $xx xx xx + // Content descriptor <text string according to encoding> $00 (00) + // Lyrics/text <full text string according to encoding> + $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); + if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { + $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; + } elseif (getid3_id3v2::LanguageLookup($source_data_array['language'], true) == '') { + $this->errors[] = 'Invalid Language in '.$frame_name.' ('.$source_data_array['language'].')'; + } else { + $framedata .= chr($source_data_array['encodingid']); + $framedata .= strtolower($source_data_array['language']); + $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); + $framedata .= $source_data_array['data']; + } + break; + + case 'SYLT': + // 4.9 SYLT Synchronised lyric/text + // Text encoding $xx + // Language $xx xx xx + // Time stamp format $xx + // $01 (32-bit value) MPEG frames from beginning of file + // $02 (32-bit value) milliseconds from beginning of file + // Content type $xx + // Content descriptor <text string according to encoding> $00 (00) + // Terminated text to be synced (typically a syllable) + // Sync identifier (terminator to above string) $00 (00) + // Time stamp $xx (xx ...) + $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); + if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { + $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; + } elseif (getid3_id3v2::LanguageLookup($source_data_array['language'], true) == '') { + $this->errors[] = 'Invalid Language in '.$frame_name.' ('.$source_data_array['language'].')'; + } elseif (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1)) { + $this->errors[] = 'Invalid Time Stamp Format byte in '.$frame_name.' ('.$source_data_array['timestampformat'].')'; + } elseif (!$this->ID3v2IsValidSYLTtype($source_data_array['contenttypeid'])) { + $this->errors[] = 'Invalid Content Type byte in '.$frame_name.' ('.$source_data_array['contenttypeid'].')'; + } elseif (!is_array($source_data_array['data'])) { + $this->errors[] = 'Invalid Lyric/Timestamp data in '.$frame_name.' (must be an array)'; + } else { + $framedata .= chr($source_data_array['encodingid']); + $framedata .= strtolower($source_data_array['language']); + $framedata .= chr($source_data_array['timestampformat']); + $framedata .= chr($source_data_array['contenttypeid']); + $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); + ksort($source_data_array['data']); + foreach ($source_data_array['data'] as $key => $val) { + $framedata .= $val['data'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); + $framedata .= getid3_lib::BigEndian2String($val['timestamp'], 4, false); + } + } + break; + + case 'COMM': + // 4.10 COMM Comments + // Text encoding $xx + // Language $xx xx xx + // Short content descrip. <text string according to encoding> $00 (00) + // The actual text <full text string according to encoding> + $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); + if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { + $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; + } elseif (getid3_id3v2::LanguageLookup($source_data_array['language'], true) == '') { + $this->errors[] = 'Invalid Language in '.$frame_name.' ('.$source_data_array['language'].')'; + } else { + $framedata .= chr($source_data_array['encodingid']); + $framedata .= strtolower($source_data_array['language']); + $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); + $framedata .= $source_data_array['data']; + } + break; + + case 'RVA2': + // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only) + // Identification <text string> $00 + // The 'identification' string is used to identify the situation and/or + // device where this adjustment should apply. The following is then + // repeated for every channel: + // Type of channel $xx + // Volume adjustment $xx xx + // Bits representing peak $xx + // Peak volume $xx (xx ...) + $framedata .= str_replace("\x00", '', $source_data_array['description'])."\x00"; + foreach ($source_data_array as $key => $val) { + if ($key != 'description') { + $framedata .= chr($val['channeltypeid']); + $framedata .= getid3_lib::BigEndian2String($val['volumeadjust'], 2, false, true); // signed 16-bit + if (!$this->IsWithinBitRange($source_data_array['bitspeakvolume'], 8, false)) { + $framedata .= chr($val['bitspeakvolume']); + if ($val['bitspeakvolume'] > 0) { + $framedata .= getid3_lib::BigEndian2String($val['peakvolume'], ceil($val['bitspeakvolume'] / 8), false, false); + } + } else { + $this->errors[] = 'Invalid Bits Representing Peak Volume in '.$frame_name.' ('.$val['bitspeakvolume'].') (range = 0 to 255)'; + } + } + } + break; + + case 'RVAD': + // 4.12 RVAD Relative volume adjustment (ID3v2.3 only) + // Increment/decrement %00fedcba + // Bits used for volume descr. $xx + // Relative volume change, right $xx xx (xx ...) // a + // Relative volume change, left $xx xx (xx ...) // b + // Peak volume right $xx xx (xx ...) + // Peak volume left $xx xx (xx ...) + // Relative volume change, right back $xx xx (xx ...) // c + // Relative volume change, left back $xx xx (xx ...) // d + // Peak volume right back $xx xx (xx ...) + // Peak volume left back $xx xx (xx ...) + // Relative volume change, center $xx xx (xx ...) // e + // Peak volume center $xx xx (xx ...) + // Relative volume change, bass $xx xx (xx ...) // f + // Peak volume bass $xx xx (xx ...) + if (!$this->IsWithinBitRange($source_data_array['bitsvolume'], 8, false)) { + $this->errors[] = 'Invalid Bits For Volume Description byte in '.$frame_name.' ('.$source_data_array['bitsvolume'].') (range = 1 to 255)'; + } else { + $incdecflag .= '00'; + $incdecflag .= $source_data_array['incdec']['right'] ? '1' : '0'; // a - Relative volume change, right + $incdecflag .= $source_data_array['incdec']['left'] ? '1' : '0'; // b - Relative volume change, left + $incdecflag .= $source_data_array['incdec']['rightrear'] ? '1' : '0'; // c - Relative volume change, right back + $incdecflag .= $source_data_array['incdec']['leftrear'] ? '1' : '0'; // d - Relative volume change, left back + $incdecflag .= $source_data_array['incdec']['center'] ? '1' : '0'; // e - Relative volume change, center + $incdecflag .= $source_data_array['incdec']['bass'] ? '1' : '0'; // f - Relative volume change, bass + $framedata .= chr(bindec($incdecflag)); + $framedata .= chr($source_data_array['bitsvolume']); + $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['right'], ceil($source_data_array['bitsvolume'] / 8), false); + $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['left'], ceil($source_data_array['bitsvolume'] / 8), false); + $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['right'], ceil($source_data_array['bitsvolume'] / 8), false); + $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['left'], ceil($source_data_array['bitsvolume'] / 8), false); + if ($source_data_array['volumechange']['rightrear'] || $source_data_array['volumechange']['leftrear'] || + $source_data_array['peakvolume']['rightrear'] || $source_data_array['peakvolume']['leftrear'] || + $source_data_array['volumechange']['center'] || $source_data_array['peakvolume']['center'] || + $source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass']) { + $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['rightrear'], ceil($source_data_array['bitsvolume']/8), false); + $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['leftrear'], ceil($source_data_array['bitsvolume']/8), false); + $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['rightrear'], ceil($source_data_array['bitsvolume']/8), false); + $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['leftrear'], ceil($source_data_array['bitsvolume']/8), false); + } + if ($source_data_array['volumechange']['center'] || $source_data_array['peakvolume']['center'] || + $source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass']) { + $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['center'], ceil($source_data_array['bitsvolume']/8), false); + $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['center'], ceil($source_data_array['bitsvolume']/8), false); + } + if ($source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass']) { + $framedata .= getid3_lib::BigEndian2String($source_data_array['volumechange']['bass'], ceil($source_data_array['bitsvolume']/8), false); + $framedata .= getid3_lib::BigEndian2String($source_data_array['peakvolume']['bass'], ceil($source_data_array['bitsvolume']/8), false); + } + } + break; + + case 'EQU2': + // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only) + // Interpolation method $xx + // $00 Band + // $01 Linear + // Identification <text string> $00 + // The following is then repeated for every adjustment point + // Frequency $xx xx + // Volume adjustment $xx xx + if (($source_data_array['interpolationmethod'] < 0) || ($source_data_array['interpolationmethod'] > 1)) { + $this->errors[] = 'Invalid Interpolation Method byte in '.$frame_name.' ('.$source_data_array['interpolationmethod'].') (valid = 0 or 1)'; + } else { + $framedata .= chr($source_data_array['interpolationmethod']); + $framedata .= str_replace("\x00", '', $source_data_array['description'])."\x00"; + foreach ($source_data_array['data'] as $key => $val) { + $framedata .= getid3_lib::BigEndian2String(intval(round($key * 2)), 2, false); + $framedata .= getid3_lib::BigEndian2String($val, 2, false, true); // signed 16-bit + } + } + break; + + case 'EQUA': + // 4.12 EQUA Equalisation (ID3v2.3 only) + // Adjustment bits $xx + // This is followed by 2 bytes + ('adjustment bits' rounded up to the + // nearest byte) for every equalisation band in the following format, + // giving a frequency range of 0 - 32767Hz: + // Increment/decrement %x (MSB of the Frequency) + // Frequency (lower 15 bits) + // Adjustment $xx (xx ...) + if (!$this->IsWithinBitRange($source_data_array['bitsvolume'], 8, false)) { + $this->errors[] = 'Invalid Adjustment Bits byte in '.$frame_name.' ('.$source_data_array['bitsvolume'].') (range = 1 to 255)'; + } else { + $framedata .= chr($source_data_array['adjustmentbits']); + foreach ($source_data_array as $key => $val) { + if ($key != 'bitsvolume') { + if (($key > 32767) || ($key < 0)) { + $this->errors[] = 'Invalid Frequency in '.$frame_name.' ('.$key.') (range = 0 to 32767)'; + } else { + if ($val >= 0) { + // put MSB of frequency to 1 if increment, 0 if decrement + $key |= 0x8000; + } + $framedata .= getid3_lib::BigEndian2String($key, 2, false); + $framedata .= getid3_lib::BigEndian2String($val, ceil($source_data_array['adjustmentbits'] / 8), false); + } + } + } + } + break; + + case 'RVRB': + // 4.13 RVRB Reverb + // Reverb left (ms) $xx xx + // Reverb right (ms) $xx xx + // Reverb bounces, left $xx + // Reverb bounces, right $xx + // Reverb feedback, left to left $xx + // Reverb feedback, left to right $xx + // Reverb feedback, right to right $xx + // Reverb feedback, right to left $xx + // Premix left to right $xx + // Premix right to left $xx + if (!$this->IsWithinBitRange($source_data_array['left'], 16, false)) { + $this->errors[] = 'Invalid Reverb Left in '.$frame_name.' ('.$source_data_array['left'].') (range = 0 to 65535)'; + } elseif (!$this->IsWithinBitRange($source_data_array['right'], 16, false)) { + $this->errors[] = 'Invalid Reverb Left in '.$frame_name.' ('.$source_data_array['right'].') (range = 0 to 65535)'; + } elseif (!$this->IsWithinBitRange($source_data_array['bouncesL'], 8, false)) { + $this->errors[] = 'Invalid Reverb Bounces, Left in '.$frame_name.' ('.$source_data_array['bouncesL'].') (range = 0 to 255)'; + } elseif (!$this->IsWithinBitRange($source_data_array['bouncesR'], 8, false)) { + $this->errors[] = 'Invalid Reverb Bounces, Right in '.$frame_name.' ('.$source_data_array['bouncesR'].') (range = 0 to 255)'; + } elseif (!$this->IsWithinBitRange($source_data_array['feedbackLL'], 8, false)) { + $this->errors[] = 'Invalid Reverb Feedback, Left-To-Left in '.$frame_name.' ('.$source_data_array['feedbackLL'].') (range = 0 to 255)'; + } elseif (!$this->IsWithinBitRange($source_data_array['feedbackLR'], 8, false)) { + $this->errors[] = 'Invalid Reverb Feedback, Left-To-Right in '.$frame_name.' ('.$source_data_array['feedbackLR'].') (range = 0 to 255)'; + } elseif (!$this->IsWithinBitRange($source_data_array['feedbackRR'], 8, false)) { + $this->errors[] = 'Invalid Reverb Feedback, Right-To-Right in '.$frame_name.' ('.$source_data_array['feedbackRR'].') (range = 0 to 255)'; + } elseif (!$this->IsWithinBitRange($source_data_array['feedbackRL'], 8, false)) { + $this->errors[] = 'Invalid Reverb Feedback, Right-To-Left in '.$frame_name.' ('.$source_data_array['feedbackRL'].') (range = 0 to 255)'; + } elseif (!$this->IsWithinBitRange($source_data_array['premixLR'], 8, false)) { + $this->errors[] = 'Invalid Premix, Left-To-Right in '.$frame_name.' ('.$source_data_array['premixLR'].') (range = 0 to 255)'; + } elseif (!$this->IsWithinBitRange($source_data_array['premixRL'], 8, false)) { + $this->errors[] = 'Invalid Premix, Right-To-Left in '.$frame_name.' ('.$source_data_array['premixRL'].') (range = 0 to 255)'; + } else { + $framedata .= getid3_lib::BigEndian2String($source_data_array['left'], 2, false); + $framedata .= getid3_lib::BigEndian2String($source_data_array['right'], 2, false); + $framedata .= chr($source_data_array['bouncesL']); + $framedata .= chr($source_data_array['bouncesR']); + $framedata .= chr($source_data_array['feedbackLL']); + $framedata .= chr($source_data_array['feedbackLR']); + $framedata .= chr($source_data_array['feedbackRR']); + $framedata .= chr($source_data_array['feedbackRL']); + $framedata .= chr($source_data_array['premixLR']); + $framedata .= chr($source_data_array['premixRL']); + } + break; + + case 'APIC': + // 4.14 APIC Attached picture + // Text encoding $xx + // MIME type <text string> $00 + // Picture type $xx + // Description <text string according to encoding> $00 (00) + // Picture data <binary data> + $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); + if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { + $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; + } elseif (!$this->ID3v2IsValidAPICpicturetype($source_data_array['picturetypeid'])) { + $this->errors[] = 'Invalid Picture Type byte in '.$frame_name.' ('.$source_data_array['picturetypeid'].') for ID3v2.'.$this->majorversion; + } elseif (($this->majorversion >= 3) && (!$this->ID3v2IsValidAPICimageformat($source_data_array['mime']))) { + $this->errors[] = 'Invalid MIME Type in '.$frame_name.' ('.$source_data_array['mime'].') for ID3v2.'.$this->majorversion; + } elseif (($source_data_array['mime'] == '-->') && (!$this->IsValidURL($source_data_array['data'], false, false))) { + $this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')'; + } else { + $framedata .= chr($source_data_array['encodingid']); + $framedata .= str_replace("\x00", '', $source_data_array['mime'])."\x00"; + $framedata .= chr($source_data_array['picturetypeid']); + $framedata .= @$source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); + $framedata .= $source_data_array['data']; + } + break; + + case 'GEOB': + // 4.15 GEOB General encapsulated object + // Text encoding $xx + // MIME type <text string> $00 + // Filename <text string according to encoding> $00 (00) + // Content description <text string according to encoding> $00 (00) + // Encapsulated object <binary data> + $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); + if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { + $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; + } elseif (!$this->IsValidMIMEstring($source_data_array['mime'])) { + $this->errors[] = 'Invalid MIME Type in '.$frame_name.' ('.$source_data_array['mime'].')'; + } elseif (!$source_data_array['description']) { + $this->errors[] = 'Missing Description in '.$frame_name; + } else { + $framedata .= chr($source_data_array['encodingid']); + $framedata .= str_replace("\x00", '', $source_data_array['mime'])."\x00"; + $framedata .= $source_data_array['filename'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); + $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); + $framedata .= $source_data_array['data']; + } + break; + + case 'PCNT': + // 4.16 PCNT Play counter + // When the counter reaches all one's, one byte is inserted in + // front of the counter thus making the counter eight bits bigger + // Counter $xx xx xx xx (xx ...) + $framedata .= getid3_lib::BigEndian2String($source_data_array['data'], 4, false); + break; + + case 'POPM': + // 4.17 POPM Popularimeter + // When the counter reaches all one's, one byte is inserted in + // front of the counter thus making the counter eight bits bigger + // Email to user <text string> $00 + // Rating $xx + // Counter $xx xx xx xx (xx ...) + if (!$this->IsWithinBitRange($source_data_array['rating'], 8, false)) { + $this->errors[] = 'Invalid Rating byte in '.$frame_name.' ('.$source_data_array['rating'].') (range = 0 to 255)'; + } elseif (!IsValidEmail($source_data_array['email'])) { + $this->errors[] = 'Invalid Email in '.$frame_name.' ('.$source_data_array['email'].')'; + } else { + $framedata .= str_replace("\x00", '', $source_data_array['email'])."\x00"; + $framedata .= chr($source_data_array['rating']); + $framedata .= getid3_lib::BigEndian2String($source_data_array['data'], 4, false); + } + break; + + case 'RBUF': + // 4.18 RBUF Recommended buffer size + // Buffer size $xx xx xx + // Embedded info flag %0000000x + // Offset to next tag $xx xx xx xx + if (!$this->IsWithinBitRange($source_data_array['buffersize'], 24, false)) { + $this->errors[] = 'Invalid Buffer Size in '.$frame_name; + } elseif (!$this->IsWithinBitRange($source_data_array['nexttagoffset'], 32, false)) { + $this->errors[] = 'Invalid Offset To Next Tag in '.$frame_name; + } else { + $framedata .= getid3_lib::BigEndian2String($source_data_array['buffersize'], 3, false); + $flag .= '0000000'; + $flag .= $source_data_array['flags']['embededinfo'] ? '1' : '0'; + $framedata .= chr(bindec($flag)); + $framedata .= getid3_lib::BigEndian2String($source_data_array['nexttagoffset'], 4, false); + } + break; + + case 'AENC': + // 4.19 AENC Audio encryption + // Owner identifier <text string> $00 + // Preview start $xx xx + // Preview length $xx xx + // Encryption info <binary data> + if (!$this->IsWithinBitRange($source_data_array['previewstart'], 16, false)) { + $this->errors[] = 'Invalid Preview Start in '.$frame_name.' ('.$source_data_array['previewstart'].')'; + } elseif (!$this->IsWithinBitRange($source_data_array['previewlength'], 16, false)) { + $this->errors[] = 'Invalid Preview Length in '.$frame_name.' ('.$source_data_array['previewlength'].')'; + } else { + $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00"; + $framedata .= getid3_lib::BigEndian2String($source_data_array['previewstart'], 2, false); + $framedata .= getid3_lib::BigEndian2String($source_data_array['previewlength'], 2, false); + $framedata .= $source_data_array['encryptioninfo']; + } + break; + + case 'LINK': + // 4.20 LINK Linked information + // Frame identifier $xx xx xx xx + // URL <text string> $00 + // ID and additional data <text string(s)> + if (!getid3_id3v2::IsValidID3v2FrameName($source_data_array['frameid'], $this->majorversion)) { + $this->errors[] = 'Invalid Frame Identifier in '.$frame_name.' ('.$source_data_array['frameid'].')'; + } elseif (!$this->IsValidURL($source_data_array['data'], true, false)) { + $this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')'; + } elseif ((($source_data_array['frameid'] == 'AENC') || ($source_data_array['frameid'] == 'APIC') || ($source_data_array['frameid'] == 'GEOB') || ($source_data_array['frameid'] == 'TXXX')) && ($source_data_array['additionaldata'] == '')) { + $this->errors[] = 'Content Descriptor must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name; + } elseif (($source_data_array['frameid'] == 'USER') && (getid3_id3v2::LanguageLookup($source_data_array['additionaldata'], true) == '')) { + $this->errors[] = 'Language must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name; + } elseif (($source_data_array['frameid'] == 'PRIV') && ($source_data_array['additionaldata'] == '')) { + $this->errors[] = 'Owner Identifier must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name; + } elseif ((($source_data_array['frameid'] == 'COMM') || ($source_data_array['frameid'] == 'SYLT') || ($source_data_array['frameid'] == 'USLT')) && ((getid3_id3v2::LanguageLookup(substr($source_data_array['additionaldata'], 0, 3), true) == '') || (substr($source_data_array['additionaldata'], 3) == ''))) { + $this->errors[] = 'Language followed by Content Descriptor must be specified as additional data for Frame Identifier of '.$source_data_array['frameid'].' in '.$frame_name; + } else { + $framedata .= $source_data_array['frameid']; + $framedata .= str_replace("\x00", '', $source_data_array['data'])."\x00"; + switch ($source_data_array['frameid']) { + case 'COMM': + case 'SYLT': + case 'USLT': + case 'PRIV': + case 'USER': + case 'AENC': + case 'APIC': + case 'GEOB': + case 'TXXX': + $framedata .= $source_data_array['additionaldata']; + break; + case 'ASPI': + case 'ETCO': + case 'EQU2': + case 'MCID': + case 'MLLT': + case 'OWNE': + case 'RVA2': + case 'RVRB': + case 'SYTC': + case 'IPLS': + case 'RVAD': + case 'EQUA': + // no additional data required + break; + case 'RBUF': + if ($this->majorversion == 3) { + // no additional data required + } else { + $this->errors[] = $source_data_array['frameid'].' is not a valid Frame Identifier in '.$frame_name.' (in ID3v2.'.$this->majorversion.')'; + } + + default: + if ((substr($source_data_array['frameid'], 0, 1) == 'T') || (substr($source_data_array['frameid'], 0, 1) == 'W')) { + // no additional data required + } else { + $this->errors[] = $source_data_array['frameid'].' is not a valid Frame Identifier in '.$frame_name.' (in ID3v2.'.$this->majorversion.')'; + } + break; + } + } + break; + + case 'POSS': + // 4.21 POSS Position synchronisation frame (ID3v2.3+ only) + // Time stamp format $xx + // Position $xx (xx ...) + if (($source_data_array['timestampformat'] < 1) || ($source_data_array['timestampformat'] > 2)) { + $this->errors[] = 'Invalid Time Stamp Format in '.$frame_name.' ('.$source_data_array['timestampformat'].') (valid = 1 or 2)'; + } elseif (!$this->IsWithinBitRange($source_data_array['position'], 32, false)) { + $this->errors[] = 'Invalid Position in '.$frame_name.' ('.$source_data_array['position'].') (range = 0 to 4294967295)'; + } else { + $framedata .= chr($source_data_array['timestampformat']); + $framedata .= getid3_lib::BigEndian2String($source_data_array['position'], 4, false); + } + break; + + case 'USER': + // 4.22 USER Terms of use (ID3v2.3+ only) + // Text encoding $xx + // Language $xx xx xx + // The actual text <text string according to encoding> + $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); + if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { + $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].')'; + } elseif (getid3_id3v2::LanguageLookup($source_data_array['language'], true) == '') { + $this->errors[] = 'Invalid Language in '.$frame_name.' ('.$source_data_array['language'].')'; + } else { + $framedata .= chr($source_data_array['encodingid']); + $framedata .= strtolower($source_data_array['language']); + $framedata .= $source_data_array['data']; + } + break; + + case 'OWNE': + // 4.23 OWNE Ownership frame (ID3v2.3+ only) + // Text encoding $xx + // Price paid <text string> $00 + // Date of purch. <text string> + // Seller <text string according to encoding> + $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); + if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { + $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].')'; + } elseif (!$this->IsANumber($source_data_array['pricepaid']['value'], false)) { + $this->errors[] = 'Invalid Price Paid in '.$frame_name.' ('.$source_data_array['pricepaid']['value'].')'; + } elseif (!$this->IsValidDateStampString($source_data_array['purchasedate'])) { + $this->errors[] = 'Invalid Date Of Purchase in '.$frame_name.' ('.$source_data_array['purchasedate'].') (format = YYYYMMDD)'; + } else { + $framedata .= chr($source_data_array['encodingid']); + $framedata .= str_replace("\x00", '', $source_data_array['pricepaid']['value'])."\x00"; + $framedata .= $source_data_array['purchasedate']; + $framedata .= $source_data_array['seller']; + } + break; + + case 'COMR': + // 4.24 COMR Commercial frame (ID3v2.3+ only) + // Text encoding $xx + // Price string <text string> $00 + // Valid until <text string> + // Contact URL <text string> $00 + // Received as $xx + // Name of seller <text string according to encoding> $00 (00) + // Description <text string according to encoding> $00 (00) + // Picture MIME type <string> $00 + // Seller logo <binary data> + $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); + if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { + $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].')'; + } elseif (!$this->IsValidDateStampString($source_data_array['pricevaliduntil'])) { + $this->errors[] = 'Invalid Valid Until date in '.$frame_name.' ('.$source_data_array['pricevaliduntil'].') (format = YYYYMMDD)'; + } elseif (!$this->IsValidURL($source_data_array['contacturl'], false, true)) { + $this->errors[] = 'Invalid Contact URL in '.$frame_name.' ('.$source_data_array['contacturl'].') (allowed schemes: http, https, ftp, mailto)'; + } elseif (!$this->ID3v2IsValidCOMRreceivedAs($source_data_array['receivedasid'])) { + $this->errors[] = 'Invalid Received As byte in '.$frame_name.' ('.$source_data_array['contacturl'].') (range = 0 to 8)'; + } elseif (!$this->IsValidMIMEstring($source_data_array['mime'])) { + $this->errors[] = 'Invalid MIME Type in '.$frame_name.' ('.$source_data_array['mime'].')'; + } else { + $framedata .= chr($source_data_array['encodingid']); + unset($pricestring); + foreach ($source_data_array['price'] as $key => $val) { + if ($this->ID3v2IsValidPriceString($key.$val['value'])) { + $pricestrings[] = $key.$val['value']; + } else { + $this->errors[] = 'Invalid Price String in '.$frame_name.' ('.$key.$val['value'].')'; + } + } + $framedata .= implode('/', $pricestrings); + $framedata .= $source_data_array['pricevaliduntil']; + $framedata .= str_replace("\x00", '', $source_data_array['contacturl'])."\x00"; + $framedata .= chr($source_data_array['receivedasid']); + $framedata .= $source_data_array['sellername'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); + $framedata .= $source_data_array['description'].getid3_id3v2::TextEncodingTerminatorLookup($source_data_array['encodingid']); + $framedata .= $source_data_array['mime']."\x00"; + $framedata .= $source_data_array['logo']; + } + break; + + case 'ENCR': + // 4.25 ENCR Encryption method registration (ID3v2.3+ only) + // Owner identifier <text string> $00 + // Method symbol $xx + // Encryption data <binary data> + if (!$this->IsWithinBitRange($source_data_array['methodsymbol'], 8, false)) { + $this->errors[] = 'Invalid Group Symbol in '.$frame_name.' ('.$source_data_array['methodsymbol'].') (range = 0 to 255)'; + } else { + $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00"; + $framedata .= ord($source_data_array['methodsymbol']); + $framedata .= $source_data_array['data']; + } + break; + + case 'GRID': + // 4.26 GRID Group identification registration (ID3v2.3+ only) + // Owner identifier <text string> $00 + // Group symbol $xx + // Group dependent data <binary data> + if (!$this->IsWithinBitRange($source_data_array['groupsymbol'], 8, false)) { + $this->errors[] = 'Invalid Group Symbol in '.$frame_name.' ('.$source_data_array['groupsymbol'].') (range = 0 to 255)'; + } else { + $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00"; + $framedata .= ord($source_data_array['groupsymbol']); + $framedata .= $source_data_array['data']; + } + break; + + case 'PRIV': + // 4.27 PRIV Private frame (ID3v2.3+ only) + // Owner identifier <text string> $00 + // The private data <binary data> + $framedata .= str_replace("\x00", '', $source_data_array['ownerid'])."\x00"; + $framedata .= $source_data_array['data']; + break; + + case 'SIGN': + // 4.28 SIGN Signature frame (ID3v2.4+ only) + // Group symbol $xx + // Signature <binary data> + if (!$this->IsWithinBitRange($source_data_array['groupsymbol'], 8, false)) { + $this->errors[] = 'Invalid Group Symbol in '.$frame_name.' ('.$source_data_array['groupsymbol'].') (range = 0 to 255)'; + } else { + $framedata .= ord($source_data_array['groupsymbol']); + $framedata .= $source_data_array['data']; + } + break; + + case 'SEEK': + // 4.29 SEEK Seek frame (ID3v2.4+ only) + // Minimum offset to next tag $xx xx xx xx + if (!$this->IsWithinBitRange($source_data_array['data'], 32, false)) { + $this->errors[] = 'Invalid Minimum Offset in '.$frame_name.' ('.$source_data_array['data'].') (range = 0 to 4294967295)'; + } else { + $framedata .= getid3_lib::BigEndian2String($source_data_array['data'], 4, false); + } + break; + + case 'ASPI': + // 4.30 ASPI Audio seek point index (ID3v2.4+ only) + // Indexed data start (S) $xx xx xx xx + // Indexed data length (L) $xx xx xx xx + // Number of index points (N) $xx xx + // Bits per index point (b) $xx + // Then for every index point the following data is included: + // Fraction at index (Fi) $xx (xx) + if (!$this->IsWithinBitRange($source_data_array['datastart'], 32, false)) { + $this->errors[] = 'Invalid Indexed Data Start in '.$frame_name.' ('.$source_data_array['datastart'].') (range = 0 to 4294967295)'; + } elseif (!$this->IsWithinBitRange($source_data_array['datalength'], 32, false)) { + $this->errors[] = 'Invalid Indexed Data Length in '.$frame_name.' ('.$source_data_array['datalength'].') (range = 0 to 4294967295)'; + } elseif (!$this->IsWithinBitRange($source_data_array['indexpoints'], 16, false)) { + $this->errors[] = 'Invalid Number Of Index Points in '.$frame_name.' ('.$source_data_array['indexpoints'].') (range = 0 to 65535)'; + } elseif (!$this->IsWithinBitRange($source_data_array['bitsperpoint'], 8, false)) { + $this->errors[] = 'Invalid Bits Per Index Point in '.$frame_name.' ('.$source_data_array['bitsperpoint'].') (range = 0 to 255)'; + } elseif ($source_data_array['indexpoints'] != count($source_data_array['indexes'])) { + $this->errors[] = 'Number Of Index Points does not match actual supplied data in '.$frame_name; + } else { + $framedata .= getid3_lib::BigEndian2String($source_data_array['datastart'], 4, false); + $framedata .= getid3_lib::BigEndian2String($source_data_array['datalength'], 4, false); + $framedata .= getid3_lib::BigEndian2String($source_data_array['indexpoints'], 2, false); + $framedata .= getid3_lib::BigEndian2String($source_data_array['bitsperpoint'], 1, false); + foreach ($source_data_array['indexes'] as $key => $val) { + $framedata .= getid3_lib::BigEndian2String($val, ceil($source_data_array['bitsperpoint'] / 8), false); + } + } + break; + + case 'RGAD': + // RGAD Replay Gain Adjustment + // http://privatewww.essex.ac.uk/~djmrob/replaygain/ + // Peak Amplitude $xx $xx $xx $xx + // Radio Replay Gain Adjustment %aaabbbcd %dddddddd + // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd + // a - name code + // b - originator code + // c - sign bit + // d - replay gain adjustment + + if (($source_data_array['track_adjustment'] > 51) || ($source_data_array['track_adjustment'] < -51)) { + $this->errors[] = 'Invalid Track Adjustment in '.$frame_name.' ('.$source_data_array['track_adjustment'].') (range = -51.0 to +51.0)'; + } elseif (($source_data_array['album_adjustment'] > 51) || ($source_data_array['album_adjustment'] < -51)) { + $this->errors[] = 'Invalid Album Adjustment in '.$frame_name.' ('.$source_data_array['album_adjustment'].') (range = -51.0 to +51.0)'; + } elseif (!$this->ID3v2IsValidRGADname($source_data_array['raw']['track_name'])) { + $this->errors[] = 'Invalid Track Name Code in '.$frame_name.' ('.$source_data_array['raw']['track_name'].') (range = 0 to 2)'; + } elseif (!$this->ID3v2IsValidRGADname($source_data_array['raw']['album_name'])) { + $this->errors[] = 'Invalid Album Name Code in '.$frame_name.' ('.$source_data_array['raw']['album_name'].') (range = 0 to 2)'; + } elseif (!$this->ID3v2IsValidRGADoriginator($source_data_array['raw']['track_originator'])) { + $this->errors[] = 'Invalid Track Originator Code in '.$frame_name.' ('.$source_data_array['raw']['track_originator'].') (range = 0 to 3)'; + } elseif (!$this->ID3v2IsValidRGADoriginator($source_data_array['raw']['album_originator'])) { + $this->errors[] = 'Invalid Album Originator Code in '.$frame_name.' ('.$source_data_array['raw']['album_originator'].') (range = 0 to 3)'; + } else { + $framedata .= getid3_lib::Float2String($source_data_array['peakamplitude'], 32); + $framedata .= getid3_lib::RGADgainString($source_data_array['raw']['track_name'], $source_data_array['raw']['track_originator'], $source_data_array['track_adjustment']); + $framedata .= getid3_lib::RGADgainString($source_data_array['raw']['album_name'], $source_data_array['raw']['album_originator'], $source_data_array['album_adjustment']); + } + break; + + default: + if ($frame_name{0} == 'T') { + // 4.2. T??? Text information frames + // Text encoding $xx + // Information <text string(s) according to encoding> + $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid); + if (!$this->ID3v2IsValidTextEncoding($source_data_array['encodingid'])) { + $this->errors[] = 'Invalid Text Encoding in '.$frame_name.' ('.$source_data_array['encodingid'].') for ID3v2.'.$this->majorversion; + } else { + $framedata .= chr($source_data_array['encodingid']); + $framedata .= $source_data_array['data']; + } + } elseif ($frame_name{0} == 'W') { + // 4.3. W??? URL link frames + // URL <text string> + if (!$this->IsValidURL($source_data_array['data'], false, false)) { + $this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')'; + } else { + $framedata .= $source_data_array['data']; + } + } else { + $this->errors[] = $frame_name.' not yet supported in $this->GenerateID3v2FrameData()'; + } + break; + } + } + if (!empty($this->errors)) { + return false; + } + return $framedata; + } + + function ID3v2FrameIsAllowed($frame_name, $source_data_array) { + static $PreviousFrames = array(); + + if ($frame_name === null) { + // if the writing functions are called multiple times, the static array needs to be + // cleared - this can be done by calling $this->ID3v2FrameIsAllowed(null, '') + $PreviousFrames = array(); + return true; + } + + if ($this->majorversion == 4) { + switch ($frame_name) { + case 'UFID': + case 'AENC': + case 'ENCR': + case 'GRID': + if (!isset($source_data_array['ownerid'])) { + $this->errors[] = '[ownerid] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['ownerid'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID ('.$source_data_array['ownerid'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['ownerid']; + } + break; + + case 'TXXX': + case 'WXXX': + case 'RVA2': + case 'EQU2': + case 'APIC': + case 'GEOB': + if (!isset($source_data_array['description'])) { + $this->errors[] = '[description] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['description'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Description ('.$source_data_array['description'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['description']; + } + break; + + case 'USER': + if (!isset($source_data_array['language'])) { + $this->errors[] = '[language] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['language'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language ('.$source_data_array['language'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['language']; + } + break; + + case 'USLT': + case 'SYLT': + case 'COMM': + if (!isset($source_data_array['language'])) { + $this->errors[] = '[language] not specified for '.$frame_name; + } elseif (!isset($source_data_array['description'])) { + $this->errors[] = '[description] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['language'].$source_data_array['description'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language + Description ('.$source_data_array['language'].' + '.$source_data_array['description'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['language'].$source_data_array['description']; + } + break; + + case 'POPM': + if (!isset($source_data_array['email'])) { + $this->errors[] = '[email] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['email'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Email ('.$source_data_array['email'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['email']; + } + break; + + case 'IPLS': + case 'MCDI': + case 'ETCO': + case 'MLLT': + case 'SYTC': + case 'RVRB': + case 'PCNT': + case 'RBUF': + case 'POSS': + case 'OWNE': + case 'SEEK': + case 'ASPI': + case 'RGAD': + if (in_array($frame_name, $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed'; + } else { + $PreviousFrames[] = $frame_name; + } + break; + + case 'LINK': + // this isn't implemented quite right (yet) - it should check the target frame data for compliance + // but right now it just allows one linked frame of each type, to be safe. + if (!isset($source_data_array['frameid'])) { + $this->errors[] = '[frameid] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['frameid'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same FrameID ('.$source_data_array['frameid'].')'; + } elseif (in_array($source_data_array['frameid'], $PreviousFrames)) { + // no links to singleton tags + $this->errors[] = 'Cannot specify a '.$frame_name.' tag to a singleton tag that already exists ('.$source_data_array['frameid'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['frameid']; // only one linked tag of this type + $PreviousFrames[] = $source_data_array['frameid']; // no non-linked singleton tags of this type + } + break; + + case 'COMR': + // There may be more than one 'commercial frame' in a tag, but no two may be identical + // Checking isn't implemented at all (yet) - just assumes that it's OK. + break; + + case 'PRIV': + case 'SIGN': + if (!isset($source_data_array['ownerid'])) { + $this->errors[] = '[ownerid] not specified for '.$frame_name; + } elseif (!isset($source_data_array['data'])) { + $this->errors[] = '[data] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['ownerid'].$source_data_array['data'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID + Data ('.$source_data_array['ownerid'].' + '.$source_data_array['data'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['ownerid'].$source_data_array['data']; + } + break; + + default: + if (($frame_name{0} != 'T') && ($frame_name{0} != 'W')) { + $this->errors[] = 'Frame not allowed in ID3v2.'.$this->majorversion.': '.$frame_name; + } + break; + } + + } elseif ($this->majorversion == 3) { + + switch ($frame_name) { + case 'UFID': + case 'AENC': + case 'ENCR': + case 'GRID': + if (!isset($source_data_array['ownerid'])) { + $this->errors[] = '[ownerid] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['ownerid'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID ('.$source_data_array['ownerid'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['ownerid']; + } + break; + + case 'TXXX': + case 'WXXX': + case 'APIC': + case 'GEOB': + if (!isset($source_data_array['description'])) { + $this->errors[] = '[description] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['description'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Description ('.$source_data_array['description'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['description']; + } + break; + + case 'USER': + if (!isset($source_data_array['language'])) { + $this->errors[] = '[language] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['language'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language ('.$source_data_array['language'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['language']; + } + break; + + case 'USLT': + case 'SYLT': + case 'COMM': + if (!isset($source_data_array['language'])) { + $this->errors[] = '[language] not specified for '.$frame_name; + } elseif (!isset($source_data_array['description'])) { + $this->errors[] = '[description] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['language'].$source_data_array['description'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language + Description ('.$source_data_array['language'].' + '.$source_data_array['description'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['language'].$source_data_array['description']; + } + break; + + case 'POPM': + if (!isset($source_data_array['email'])) { + $this->errors[] = '[email] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['email'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Email ('.$source_data_array['email'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['email']; + } + break; + + case 'IPLS': + case 'MCDI': + case 'ETCO': + case 'MLLT': + case 'SYTC': + case 'RVAD': + case 'EQUA': + case 'RVRB': + case 'PCNT': + case 'RBUF': + case 'POSS': + case 'OWNE': + case 'RGAD': + if (in_array($frame_name, $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed'; + } else { + $PreviousFrames[] = $frame_name; + } + break; + + case 'LINK': + // this isn't implemented quite right (yet) - it should check the target frame data for compliance + // but right now it just allows one linked frame of each type, to be safe. + if (!isset($source_data_array['frameid'])) { + $this->errors[] = '[frameid] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['frameid'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same FrameID ('.$source_data_array['frameid'].')'; + } elseif (in_array($source_data_array['frameid'], $PreviousFrames)) { + // no links to singleton tags + $this->errors[] = 'Cannot specify a '.$frame_name.' tag to a singleton tag that already exists ('.$source_data_array['frameid'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['frameid']; // only one linked tag of this type + $PreviousFrames[] = $source_data_array['frameid']; // no non-linked singleton tags of this type + } + break; + + case 'COMR': + // There may be more than one 'commercial frame' in a tag, but no two may be identical + // Checking isn't implemented at all (yet) - just assumes that it's OK. + break; + + case 'PRIV': + if (!isset($source_data_array['ownerid'])) { + $this->errors[] = '[ownerid] not specified for '.$frame_name; + } elseif (!isset($source_data_array['data'])) { + $this->errors[] = '[data] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['ownerid'].$source_data_array['data'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID + Data ('.$source_data_array['ownerid'].' + '.$source_data_array['data'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['ownerid'].$source_data_array['data']; + } + break; + + default: + if (($frame_name{0} != 'T') && ($frame_name{0} != 'W')) { + $this->errors[] = 'Frame not allowed in ID3v2.'.$this->majorversion.': '.$frame_name; + } + break; + } + + } elseif ($this->majorversion == 2) { + + switch ($frame_name) { + case 'UFI': + case 'CRM': + case 'CRA': + if (!isset($source_data_array['ownerid'])) { + $this->errors[] = '[ownerid] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['ownerid'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same OwnerID ('.$source_data_array['ownerid'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['ownerid']; + } + break; + + case 'TXX': + case 'WXX': + case 'PIC': + case 'GEO': + if (!isset($source_data_array['description'])) { + $this->errors[] = '[description] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['description'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Description ('.$source_data_array['description'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['description']; + } + break; + + case 'ULT': + case 'SLT': + case 'COM': + if (!isset($source_data_array['language'])) { + $this->errors[] = '[language] not specified for '.$frame_name; + } elseif (!isset($source_data_array['description'])) { + $this->errors[] = '[description] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['language'].$source_data_array['description'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Language + Description ('.$source_data_array['language'].' + '.$source_data_array['description'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['language'].$source_data_array['description']; + } + break; + + case 'POP': + if (!isset($source_data_array['email'])) { + $this->errors[] = '[email] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['email'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same Email ('.$source_data_array['email'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['email']; + } + break; + + case 'IPL': + case 'MCI': + case 'ETC': + case 'MLL': + case 'STC': + case 'RVA': + case 'EQU': + case 'REV': + case 'CNT': + case 'BUF': + if (in_array($frame_name, $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed'; + } else { + $PreviousFrames[] = $frame_name; + } + break; + + case 'LNK': + // this isn't implemented quite right (yet) - it should check the target frame data for compliance + // but right now it just allows one linked frame of each type, to be safe. + if (!isset($source_data_array['frameid'])) { + $this->errors[] = '[frameid] not specified for '.$frame_name; + } elseif (in_array($frame_name.$source_data_array['frameid'], $PreviousFrames)) { + $this->errors[] = 'Only one '.$frame_name.' tag allowed with the same FrameID ('.$source_data_array['frameid'].')'; + } elseif (in_array($source_data_array['frameid'], $PreviousFrames)) { + // no links to singleton tags + $this->errors[] = 'Cannot specify a '.$frame_name.' tag to a singleton tag that already exists ('.$source_data_array['frameid'].')'; + } else { + $PreviousFrames[] = $frame_name.$source_data_array['frameid']; // only one linked tag of this type + $PreviousFrames[] = $source_data_array['frameid']; // no non-linked singleton tags of this type + } + break; + + default: + if (($frame_name{0} != 'T') && ($frame_name{0} != 'W')) { + $this->errors[] = 'Frame not allowed in ID3v2.'.$this->majorversion.': '.$frame_name; + } + break; + } + } + + if (!empty($this->errors)) { + return false; + } + return true; + } + + function GenerateID3v2Tag($noerrorsonly=true) { + $this->ID3v2FrameIsAllowed(null, ''); // clear static array in case this isn't the first call to $this->GenerateID3v2Tag() + + $tagstring = ''; + if (is_array($this->tag_data)) { + foreach ($this->tag_data as $frame_name => $frame_rawinputdata) { + foreach ($frame_rawinputdata as $irrelevantindex => $source_data_array) { + if (getid3_id3v2::IsValidID3v2FrameName($frame_name, $this->majorversion)) { + unset($frame_length); + unset($frame_flags); + $frame_data = false; + if ($this->ID3v2FrameIsAllowed($frame_name, $source_data_array)) { + if ($frame_data = $this->GenerateID3v2FrameData($frame_name, $source_data_array)) { + $FrameUnsynchronisation = false; + if ($this->majorversion >= 4) { + // frame-level unsynchronisation + $unsynchdata = $frame_data; + if ($this->id3v2_use_unsynchronisation) { + $unsynchdata = $this->Unsynchronise($frame_data); + } + if (strlen($unsynchdata) != strlen($frame_data)) { + // unsynchronisation needed + $FrameUnsynchronisation = true; + $frame_data = $unsynchdata; + if (isset($TagUnsynchronisation) && $TagUnsynchronisation === false) { + // only set to true if ALL frames are unsynchronised + } else { + $TagUnsynchronisation = true; + } + } else { + if (isset($TagUnsynchronisation)) { + $TagUnsynchronisation = false; + } + } + unset($unsynchdata); + + $frame_length = getid3_lib::BigEndian2String(strlen($frame_data), 4, true); + } else { + $frame_length = getid3_lib::BigEndian2String(strlen($frame_data), 4, false); + } + $frame_flags = $this->GenerateID3v2FrameFlags($this->ID3v2FrameFlagsLookupTagAlter($frame_name), $this->ID3v2FrameFlagsLookupFileAlter($frame_name), false, false, false, false, $FrameUnsynchronisation, false); + } + } else { + $this->errors[] = 'Frame "'.$frame_name.'" is NOT allowed'; + } + if ($frame_data === false) { + $this->errors[] = '$this->GenerateID3v2FrameData() failed for "'.$frame_name.'"'; + if ($noerrorsonly) { + return false; + } else { + unset($frame_name); + } + } + } else { + // ignore any invalid frame names, including 'title', 'header', etc + $this->warnings[] = 'Ignoring invalid ID3v2 frame type: "'.$frame_name.'"'; + unset($frame_name); + unset($frame_length); + unset($frame_flags); + unset($frame_data); + } + if (isset($frame_name) && isset($frame_length) && isset($frame_flags) && isset($frame_data)) { + $tagstring .= $frame_name.$frame_length.$frame_flags.$frame_data; + } + } + } + + if (!isset($TagUnsynchronisation)) { + $TagUnsynchronisation = false; + } + if (($this->majorversion <= 3) && $this->id3v2_use_unsynchronisation) { + // tag-level unsynchronisation + $unsynchdata = $this->Unsynchronise($tagstring); + if (strlen($unsynchdata) != strlen($tagstring)) { + // unsynchronisation needed + $TagUnsynchronisation = true; + $tagstring = $unsynchdata; + } + } + + while ($this->paddedlength < (strlen($tagstring) + getid3_id3v2::ID3v2HeaderLength($this->majorversion))) { + $this->paddedlength += 1024; + } + + $footer = false; // ID3v2 footers not yet supported in getID3() + if (!$footer && ($this->paddedlength > (strlen($tagstring) + getid3_id3v2::ID3v2HeaderLength($this->majorversion)))) { + // pad up to $paddedlength bytes if unpadded tag is shorter than $paddedlength + // "Furthermore it MUST NOT have any padding when a tag footer is added to the tag." + $tagstring .= @str_repeat("\x00", $this->paddedlength - strlen($tagstring) - getid3_id3v2::ID3v2HeaderLength($this->majorversion)); + } + if ($this->id3v2_use_unsynchronisation && (substr($tagstring, strlen($tagstring) - 1, 1) == "\xFF")) { + // special unsynchronisation case: + // if last byte == $FF then appended a $00 + $TagUnsynchronisation = true; + $tagstring .= "\x00"; + } + + $tagheader = 'ID3'; + $tagheader .= chr($this->majorversion); + $tagheader .= chr($this->minorversion); + $tagheader .= $this->GenerateID3v2TagFlags(array('unsynchronisation'=>$TagUnsynchronisation)); + $tagheader .= getid3_lib::BigEndian2String(strlen($tagstring), 4, true); + + return $tagheader.$tagstring; + } + $this->errors[] = 'tag_data is not an array in GenerateID3v2Tag()'; + return false; + } + + function ID3v2IsValidPriceString($pricestring) { + if (getid3_id3v2::LanguageLookup(substr($pricestring, 0, 3), true) == '') { + return false; + } elseif (!$this->IsANumber(substr($pricestring, 3), true)) { + return false; + } + return true; + } + + function ID3v2FrameFlagsLookupTagAlter($framename) { + // unfinished + switch ($framename) { + case 'RGAD': + $allow = true; + default: + $allow = false; + break; + } + return $allow; + } + + function ID3v2FrameFlagsLookupFileAlter($framename) { + // unfinished + switch ($framename) { + case 'RGAD': + return false; + break; + + default: + return false; + break; + } + } + + function ID3v2IsValidETCOevent($eventid) { + if (($eventid < 0) || ($eventid > 0xFF)) { + // outside range of 1 byte + return false; + } elseif (($eventid >= 0xF0) && ($eventid <= 0xFC)) { + // reserved for future use + return false; + } elseif (($eventid >= 0x17) && ($eventid <= 0xDF)) { + // reserved for future use + return false; + } elseif (($eventid >= 0x0E) && ($eventid <= 0x16) && ($this->majorversion == 2)) { + // not defined in ID3v2.2 + return false; + } elseif (($eventid >= 0x15) && ($eventid <= 0x16) && ($this->majorversion == 3)) { + // not defined in ID3v2.3 + return false; + } + return true; + } + + function ID3v2IsValidSYLTtype($contenttype) { + if (($contenttype >= 0) && ($contenttype <= 8) && ($this->majorversion == 4)) { + return true; + } elseif (($contenttype >= 0) && ($contenttype <= 6) && ($this->majorversion == 3)) { + return true; + } + return false; + } + + function ID3v2IsValidRVA2channeltype($channeltype) { + if (($channeltype >= 0) && ($channeltype <= 8) && ($this->majorversion == 4)) { + return true; + } + return false; + } + + function ID3v2IsValidAPICpicturetype($picturetype) { + if (($picturetype >= 0) && ($picturetype <= 0x14) && ($this->majorversion >= 2) && ($this->majorversion <= 4)) { + return true; + } + return false; + } + + function ID3v2IsValidAPICimageformat($imageformat) { + if ($imageformat == '-->') { + return true; + } elseif ($this->majorversion == 2) { + if ((strlen($imageformat) == 3) && ($imageformat == strtoupper($imageformat))) { + return true; + } + } elseif (($this->majorversion == 3) || ($this->majorversion == 4)) { + if ($this->IsValidMIMEstring($imageformat)) { + return true; + } + } + return false; + } + + function ID3v2IsValidCOMRreceivedAs($receivedas) { + if (($this->majorversion >= 3) && ($receivedas >= 0) && ($receivedas <= 8)) { + return true; + } + return false; + } + + function ID3v2IsValidRGADname($RGADname) { + if (($RGADname >= 0) && ($RGADname <= 2)) { + return true; + } + return false; + } + + function ID3v2IsValidRGADoriginator($RGADoriginator) { + if (($RGADoriginator >= 0) && ($RGADoriginator <= 3)) { + return true; + } + return false; + } + + function ID3v2IsValidTextEncoding($textencodingbyte) { + static $ID3v2IsValidTextEncoding_cache = array( + 2 => array(true, true), + 3 => array(true, true), + 4 => array(true, true, true, true)); + return isset($ID3v2IsValidTextEncoding_cache[$this->majorversion][$textencodingbyte]); + } + + function Unsynchronise($data) { + // Whenever a false synchronisation is found within the tag, one zeroed + // byte is inserted after the first false synchronisation byte. The + // format of a correct sync that should be altered by ID3 encoders is as + // follows: + // %11111111 111xxxxx + // And should be replaced with: + // %11111111 00000000 111xxxxx + // This has the side effect that all $FF 00 combinations have to be + // altered, so they won't be affected by the decoding process. Therefore + // all the $FF 00 combinations have to be replaced with the $FF 00 00 + // combination during the unsynchronisation. + + $data = str_replace("\xFF\x00", "\xFF\x00\x00", $data); + $unsyncheddata = ''; + $datalength = strlen($data); + for ($i = 0; $i < $datalength; $i++) { + $thischar = $data{$i}; + $unsyncheddata .= $thischar; + if ($thischar == "\xFF") { + $nextchar = ord($data{$i + 1}); + if (($nextchar & 0xE0) == 0xE0) { + // previous byte = 11111111, this byte = 111????? + $unsyncheddata .= "\x00"; + } + } + } + return $unsyncheddata; + } + + function is_hash($var) { + // written by dev-nullØchristophe*vg + // taken from http://www.php.net/manual/en/function.array-merge-recursive.php + if (is_array($var)) { + $keys = array_keys($var); + $all_num = true; + for ($i = 0; $i < count($keys); $i++) { + if (is_string($keys[$i])) { + return true; + } + } + } + return false; + } + + function array_join_merge($arr1, $arr2) { + // written by dev-nullØchristophe*vg + // taken from http://www.php.net/manual/en/function.array-merge-recursive.php + if (is_array($arr1) && is_array($arr2)) { + // the same -> merge + $new_array = array(); + + if ($this->is_hash($arr1) && $this->is_hash($arr2)) { + // hashes -> merge based on keys + $keys = array_merge(array_keys($arr1), array_keys($arr2)); + foreach ($keys as $key) { + $new_array[$key] = $this->array_join_merge(@$arr1[$key], @$arr2[$key]); + } + } else { + // two real arrays -> merge + $new_array = array_reverse(array_unique(array_reverse(array_merge($arr1, $arr2)))); + } + return $new_array; + } else { + // not the same ... take new one if defined, else the old one stays + return $arr2 ? $arr2 : $arr1; + } + } + + function IsValidMIMEstring($mimestring) { + if ((strlen($mimestring) >= 3) && (strpos($mimestring, '/') > 0) && (strpos($mimestring, '/') < (strlen($mimestring) - 1))) { + return true; + } + return false; + } + + function IsWithinBitRange($number, $maxbits, $signed=false) { + if ($signed) { + if (($number > (0 - pow(2, $maxbits - 1))) && ($number <= pow(2, $maxbits - 1))) { + return true; + } + } else { + if (($number >= 0) && ($number <= pow(2, $maxbits))) { + return true; + } + } + return false; + } + + function safe_parse_url($url) { + $parts = @parse_url($url); + $parts['scheme'] = (isset($parts['scheme']) ? $parts['scheme'] : ''); + $parts['host'] = (isset($parts['host']) ? $parts['host'] : ''); + $parts['user'] = (isset($parts['user']) ? $parts['user'] : ''); + $parts['pass'] = (isset($parts['pass']) ? $parts['pass'] : ''); + $parts['path'] = (isset($parts['path']) ? $parts['path'] : ''); + $parts['query'] = (isset($parts['query']) ? $parts['query'] : ''); + return $parts; + } + + function IsValidURL($url, $allowUserPass=false) { + if ($url == '') { + return false; + } + if ($allowUserPass !== true) { + if (strstr($url, '@')) { + // in the format http://user:pass@example.com or http://user@example.com + // but could easily be somebody incorrectly entering an email address in place of a URL + return false; + } + } + if ($parts = $this->safe_parse_url($url)) { + if (($parts['scheme'] != 'http') && ($parts['scheme'] != 'https') && ($parts['scheme'] != 'ftp') && ($parts['scheme'] != 'gopher')) { + return false; + } elseif (!eregi("^[[:alnum:]]([-.]?[0-9a-z])*\.[a-z]{2,3}$", $parts['host'], $regs) && !IsValidDottedIP($parts['host'])) { + return false; + } elseif (!eregi("^([[:alnum:]-]|[\_])*$", $parts['user'], $regs)) { + return false; + } elseif (!eregi("^([[:alnum:]-]|[\_])*$", $parts['pass'], $regs)) { + return false; + } elseif (!eregi("^[[:alnum:]/_\.@~-]*$", $parts['path'], $regs)) { + return false; + } elseif (!eregi("^[[:alnum:]?&=+:;_()%#/,\.-]*$", $parts['query'], $regs)) { + return false; + } else { + return true; + } + } + return false; + } + + function ID3v2ShortFrameNameLookup($majorversion, $long_description) { + $long_description = str_replace(' ', '_', strtolower(trim($long_description))); + static $ID3v2ShortFrameNameLookup = array(); + if (empty($ID3v2ShortFrameNameLookup)) { + + // The following are unique to ID3v2.2 + $ID3v2ShortFrameNameLookup[2]['comment'] = 'COM'; + $ID3v2ShortFrameNameLookup[2]['album'] = 'TAL'; + $ID3v2ShortFrameNameLookup[2]['beats_per_minute'] = 'TBP'; + $ID3v2ShortFrameNameLookup[2]['composer'] = 'TCM'; + $ID3v2ShortFrameNameLookup[2]['genre'] = 'TCO'; + $ID3v2ShortFrameNameLookup[2]['copyright'] = 'TCR'; + $ID3v2ShortFrameNameLookup[2]['encoded_by'] = 'TEN'; + $ID3v2ShortFrameNameLookup[2]['language'] = 'TLA'; + $ID3v2ShortFrameNameLookup[2]['length'] = 'TLE'; + $ID3v2ShortFrameNameLookup[2]['original_artist'] = 'TOA'; + $ID3v2ShortFrameNameLookup[2]['original_filename'] = 'TOF'; + $ID3v2ShortFrameNameLookup[2]['original_lyricist'] = 'TOL'; + $ID3v2ShortFrameNameLookup[2]['original_album_title'] = 'TOT'; + $ID3v2ShortFrameNameLookup[2]['artist'] = 'TP1'; + $ID3v2ShortFrameNameLookup[2]['band'] = 'TP2'; + $ID3v2ShortFrameNameLookup[2]['conductor'] = 'TP3'; + $ID3v2ShortFrameNameLookup[2]['remixer'] = 'TP4'; + $ID3v2ShortFrameNameLookup[2]['publisher'] = 'TPB'; + $ID3v2ShortFrameNameLookup[2]['isrc'] = 'TRC'; + $ID3v2ShortFrameNameLookup[2]['tracknumber'] = 'TRK'; + $ID3v2ShortFrameNameLookup[2]['size'] = 'TSI'; + $ID3v2ShortFrameNameLookup[2]['encoder_settings'] = 'TSS'; + $ID3v2ShortFrameNameLookup[2]['description'] = 'TT1'; + $ID3v2ShortFrameNameLookup[2]['title'] = 'TT2'; + $ID3v2ShortFrameNameLookup[2]['subtitle'] = 'TT3'; + $ID3v2ShortFrameNameLookup[2]['lyricist'] = 'TXT'; + $ID3v2ShortFrameNameLookup[2]['user_text'] = 'TXX'; + $ID3v2ShortFrameNameLookup[2]['year'] = 'TYE'; + $ID3v2ShortFrameNameLookup[2]['unique_file_identifier'] = 'UFI'; + $ID3v2ShortFrameNameLookup[2]['unsynchronised_lyrics'] = 'ULT'; + $ID3v2ShortFrameNameLookup[2]['url_file'] = 'WAF'; + $ID3v2ShortFrameNameLookup[2]['url_artist'] = 'WAR'; + $ID3v2ShortFrameNameLookup[2]['url_source'] = 'WAS'; + $ID3v2ShortFrameNameLookup[2]['copyright_information'] = 'WCP'; + $ID3v2ShortFrameNameLookup[2]['url_publisher'] = 'WPB'; + $ID3v2ShortFrameNameLookup[2]['url_user'] = 'WXX'; + + // The following are common to ID3v2.3 and ID3v2.4 + $ID3v2ShortFrameNameLookup[3]['audio_encryption'] = 'AENC'; + $ID3v2ShortFrameNameLookup[3]['attached_picture'] = 'APIC'; + $ID3v2ShortFrameNameLookup[3]['comment'] = 'COMM'; + $ID3v2ShortFrameNameLookup[3]['commercial'] = 'COMR'; + $ID3v2ShortFrameNameLookup[3]['encryption_method_registration'] = 'ENCR'; + $ID3v2ShortFrameNameLookup[3]['event_timing_codes'] = 'ETCO'; + $ID3v2ShortFrameNameLookup[3]['general_encapsulated_object'] = 'GEOB'; + $ID3v2ShortFrameNameLookup[3]['group_identification_registration'] = 'GRID'; + $ID3v2ShortFrameNameLookup[3]['linked_information'] = 'LINK'; + $ID3v2ShortFrameNameLookup[3]['music_cd_identifier'] = 'MCDI'; + $ID3v2ShortFrameNameLookup[3]['mpeg_location_lookup_table'] = 'MLLT'; + $ID3v2ShortFrameNameLookup[3]['ownership'] = 'OWNE'; + $ID3v2ShortFrameNameLookup[3]['play_counter'] = 'PCNT'; + $ID3v2ShortFrameNameLookup[3]['popularimeter'] = 'POPM'; + $ID3v2ShortFrameNameLookup[3]['position_synchronisation'] = 'POSS'; + $ID3v2ShortFrameNameLookup[3]['private'] = 'PRIV'; + $ID3v2ShortFrameNameLookup[3]['recommended_buffer_size'] = 'RBUF'; + $ID3v2ShortFrameNameLookup[3]['reverb'] = 'RVRB'; + $ID3v2ShortFrameNameLookup[3]['synchronised_lyrics'] = 'SYLT'; + $ID3v2ShortFrameNameLookup[3]['synchronised_tempo_codes'] = 'SYTC'; + $ID3v2ShortFrameNameLookup[3]['album'] = 'TALB'; + $ID3v2ShortFrameNameLookup[3]['beats_per_minute'] = 'TBPM'; + $ID3v2ShortFrameNameLookup[3]['composer'] = 'TCOM'; + $ID3v2ShortFrameNameLookup[3]['genre'] = 'TCON'; + $ID3v2ShortFrameNameLookup[3]['copyright'] = 'TCOP'; + $ID3v2ShortFrameNameLookup[3]['playlist_delay'] = 'TDLY'; + $ID3v2ShortFrameNameLookup[3]['encoded_by'] = 'TENC'; + $ID3v2ShortFrameNameLookup[3]['lyricist'] = 'TEXT'; + $ID3v2ShortFrameNameLookup[3]['file_type'] = 'TFLT'; + $ID3v2ShortFrameNameLookup[3]['content_group_description'] = 'TIT1'; + $ID3v2ShortFrameNameLookup[3]['title'] = 'TIT2'; + $ID3v2ShortFrameNameLookup[3]['subtitle'] = 'TIT3'; + $ID3v2ShortFrameNameLookup[3]['initial_key'] = 'TKEY'; + $ID3v2ShortFrameNameLookup[3]['language'] = 'TLAN'; + $ID3v2ShortFrameNameLookup[3]['length'] = 'TLEN'; + $ID3v2ShortFrameNameLookup[3]['media_type'] = 'TMED'; + $ID3v2ShortFrameNameLookup[3]['original_album_title'] = 'TOAL'; + $ID3v2ShortFrameNameLookup[3]['original_filename'] = 'TOFN'; + $ID3v2ShortFrameNameLookup[3]['original_lyricist'] = 'TOLY'; + $ID3v2ShortFrameNameLookup[3]['original_artist'] = 'TOPE'; + $ID3v2ShortFrameNameLookup[3]['file_owner'] = 'TOWN'; + $ID3v2ShortFrameNameLookup[3]['artist'] = 'TPE1'; + $ID3v2ShortFrameNameLookup[3]['band'] = 'TPE2'; + $ID3v2ShortFrameNameLookup[3]['conductor'] = 'TPE3'; + $ID3v2ShortFrameNameLookup[3]['remixer'] = 'TPE4'; + $ID3v2ShortFrameNameLookup[3]['part_of_set'] = 'TPOS'; + $ID3v2ShortFrameNameLookup[3]['publisher'] = 'TPUB'; + $ID3v2ShortFrameNameLookup[3]['tracknumber'] = 'TRCK'; + $ID3v2ShortFrameNameLookup[3]['internet_radio_station_name'] = 'TRSN'; + $ID3v2ShortFrameNameLookup[3]['internet_radio_station_owner'] = 'TRSO'; + $ID3v2ShortFrameNameLookup[3]['isrc'] = 'TSRC'; + $ID3v2ShortFrameNameLookup[3]['encoder_settings'] = 'TSSE'; + $ID3v2ShortFrameNameLookup[3]['user_text'] = 'TXXX'; + $ID3v2ShortFrameNameLookup[3]['unique_file_identifier'] = 'UFID'; + $ID3v2ShortFrameNameLookup[3]['terms_of_use'] = 'USER'; + $ID3v2ShortFrameNameLookup[3]['unsynchronised_lyrics'] = 'USLT'; + $ID3v2ShortFrameNameLookup[3]['commercial'] = 'WCOM'; + $ID3v2ShortFrameNameLookup[3]['copyright_information'] = 'WCOP'; + $ID3v2ShortFrameNameLookup[3]['url_file'] = 'WOAF'; + $ID3v2ShortFrameNameLookup[3]['url_artist'] = 'WOAR'; + $ID3v2ShortFrameNameLookup[3]['url_source'] = 'WOAS'; + $ID3v2ShortFrameNameLookup[3]['url_station'] = 'WORS'; + $ID3v2ShortFrameNameLookup[3]['payment'] = 'WPAY'; + $ID3v2ShortFrameNameLookup[3]['url_publisher'] = 'WPUB'; + $ID3v2ShortFrameNameLookup[3]['url_user'] = 'WXXX'; + + // The above are common to ID3v2.3 and ID3v2.4 + // so copy them to ID3v2.4 before adding specifics for 2.3 and 2.4 + $ID3v2ShortFrameNameLookup[4] = $ID3v2ShortFrameNameLookup[3]; + + // The following are unique to ID3v2.3 + $ID3v2ShortFrameNameLookup[3]['equalisation'] = 'EQUA'; + $ID3v2ShortFrameNameLookup[3]['involved_people_list'] = 'IPLS'; + $ID3v2ShortFrameNameLookup[3]['relative_volume_adjustment'] = 'RVAD'; + $ID3v2ShortFrameNameLookup[3]['date'] = 'TDAT'; + $ID3v2ShortFrameNameLookup[3]['time'] = 'TIME'; + $ID3v2ShortFrameNameLookup[3]['original_release_year'] = 'TORY'; + $ID3v2ShortFrameNameLookup[3]['recording_dates'] = 'TRDA'; + $ID3v2ShortFrameNameLookup[3]['size'] = 'TSIZ'; + $ID3v2ShortFrameNameLookup[3]['year'] = 'TYER'; + + + // The following are unique to ID3v2.4 + $ID3v2ShortFrameNameLookup[4]['audio_seek_point_index'] = 'ASPI'; + $ID3v2ShortFrameNameLookup[4]['equalisation'] = 'EQU2'; + $ID3v2ShortFrameNameLookup[4]['relative_volume_adjustment'] = 'RVA2'; + $ID3v2ShortFrameNameLookup[4]['seek'] = 'SEEK'; + $ID3v2ShortFrameNameLookup[4]['signature'] = 'SIGN'; + $ID3v2ShortFrameNameLookup[4]['encoding_time'] = 'TDEN'; + $ID3v2ShortFrameNameLookup[4]['original_release_time'] = 'TDOR'; + $ID3v2ShortFrameNameLookup[4]['recording_time'] = 'TDRC'; + $ID3v2ShortFrameNameLookup[4]['release_time'] = 'TDRL'; + $ID3v2ShortFrameNameLookup[4]['tagging_time'] = 'TDTG'; + $ID3v2ShortFrameNameLookup[4]['involved_people_list'] = 'TIPL'; + $ID3v2ShortFrameNameLookup[4]['musician_credits_list'] = 'TMCL'; + $ID3v2ShortFrameNameLookup[4]['mood'] = 'TMOO'; + $ID3v2ShortFrameNameLookup[4]['produced_notice'] = 'TPRO'; + $ID3v2ShortFrameNameLookup[4]['album_sort_order'] = 'TSOA'; + $ID3v2ShortFrameNameLookup[4]['performer_sort_order'] = 'TSOP'; + $ID3v2ShortFrameNameLookup[4]['title_sort_order'] = 'TSOT'; + $ID3v2ShortFrameNameLookup[4]['set_subtitle'] = 'TSST'; + } + return @$ID3v2ShortFrameNameLookup[$majorversion][strtolower($long_description)]; + + } + +} + +?> diff --git a/modules/id3/getid3/write.lyrics3.php b/modules/id3/getid3/write.lyrics3.php new file mode 100644 index 00000000..6b8a47d6 --- /dev/null +++ b/modules/id3/getid3/write.lyrics3.php @@ -0,0 +1,78 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// write.lyrics3.php // +// module for writing Lyrics3 tags // +// dependencies: module.tag.lyrics3.php // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_write_lyrics3 +{ + var $filename; + var $tag_data; + //var $lyrics3_version = 2; // 1 or 2 + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + + function getid3_write_lyrics3() { + return true; + } + + function WriteLyrics3() { + $this->errors[] = 'WriteLyrics3() not yet functional - cannot write Lyrics3'; + return false; + } + + function DeleteLyrics3() { + // Initialize getID3 engine + $getID3 = new getID3; + $ThisFileInfo = $getID3->analyze($this->filename); + if (isset($ThisFileInfo['lyrics3']['tag_offset_start']) && isset($ThisFileInfo['lyrics3']['tag_offset_end'])) { + if ($fp = @fopen($this->filename, 'a+b')) { + + flock($fp, LOCK_EX); + $oldignoreuserabort = ignore_user_abort(true); + + fseek($fp, $ThisFileInfo['lyrics3']['tag_offset_end'], SEEK_SET); + $DataAfterLyrics3 = ''; + if ($ThisFileInfo['filesize'] > $ThisFileInfo['lyrics3']['tag_offset_end']) { + $DataAfterLyrics3 = fread($fp, $ThisFileInfo['filesize'] - $ThisFileInfo['lyrics3']['tag_offset_end']); + } + + ftruncate($fp, $ThisFileInfo['lyrics3']['tag_offset_start']); + + if (!empty($DataAfterLyrics3)) { + fseek($fp, $ThisFileInfo['lyrics3']['tag_offset_start'], SEEK_SET); + fwrite($fp, $DataAfterLyrics3, strlen($DataAfterLyrics3)); + } + + flock($fp, LOCK_UN); + fclose($fp); + ignore_user_abort($oldignoreuserabort); + + return true; + + } else { + + $this->errors[] = 'Cannot open "'.$this->filename.'" in "a+b" mode'; + return false; + + } + } + // no Lyrics3 present + return true; + } + + + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/write.metaflac.php b/modules/id3/getid3/write.metaflac.php new file mode 100644 index 00000000..c5acc8ce --- /dev/null +++ b/modules/id3/getid3/write.metaflac.php @@ -0,0 +1,167 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// write.metaflac.php // +// module for writing metaflac tags // +// dependencies: /helperapps/metaflac.exe // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_write_metaflac +{ + + var $filename; + var $tag_data; + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + + function getid3_write_metaflac() { + return true; + } + + function WriteMetaFLAC() { + + if (!ini_get('safe_mode')) { + + // Create file with new comments + $tempcommentsfilename = tempnam('*', 'getID3'); + if ($fpcomments = @fopen($tempcommentsfilename, 'wb')) { + + foreach ($this->tag_data as $key => $value) { + foreach ($value as $commentdata) { + fwrite($fpcomments, $this->CleanmetaflacName($key).'='.$commentdata."\n"); + } + } + fclose($fpcomments); + + } else { + + $this->errors[] = 'failed to open temporary tags file "'.$tempcommentsfilename.'", tags not written'; + return false; + + } + + $oldignoreuserabort = ignore_user_abort(true); + if (GETID3_OS_ISWINDOWS) { + + if (file_exists(GETID3_HELPERAPPSDIR.'metaflac.exe')) { + //$commandline = '"'.GETID3_HELPERAPPSDIR.'metaflac.exe" --no-utf8-convert --remove-vc-all --import-vc-from="'.$tempcommentsfilename.'" "'.str_replace('/', '\\', $this->filename).'"'; + // metaflac works fine if you copy-paste the above commandline into a command prompt, + // but refuses to work with `backtick` if there are "doublequotes" present around BOTH + // the metaflac pathname and the target filename. For whatever reason...?? + // The solution is simply ensure that the metaflac pathname has no spaces, + // and therefore does not need to be quoted + + // On top of that, if error messages are not always captured properly under Windows + // To at least see if there was a problem, compare file modification timestamps before and after writing + clearstatcache(); + $timestampbeforewriting = filemtime($this->filename); + + $commandline = GETID3_HELPERAPPSDIR.'metaflac.exe --no-utf8-convert --remove-vc-all --import-vc-from="'.$tempcommentsfilename.'" "'.$this->filename.'" 2>&1'; + $metaflacError = `$commandline`; + + if (empty($metaflacError)) { + clearstatcache(); + if ($timestampbeforewriting == filemtime($this->filename)) { + $metaflacError = 'File modification timestamp has not changed - it looks like the tags were not written'; + } + } + } else { + $metaflacError = 'metaflac.exe not found in '.GETID3_HELPERAPPSDIR; + } + + } else { + + // It's simpler on *nix + $commandline = 'metaflac --no-utf8-convert --remove-vc-all --import-vc-from='.$tempcommentsfilename.' "'.$this->filename.'" 2>&1'; + $metaflacError = `$commandline`; + + } + + // Remove temporary comments file + unlink($tempcommentsfilename); + ignore_user_abort($oldignoreuserabort); + + if (!empty($metaflacError)) { + + $this->errors[] = 'System call to metaflac failed with this message returned: '."\n\n".$metaflacError; + return false; + + } + + return true; + } + + $this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call metaflac, tags not written'; + return false; + } + + + function DeleteMetaFLAC() { + + if (!ini_get('safe_mode')) { + + $oldignoreuserabort = ignore_user_abort(true); + if (GETID3_OS_ISWINDOWS) { + + if (file_exists(GETID3_HELPERAPPSDIR.'metaflac.exe')) { + // To at least see if there was a problem, compare file modification timestamps before and after writing + clearstatcache(); + $timestampbeforewriting = filemtime($this->filename); + + $commandline = GETID3_HELPERAPPSDIR.'metaflac.exe --remove-vc-all "'.$this->filename.'" 2>&1'; + $metaflacError = `$commandline`; + + if (empty($metaflacError)) { + clearstatcache(); + if ($timestampbeforewriting == filemtime($this->filename)) { + $metaflacError = 'File modification timestamp has not changed - it looks like the tags were not deleted'; + } + } + } else { + $metaflacError = 'metaflac.exe not found in '.GETID3_HELPERAPPSDIR; + } + + } else { + + // It's simpler on *nix + $commandline = 'metaflac --remove-vc-all "'.$this->filename.'" 2>&1'; + $metaflacError = `$commandline`; + + } + + ignore_user_abort($oldignoreuserabort); + + if (!empty($metaflacError)) { + $this->errors[] = 'System call to metaflac failed with this message returned: '."\n\n".$metaflacError; + return false; + } + return true; + } + $this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call metaflac, tags not deleted'; + return false; + } + + + function CleanmetaflacName($originalcommentname) { + // A case-insensitive field name that may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded. + // ASCII 0x41 through 0x5A inclusive (A-Z) is to be considered equivalent to ASCII 0x61 through + // 0x7A inclusive (a-z). + + // replace invalid chars with a space, return uppercase text + // Thanks Chris Bolt <chris-getid3Øbolt*cx> for improving this function + // note: ereg_replace() replaces nulls with empty string (not space) + return strtoupper(ereg_replace('[^ -<>-}]', ' ', str_replace("\x00", ' ', $originalcommentname))); + + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/write.php b/modules/id3/getid3/write.php new file mode 100644 index 00000000..04c0226e --- /dev/null +++ b/modules/id3/getid3/write.php @@ -0,0 +1,582 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +/// // +// write.php // +// module for writing tags (APEv2, ID3v1, ID3v2) // +// dependencies: getid3.lib.php // +// write.apetag.php (optional) // +// write.id3v1.php (optional) // +// write.id3v2.php (optional) // +// write.vorbiscomment.php (optional) // +// write.metaflac.php (optional) // +// write.lyrics3.php (optional) // +// /// +///////////////////////////////////////////////////////////////// + +if (!defined('GETID3_INCLUDEPATH')) { + die('getid3.php MUST be included before calling getid3_writetags'); +} +if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) { + die('write.php depends on getid3.lib.php, which is missing.'); +} + + +// NOTES: +// +// You should pass data here with standard field names as follows: +// * TITLE +// * ARTIST +// * ALBUM +// * TRACKNUMBER +// * COMMENT +// * GENRE +// * YEAR +// * ATTACHED_PICTURE (ID3v2 only) +// +// http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html +// The APEv2 Tag Items Keys definition says "TRACK" is correct but foobar2000 uses "TRACKNUMBER" instead +// Pass data here as "TRACKNUMBER" for compatability with all formats + + +class getid3_writetags +{ + // public + var $filename; // absolute filename of file to write tags to + var $tagformats = array(); // array of tag formats to write ('id3v1', 'id3v2.2', 'id2v2.3', 'id3v2.4', 'ape', 'vorbiscomment', 'metaflac', 'real') + var $tag_data = array(array()); // 2-dimensional array of tag data (ex: $data['ARTIST'][0] = 'Elvis') + var $tag_encoding = 'ISO-8859-1'; // text encoding used for tag data ('ISO-8859-1', 'UTF-8', 'UTF-16', 'UTF-16LE', 'UTF-16BE', ) + var $overwrite_tags = true; // if true will erase existing tag data and write only passed data; if false will merge passed data with existing tag data + var $remove_other_tags = false; // if true will erase remove all existing tags and only write those passed in $tagformats; if false will ignore any tags not mentioned in $tagformats + + var $id3v2_tag_language = 'eng'; // ISO-639-2 3-character language code needed for some ID3v2 frames (http://www.id3.org/iso639-2.html) + var $id3v2_paddedlength = 4096; // minimum length of ID3v2 tags (will be padded to this length if tag data is shorter) + + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + + // private + var $ThisFileInfo; // analysis of file before writing + + function getid3_writetags() { + return true; + } + + + function WriteTags() { + + if (empty($this->filename)) { + $this->errors[] = 'filename is undefined in getid3_writetags'; + return false; + } elseif (!file_exists($this->filename)) { + $this->errors[] = 'filename set to non-existant file "'.$this->filename.'" in getid3_writetags'; + return false; + } + + if (!is_array($this->tagformats)) { + $this->errors[] = 'tagformats must be an array in getid3_writetags'; + return false; + } + + if (filesize($this->filename) == 0) { + + // empty file special case - allow any tag format, don't check existing format + // could be useful if you want to generate tag data for a non-existant file + $this->ThisFileInfo = array('fileformat'=>''); + $AllowedTagFormats = array('id3v1', 'id3v2.2', 'id3v2.3', 'id3v2.4', 'ape', 'lyrics3'); + + } else { + + $getID3 = new getID3; + $getID3->encoding = $this->tag_encoding; + $this->ThisFileInfo = $getID3->analyze($this->filename); + + // check for what file types are allowed on this fileformat + switch (@$this->ThisFileInfo['fileformat']) { + case 'mp3': + case 'mp2': + case 'mp1': + $AllowedTagFormats = array('id3v1', 'id3v2.2', 'id3v2.3', 'id3v2.4', 'ape', 'lyrics3'); + break; + + case 'mpc': + $AllowedTagFormats = array('ape'); + break; + + case 'flac': + $AllowedTagFormats = array('metaflac'); + break; + + case 'real': + $AllowedTagFormats = array('real'); + break; + + case 'ogg': + switch (@$this->ThisFileInfo['audio']['dataformat']) { + case 'flac': + //$AllowedTagFormats = array('metaflac'); + $this->errors[] = 'metaflac is not (yet) compatible with OggFLAC files'; + return false; + break; + case 'vorbis': + $AllowedTagFormats = array('vorbiscomment'); + break; + default: + $this->errors[] = 'metaflac is not (yet) compatible with Ogg files other than OggVorbis'; + return false; + break; + } + break; + + default: + $AllowedTagFormats = array(); + break; + } + foreach ($this->tagformats as $requested_tag_format) { + if (!in_array($requested_tag_format, $AllowedTagFormats)) { + $errormessage = 'Tag format "'.$requested_tag_format.'" is not allowed on "'.@$this->ThisFileInfo['fileformat']; + if (@$this->ThisFileInfo['fileformat'] != @$this->ThisFileInfo['audio']['dataformat']) { + $errormessage .= '.'.@$this->ThisFileInfo['audio']['dataformat']; + } + $errormessage .= '" files'; + $this->errors[] = $errormessage; + return false; + } + } + + // List of other tag formats, removed if requested + $TagFormatsToRemove = array(); + if ($this->remove_other_tags) { + foreach ($AllowedTagFormats as $AllowedTagFormat) { + switch ($AllowedTagFormat) { + case 'id3v2.2': + case 'id3v2.3': + case 'id3v2.4': + if (!in_array('id3v2', $TagFormatsToRemove) && !in_array('id3v2.2', $this->tagformats) && !in_array('id3v2.3', $this->tagformats) && !in_array('id3v2.4', $this->tagformats)) { + $TagFormatsToRemove[] = 'id3v2'; + } + break; + + default: + if (!in_array($AllowedTagFormat, $this->tagformats)) { + $TagFormatsToRemove[] = $AllowedTagFormat; + } + break; + } + } + } + } + + $WritingFilesToInclude = array_merge($this->tagformats, $TagFormatsToRemove); + + // Check for required include files and include them + foreach ($WritingFilesToInclude as $tagformat) { + switch ($tagformat) { + case 'ape': + $GETID3_ERRORARRAY = &$this->errors; + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.apetag.php', __FILE__, false)) { + return false; + } + break; + + case 'id3v1': + case 'lyrics3': + case 'vorbiscomment': + case 'metaflac': + case 'real': + $GETID3_ERRORARRAY = &$this->errors; + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.'.$tagformat.'.php', __FILE__, false)) { + return false; + } + break; + + case 'id3v2.2': + case 'id3v2.3': + case 'id3v2.4': + case 'id3v2': + $GETID3_ERRORARRAY = &$this->errors; + if (!getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.id3v2.php', __FILE__, false)) { + return false; + } + break; + + default: + $this->errors[] = 'unknown tag format "'.$tagformat.'" in $tagformats in WriteTags()'; + return false; + break; + } + + } + + // Validation of supplied data + if (!is_array($this->tag_data)) { + $this->errors[] = '$tag_data is not an array in WriteTags()'; + return false; + } + // convert supplied data array keys to upper case, if they're not already + foreach ($this->tag_data as $tag_key => $tag_array) { + if (strtoupper($tag_key) !== $tag_key) { + $this->tag_data[strtoupper($tag_key)] = $this->tag_data[$tag_key]; + unset($this->tag_data[$tag_key]); + } + } + // convert source data array keys to upper case, if they're not already + if (!empty($this->ThisFileInfo['tags'])) { + foreach ($this->ThisFileInfo['tags'] as $tag_format => $tag_data_array) { + foreach ($tag_data_array as $tag_key => $tag_array) { + if (strtoupper($tag_key) !== $tag_key) { + $this->ThisFileInfo['tags'][$tag_format][strtoupper($tag_key)] = $this->ThisFileInfo['tags'][$tag_format][$tag_key]; + unset($this->ThisFileInfo['tags'][$tag_format][$tag_key]); + } + } + } + } + + // Convert "TRACK" to "TRACKNUMBER" (if needed) for compatability with all formats + if (isset($this->tag_data['TRACK']) && !isset($this->tag_data['TRACKNUMBER'])) { + $this->tag_data['TRACKNUMBER'] = $this->tag_data['TRACK']; + unset($this->tag_data['TRACK']); + } + + // Remove all other tag formats, if requested + if ($this->remove_other_tags) { + $this->DeleteTags($TagFormatsToRemove); + } + + // Write data for each tag format + foreach ($this->tagformats as $tagformat) { + $success = false; // overridden if tag writing is successful + switch ($tagformat) { + case 'ape': + $ape_writer = new getid3_write_apetag; + if (($ape_writer->tag_data = $this->FormatDataForAPE()) !== false) { + $ape_writer->filename = $this->filename; + if (($success = $ape_writer->WriteAPEtag()) === false) { + $this->errors[] = 'WriteAPEtag() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $ape_writer->errors)).'</LI></UL></PRE>'; + } + } else { + $this->errors[] = 'FormatDataForAPE() failed'; + } + break; + + case 'id3v1': + $id3v1_writer = new getid3_write_id3v1; + if (($id3v1_writer->tag_data = $this->FormatDataForID3v1()) !== false) { + $id3v1_writer->filename = $this->filename; + if (($success = $id3v1_writer->WriteID3v1()) === false) { + $this->errors[] = 'WriteID3v1() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v1_writer->errors)).'</LI></UL></PRE>'; + } + } else { + $this->errors[] = 'FormatDataForID3v1() failed'; + } + break; + + case 'id3v2.2': + case 'id3v2.3': + case 'id3v2.4': + $id3v2_writer = new getid3_write_id3v2; + $id3v2_writer->majorversion = (int) substr($tagformat, -1); + $id3v2_writer->paddedlength = $this->id3v2_paddedlength; + if (($id3v2_writer->tag_data = $this->FormatDataForID3v2($id3v2_writer->majorversion)) !== false) { + $id3v2_writer->filename = $this->filename; + if (($success = $id3v2_writer->WriteID3v2()) === false) { + $this->errors[] = 'WriteID3v2() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v2_writer->errors)).'</LI></UL></PRE>'; + } + } else { + $this->errors[] = 'FormatDataForID3v2() failed'; + } + break; + + case 'vorbiscomment': + $vorbiscomment_writer = new getid3_write_vorbiscomment; + if (($vorbiscomment_writer->tag_data = $this->FormatDataForVorbisComment()) !== false) { + $vorbiscomment_writer->filename = $this->filename; + if (($success = $vorbiscomment_writer->WriteVorbisComment()) === false) { + $this->errors[] = 'WriteVorbisComment() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $vorbiscomment_writer->errors)).'</LI></UL></PRE>'; + } + } else { + $this->errors[] = 'WriteVorbisComment() failed'; + } + break; + + case 'metaflac': + $metaflac_writer = new getid3_write_metaflac; + if (($metaflac_writer->tag_data = $this->FormatDataForMetaFLAC()) !== false) { + $metaflac_writer->filename = $this->filename; + if (($success = $metaflac_writer->WriteMetaFLAC()) === false) { + $this->errors[] = 'WriteMetaFLAC() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $metaflac_writer->errors)).'</LI></UL></PRE>'; + } + } else { + $this->errors[] = 'FormatDataForMetaFLAC() failed'; + } + break; + + case 'real': + $real_writer = new getid3_write_real; + if (($real_writer->tag_data = $this->FormatDataForReal()) !== false) { + $real_writer->filename = $this->filename; + if (($success = $real_writer->WriteReal()) === false) { + $this->errors[] = 'WriteReal() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $real_writer->errors)).'</LI></UL></PRE>'; + } + } else { + $this->errors[] = 'FormatDataForReal() failed'; + } + break; + + default: + $this->errors[] = 'Invalid tag format to write: "'.$tagformat.'"'; + return false; + break; + } + if (!$success) { + return false; + } + } + return true; + + } + + + function DeleteTags($TagFormatsToDelete) { + foreach ($TagFormatsToDelete as $DeleteTagFormat) { + $success = false; // overridden if tag deletion is successful + switch ($DeleteTagFormat) { + case 'id3v1': + $id3v1_writer = new getid3_write_id3v1; + $id3v1_writer->filename = $this->filename; + if (($success = $id3v1_writer->RemoveID3v1()) === false) { + $this->errors[] = 'RemoveID3v1() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v1_writer->errors)).'</LI></UL></PRE>'; + } + break; + + case 'id3v2': + $id3v2_writer = new getid3_write_id3v2; + $id3v2_writer->filename = $this->filename; + if (($success = $id3v2_writer->RemoveID3v2()) === false) { + $this->errors[] = 'RemoveID3v2() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $id3v2_writer->errors)).'</LI></UL></PRE>'; + } + break; + + case 'ape': + $ape_writer = new getid3_write_apetag; + $ape_writer->filename = $this->filename; + if (($success = $ape_writer->DeleteAPEtag()) === false) { + $this->errors[] = 'DeleteAPEtag() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $ape_writer->errors)).'</LI></UL></PRE>'; + } + break; + + case 'vorbiscomment': + $vorbiscomment_writer = new getid3_write_vorbiscomment; + $vorbiscomment_writer->filename = $this->filename; + if (($success = $vorbiscomment_writer->DeleteVorbisComment()) === false) { + $this->errors[] = 'DeleteVorbisComment() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $vorbiscomment_writer->errors)).'</LI></UL></PRE>'; + } + break; + + case 'metaflac': + $metaflac_writer = new getid3_write_metaflac; + $metaflac_writer->filename = $this->filename; + if (($success = $metaflac_writer->DeleteMetaFLAC()) === false) { + $this->errors[] = 'DeleteMetaFLAC() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $metaflac_writer->errors)).'</LI></UL></PRE>'; + } + break; + + case 'lyrics3': + $lyrics3_writer = new getid3_write_lyrics3; + $lyrics3_writer->filename = $this->filename; + if (($success = $lyrics3_writer->DeleteLyrics3()) === false) { + $this->errors[] = 'DeleteLyrics3() failed with message(s):<PRE><UL><LI>'.trim(implode('</LI><LI>', $lyrics3_writer->errors)).'</LI></UL></PRE>'; + } + break; + + default: + $this->errors[] = 'Invalid tag format to delete: "'.$tagformat.'"'; + return false; + break; + } + if (!$success) { + return false; + } + } + return true; + } + + + function MergeExistingTagData($TagFormat, &$tag_data) { + // Merge supplied data with existing data, if requested + if ($this->overwrite_tags) { + // do nothing - ignore previous data + } else { + if (!isset($this->ThisFileInfo['tags'][$TagFormat])) { + return false; + } + $tag_data = array_merge_recursive($tag_data, $this->ThisFileInfo['tags'][$TagFormat]); + } + return true; + } + + function FormatDataForAPE() { + $ape_tag_data = array(); + foreach ($this->tag_data as $tag_key => $valuearray) { + switch ($tag_key) { + case 'ATTACHED_PICTURE': + // ATTACHED_PICTURE is ID3v2 only - ignore + $this->warnings[] = '$data['.$tag_key.'] is assumed to be ID3v2 APIC data - NOT written to APE tag'; + break; + + default: + foreach ($valuearray as $key => $value) { + if (is_string($value) || is_numeric($value)) { + $ape_tag_data[$tag_key][$key] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value); + } else { + $this->warnings[] = '$data['.$tag_key.']['.$key.'] is not a string value - all of $data['.$tag_key.'] NOT written to APE tag'; + unset($ape_tag_data[$tag_key]); + break; + } + } + break; + } + } + $this->MergeExistingTagData('ape', $ape_tag_data); + return $ape_tag_data; + } + + + function FormatDataForID3v1() { + $tag_data_id3v1['genreid'] = 255; + if (!empty($this->tag_data['GENRE'])) { + foreach ($this->tag_data['GENRE'] as $key => $value) { + if (getid3_id3v1::LookupGenreID($value) !== false) { + $tag_data_id3v1['genreid'] = getid3_id3v1::LookupGenreID($value); + break; + } + } + } + + $tag_data_id3v1['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['TITLE'])); + $tag_data_id3v1['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['ARTIST'])); + $tag_data_id3v1['album'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['ALBUM'])); + $tag_data_id3v1['year'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['YEAR'])); + $tag_data_id3v1['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['COMMENT'])); + + $tag_data_id3v1['track'] = intval(getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['TRACKNUMBER']))); + if ($tag_data_id3v1['track'] <= 0) { + $tag_data_id3v1['track'] = ''; + } + + $this->MergeExistingTagData('id3v1', $tag_data_id3v1); + return $tag_data_id3v1; + } + + function FormatDataForID3v2($id3v2_majorversion) { + $tag_data_id3v2 = array(); + + $ID3v2_text_encoding_lookup[2] = array('ISO-8859-1'=>0, 'UTF-16'=>1); + $ID3v2_text_encoding_lookup[3] = array('ISO-8859-1'=>0, 'UTF-16'=>1); + $ID3v2_text_encoding_lookup[4] = array('ISO-8859-1'=>0, 'UTF-16'=>1, 'UTF-16BE'=>2, 'UTF-8'=>3); + foreach ($this->tag_data as $tag_key => $valuearray) { + $ID3v2_framename = getid3_write_id3v2::ID3v2ShortFrameNameLookup($id3v2_majorversion, $tag_key); + switch ($ID3v2_framename) { + case 'APIC': + foreach ($valuearray as $key => $apic_data_array) { + if (isset($apic_data_array['data']) && + isset($apic_data_array['picturetypeid']) && + isset($apic_data_array['description']) && + isset($apic_data_array['mime'])) { + $tag_data_id3v2['APIC'][] = $apic_data_array; + } else { + $this->errors[] = 'ID3v2 APIC data is not properly structured'; + return false; + } + } + break; + + case '': + $this->errors[] = 'ID3v2: Skipping "'.$tag_key.'" because cannot match it to a known ID3v2 frame type'; + // some other data type, don't know how to handle it, ignore it + break; + + default: + // most other (text) frames can be copied over as-is + foreach ($valuearray as $key => $value) { + if (isset($ID3v2_text_encoding_lookup[$id3v2_majorversion][$this->tag_encoding])) { + // source encoding is valid in ID3v2 - use it with no conversion + $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = $ID3v2_text_encoding_lookup[$id3v2_majorversion][$this->tag_encoding]; + $tag_data_id3v2[$ID3v2_framename][$key]['data'] = $value; + } else { + // source encoding is NOT valid in ID3v2 - convert it to an ID3v2-valid encoding first + if ($id3v2_majorversion < 4) { + // convert data from other encoding to UTF-16 + $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 1; + $tag_data_id3v2[$ID3v2_framename][$key]['data'] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-16', $value); + } else { + // convert data from other encoding to UTF-8 + $tag_data_id3v2[$ID3v2_framename][$key]['encodingid'] = 3; + $tag_data_id3v2[$ID3v2_framename][$key]['data'] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value); + } + } + + // These values are not needed for all frame types, but if they're not used no matter + $tag_data_id3v2[$ID3v2_framename][$key]['description'] = ''; + $tag_data_id3v2[$ID3v2_framename][$key]['language'] = $this->id3v2_tag_language; + } + break; + } + } + $this->MergeExistingTagData('id3v2', $tag_data_id3v2); + return $tag_data_id3v2; + } + + function FormatDataForVorbisComment() { + $tag_data_vorbiscomment = $this->tag_data; + + // check for multi-line comment values - split out to multiple comments if neccesary + // and convert data to UTF-8 strings + foreach ($tag_data_vorbiscomment as $tag_key => $valuearray) { + foreach ($valuearray as $key => $value) { + str_replace("\r", "\n", $value); + if (strstr($value, "\n")) { + unset($tag_data_vorbiscomment[$tag_key][$key]); + $multilineexploded = explode("\n", $value); + foreach ($multilineexploded as $newcomment) { + if (strlen(trim($newcomment)) > 0) { + $tag_data_vorbiscomment[$tag_key][] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $newcomment); + } + } + } elseif (is_string($value) || is_numeric($value)) { + $tag_data_vorbiscomment[$tag_key][$key] = getid3_lib::iconv_fallback($this->tag_encoding, 'UTF-8', $value); + } else { + $this->warnings[] = '$data['.$tag_key.']['.$key.'] is not a string value - all of $data['.$tag_key.'] NOT written to VorbisComment tag'; + unset($tag_data_vorbiscomment[$tag_key]); + break; + } + } + } + $this->MergeExistingTagData('vorbiscomment', $tag_data_vorbiscomment); + return $tag_data_vorbiscomment; + } + + function FormatDataForMetaFLAC() { + // FLAC & OggFLAC use VorbisComments same as OggVorbis + // but require metaflac to do the writing rather than vorbiscomment + return $this->FormatDataForVorbisComment(); + } + + function FormatDataForReal() { + $tag_data_real['title'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['TITLE'])); + $tag_data_real['artist'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['ARTIST'])); + $tag_data_real['copyright'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['COPYRIGHT'])); + $tag_data_real['comment'] = getid3_lib::iconv_fallback($this->tag_encoding, 'ISO-8859-1', @implode(' ', @$this->tag_data['COMMENT'])); + + $this->MergeExistingTagData('real', $tag_data_real); + return $tag_data_real; + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/write.real.php b/modules/id3/getid3/write.real.php new file mode 100644 index 00000000..5ede28d1 --- /dev/null +++ b/modules/id3/getid3/write.real.php @@ -0,0 +1,140 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// write.real.php // +// module for writing RealAudio/RealVideo tags // +// dependencies: module.tag.real.php // +// /// +///////////////////////////////////////////////////////////////// + +class getid3_write_real +{ + var $filename; + var $tag_data; + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + var $paddedlength = 512; // minimum length of CONT tag in bytes + + function getid3_write_real() { + return true; + } + + function WriteReal() { + // File MUST be writeable - CHMOD(646) at least + if (is_writeable($this->filename)) { + if ($fp_source = @fopen($this->filename, 'r+b')) { + + // Initialize getID3 engine + $getID3 = new getID3; + $OldThisFileInfo = $getID3->analyze($this->filename); + if (empty($OldThisFileInfo['chunks']) && !empty($OldThisFileInfo['old_ra_header'])) { + $this->errors[] = 'Cannot write Real tags on old-style file format'; + return false; + } + + $OldPROPinfo = false; + $StartOfDATA = false; + foreach ($OldThisFileInfo['chunks'] as $chunknumber => $chunkarray) { + if ($chunkarray['name'] == 'PROP') { + $OldPROPinfo = $chunkarray; + } elseif ($chunkarray['name'] = 'DATA') { + $StartOfDATA = $chunkarray['offset']; + } + } + + if (!empty($OldPROPinfo['length'])) { + $this->paddedlength = max($OldPROPinfo['length'], $this->paddedlength); + } + + $new_real_tag_data = GenerateRealTag(); + + if (@$OldPROPinfo['length'] == $new_real_tag_data) { + + // new data length is same as old data length - just overwrite + fseek($fp_source, $OldPROPinfo['offset'], SEEK_SET); + fwrite($fp_source, $new_real_tag_data); + + } else { + + if (empty($OldPROPinfo)) { + // no existing PROP chunk + $BeforeOffset = $StartOfDATA; + $AfterOffset = $StartOfDATA; + } else { + // new data is longer than old data + $BeforeOffset = $OldPROPinfo['offset']; + $AfterOffset = $OldPROPinfo['offset'] + $OldPROPinfo['length']; + } + + + } + + fclose($fp_source); + return true; + + } else { + $this->errors[] = 'Could not open '.$this->filename.' mode "r+b"'; + return false; + } + } + $this->errors[] = 'File is not writeable: '.$this->filename; + return false; + } + + function GenerateRealTag() { + $RealCONT = "\x00\x00"; // object version + + $RealCONT .= BigEndian2String(strlen(@$this->tag_data['title']), 4); + $RealCONT .= @$this->tag_data['title']; + + $RealCONT .= BigEndian2String(strlen(@$this->tag_data['artist']), 4); + $RealCONT .= @$this->tag_data['artist']; + + $RealCONT .= BigEndian2String(strlen(@$this->tag_data['copyright']), 4); + $RealCONT .= @$this->tag_data['copyright']; + + $RealCONT .= BigEndian2String(strlen(@$this->tag_data['comment']), 4); + $RealCONT .= @$this->tag_data['comment']; + + if ($this->paddedlength > (strlen($RealCONT) + 8)) { + $RealCONT .= str_repeat("\x00", $this->paddedlength - strlen($RealCONT) - 8); + } + + $RealCONT = 'CONT'.BigEndian2String(strlen($RealCONT) + 8, 4).$RealCONT; // CONT chunk identifier + chunk length + + return $RealCONT; + } + + function RemoveReal() { + // File MUST be writeable - CHMOD(646) at least + if (is_writeable($this->filename)) { + if ($fp_source = @fopen($this->filename, 'r+b')) { + +return false; + //fseek($fp_source, -128, SEEK_END); + //if (fread($fp_source, 3) == 'TAG') { + // ftruncate($fp_source, filesize($this->filename) - 128); + //} else { + // // no real tag to begin with - do nothing + //} + fclose($fp_source); + return true; + + } else { + $this->errors[] = 'Could not open '.$this->filename.' mode "r+b"'; + } + } else { + $this->errors[] = $this->filename.' is not writeable'; + } + return false; + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/getid3/write.vorbiscomment.php b/modules/id3/getid3/write.vorbiscomment.php new file mode 100644 index 00000000..f93b1a1c --- /dev/null +++ b/modules/id3/getid3/write.vorbiscomment.php @@ -0,0 +1,124 @@ +<?php +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// See readme.txt for more details // +///////////////////////////////////////////////////////////////// +// // +// write.vorbiscomment.php // +// module for writing VorbisComment tags // +// dependencies: /helperapps/vorbiscomment.exe // +// /// +///////////////////////////////////////////////////////////////// + + +class getid3_write_vorbiscomment +{ + + var $filename; + var $tag_data; + var $warnings = array(); // any non-critical errors will be stored here + var $errors = array(); // any critical errors will be stored here + + function getid3_write_vorbiscomment() { + return true; + } + + function WriteVorbisComment() { + + if (!ini_get('safe_mode')) { + + // Create file with new comments + $tempcommentsfilename = tempnam('*', 'getID3'); + if ($fpcomments = @fopen($tempcommentsfilename, 'wb')) { + + foreach ($this->tag_data as $key => $value) { + foreach ($value as $commentdata) { + fwrite($fpcomments, $this->CleanVorbisCommentName($key).'='.$commentdata."\n"); + } + } + fclose($fpcomments); + + } else { + + $this->errors[] = 'failed to open temporary tags file "'.$tempcommentsfilename.'", tags not written'; + return false; + + } + + $oldignoreuserabort = ignore_user_abort(true); + if (GETID3_OS_ISWINDOWS) { + + if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) { + //$commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w --raw -c "'.$tempcommentsfilename.'" "'.str_replace('/', '\\', $this->filename).'"'; + // vorbiscomment works fine if you copy-paste the above commandline into a command prompt, + // but refuses to work with `backtick` if there are "doublequotes" present around BOTH + // the metaflac pathname and the target filename. For whatever reason...?? + // The solution is simply ensure that the metaflac pathname has no spaces, + // and therefore does not need to be quoted + + // On top of that, if error messages are not always captured properly under Windows + // To at least see if there was a problem, compare file modification timestamps before and after writing + clearstatcache(); + $timestampbeforewriting = filemtime($this->filename); + + $commandline = GETID3_HELPERAPPSDIR.'vorbiscomment.exe -w --raw -c "'.$tempcommentsfilename.'" "'.$this->filename.'" 2>&1'; + $VorbiscommentError = `$commandline`; + + if (empty($VorbiscommentError)) { + clearstatcache(); + if ($timestampbeforewriting == filemtime($this->filename)) { + $VorbiscommentError = 'File modification timestamp has not changed - it looks like the tags were not written'; + } + } + } else { + $VorbiscommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR; + } + + } else { + + $commandline = 'vorbiscomment -w --raw -c "'.$tempcommentsfilename.'" "'.$this->filename.'" 2>&1'; + $VorbiscommentError = `$commandline`; + + } + + // Remove temporary comments file + unlink($tempcommentsfilename); + ignore_user_abort($oldignoreuserabort); + + if (!empty($VorbiscommentError)) { + + $this->errors[] = 'system call to vorbiscomment failed with message: '."\n\n".$VorbiscommentError; + return false; + + } + + return true; + } + + $this->errors[] = 'PHP running in Safe Mode (backtick operator not available) - cannot call vorbiscomment, tags not written'; + return false; + } + + function DeleteVorbisComment() { + $this->tag_data = array(array()); + return $this->WriteVorbisComment(); + } + + function CleanVorbisCommentName($originalcommentname) { + // A case-insensitive field name that may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded. + // ASCII 0x41 through 0x5A inclusive (A-Z) is to be considered equivalent to ASCII 0x61 through + // 0x7A inclusive (a-z). + + // replace invalid chars with a space, return uppercase text + // Thanks Chris Bolt <chris-getid3Øbolt*cx> for improving this function + // note: ereg_replace() replaces nulls with empty string (not space) + return strtoupper(ereg_replace('[^ -<>-}]', ' ', str_replace("\x00", ' ', $originalcommentname))); + + } + +} + +?>
\ No newline at end of file diff --git a/modules/id3/helperapps/cygwin1.dll b/modules/id3/helperapps/cygwin1.dll Binary files differnew file mode 100644 index 00000000..4f2596ce --- /dev/null +++ b/modules/id3/helperapps/cygwin1.dll diff --git a/modules/id3/helperapps/head.exe b/modules/id3/helperapps/head.exe Binary files differnew file mode 100755 index 00000000..7f5ef876 --- /dev/null +++ b/modules/id3/helperapps/head.exe diff --git a/modules/id3/helperapps/md5sum.exe b/modules/id3/helperapps/md5sum.exe Binary files differnew file mode 100755 index 00000000..03131206 --- /dev/null +++ b/modules/id3/helperapps/md5sum.exe diff --git a/modules/id3/helperapps/metaflac.exe b/modules/id3/helperapps/metaflac.exe Binary files differnew file mode 100755 index 00000000..cdf41c50 --- /dev/null +++ b/modules/id3/helperapps/metaflac.exe diff --git a/modules/id3/helperapps/readme.txt b/modules/id3/helperapps/readme.txt new file mode 100644 index 00000000..aea9002b --- /dev/null +++ b/modules/id3/helperapps/readme.txt @@ -0,0 +1,47 @@ +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// /helperapps/readme.txt - part of getID3() // +// List of binary files required under Windows for some // +// features and/or file formats // +// See /readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + +This directory should contain binaries of various helper applications +that getID3() depends on to handle some file formats under Windows. + +The location of this directory is configurable in /getid3/getid3.php +as GETID3_HELPERAPPSDIR + +If this directory is empty, or you are missing any files, please +download the latest version of the "getID3()-WindowsSupport" package +from the usual download location (http://getid3.sourceforge.net) + + + +Included files: +===================================================== + +Taken from http://www.cygwin.com/ +* cygwin1.dll + +Taken from http://unxutils.sourceforge.net/ +* head.exe +* md5sum.exe +* tail.exe + +Taken from http://ebible.org/mpj/software.htm +* sha1sum.exe + +Taken from http://www.vorbis.com/download.psp +* vorbiscomment.exe + +Taken from http://flac.sourceforge.net/download.html +* metaflac.exe + +Taken from http://www.etree.org/shncom.html +* shorten.exe diff --git a/modules/id3/helperapps/sha1sum.exe b/modules/id3/helperapps/sha1sum.exe Binary files differnew file mode 100755 index 00000000..f1a52164 --- /dev/null +++ b/modules/id3/helperapps/sha1sum.exe diff --git a/modules/id3/helperapps/shorten.exe b/modules/id3/helperapps/shorten.exe Binary files differnew file mode 100755 index 00000000..b82d6c35 --- /dev/null +++ b/modules/id3/helperapps/shorten.exe diff --git a/modules/id3/helperapps/tail.exe b/modules/id3/helperapps/tail.exe Binary files differnew file mode 100755 index 00000000..36c2abc2 --- /dev/null +++ b/modules/id3/helperapps/tail.exe diff --git a/modules/id3/helperapps/vorbiscomment.exe b/modules/id3/helperapps/vorbiscomment.exe Binary files differnew file mode 100755 index 00000000..9e4e4b98 --- /dev/null +++ b/modules/id3/helperapps/vorbiscomment.exe diff --git a/modules/id3/license.txt b/modules/id3/license.txt new file mode 100644 index 00000000..9fec8082 --- /dev/null +++ b/modules/id3/license.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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 + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/modules/id3/readme.txt b/modules/id3/readme.txt new file mode 100644 index 00000000..2c4d8eeb --- /dev/null +++ b/modules/id3/readme.txt @@ -0,0 +1,329 @@ +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org /// +///////////////////////////////////////////////////////////////// + + This code is released under the GNU GPL: + http://www.gnu.org/copyleft/gpl.html + + +---------------------------------------------+ + | If you do use this code somewhere, send me | + | an email and tell me how/where you used it. | + | | + | If you want to donate, there is a link on | + | http://www.getid3.org for PayPal donations. | + +---------------------------------------------+ + +Quick Start +=========== + +Q: How can I check that getID3() works on my server/files? +A: Unzip getID3() to a directory, then access /demos/demo.browse.php + + +Mailing List +============ + +It's highly recommended that you sign up for the getID3()-Announce +mailing list to be notified when new versions are released, as they +may contain important bugfixes (as well as new features, of course). +Sign up for the mailing list from http://getid3.sourceforge.net + + +What does getID3() do? +====================== + +Reads & parses (to varying degrees): + ¤ tags: + * APE (v1 and v2) + * ID3v1 (& ID3v1.1) + * ID3v2 (v2.4, v2.3, v2.2) + * Lyrics3 (v1 & v2) + + ¤ audio-lossy: + * MP3/MP2/MP1 + * MPC / Musepack + * Ogg (Vorbis, OggFLAC, Speex) + * RealAudio + * Speex + * VQF + + ¤ audio-lossless: + * AIFF + * AU + * Bonk + * CD-audio (*.cda) + * FLAC + * LA (Lossless Audio) + * LPAC + * MIDI + * Monkey's Audio + * OptimFROG + * RKAU + * VOC + * WAV (RIFF) + * WavPack + + ¤ audio-video: + * ASF: ASF, Windows Media Audio (WMA), Windows Media Video (WMV) + * AVI (RIFF) + * Flash + * MPEG-1 / MPEG-2 + * NSV (Nullsoft Streaming Video) + * Quicktime + * RealVideo + + ¤ still image: + * BMP + * GIF + * JPEG + * PNG + + ¤ data: + * ISO-9660 CD-ROM image (directory structure) + * SZIP (limited support) + * ZIP (directory structure) + + +Writes: + * ID3v1 (& ID3v1.1) + * ID3v2 (v2.3 & v2.4) + * VorbisComment on OggVorbis + * VorbisComment on FLAC (not OggFLAC) + * APE v2 + * Lyrics3 (delete only) + + +Requirements +============ + +* PHP 4.1.0 (or higher) + + +Usage +===== + +require_once('/path/getid3.php'); +$getID3 = new getID3; +$fileinfo = $getID3->analyze($filename); + +For an example of a complete directory-browsing, file-scanning +implementation of getID3(), please run /demos/demo.browse.php + +See /demos/demo.basic.php for a very basic use of getID3() with no +fancy output, just scanning one file. + +See /demos/scandir.php for a sample recursive scanning code that +scans every file in a given directory, and all sub-directories + +See /demos/simple.php for a simple example script that scans all +files in one directory and output artist, title, bitrate and playtime + +See /demos/mimeonly.php for a simple example script that scans a +single file and returns only the MIME information + +To analyze remote files over HTTP or FTP you need to copy the file +locally first before running getID3(). Your code would look something +like this: + +// Copy remote file locally to scan with getID3() +$remotefilename = 'http://www.example.com/filename.mp3'; +if ($fp_remote = fopen($remotefilename, 'rb')) { + $localtempfilename = tempnam('/tmp', 'getID3'); + if ($fp_local = fopen($localtempfilename, 'wb')) { + while ($buffer = fread($fp_remote, 8192)) { + fwrite($fp_local, $buffer); + } + fclose($fp_local); + + // Initialize getID3 engine + $getID3 = new getID3; + + $ThisFileInfo = $getID3->analyze($filename); + + // Delete temporary file + unlink($localtempfilename); + } + fclose($fp_remote); +} + + +// Writing tags: +require_once('getid3.php'); +$getID3 = new getID3; +getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'write.php', __FILE__); +$tagwriter = new getid3_writetags; +$tagwriter->filename = $Filename; +$tagwriter->tagformats = array('id3v2.3', 'ape'); +$TagData['title'][] = 'Song Title'; +$TagData['artist'][] = 'Artist Name'; +$tagwriter->tag_data = array(; +if ($tagwriter->WriteTags()) { + echo 'success'; +} else { + echo 'failure'; +} + + + +What does the returned data structure look like? +================================================ + +See getid3.structure.txt + +It is recommended that you look at the output of +/demos/demo.browse.php scanning the file(s) you're interested in to +confirm what data is actually returned for any particular filetype in +general, and your files in particular, as the actual data returned +may vary considerably depending on what information is available in +the file itself. + + +Notes +===== + +If the format parser encounters a critical problem, it will return +something in $fileinfo['error'], describing the encountered error. If +a less critical error or notice is generated it will appear in +$fileinfo['warning']. Both keys may contain more than one warning or +error. If something is returned in ['error'] then the file was not +correctly parsed and returned data may or may not be correct and/or +complete. If something is returned in ['warning'] (and not ['error']) +then the data that is returned is OK - usually getID3() is reporting +errors in the file that have been worked around due to known bugs in +other programs. Some warnings may indicate that the data that is +returned is OK but that some data could not be extracted due to +errors in the file. + + +Known Bugs/Issues +================= + +See the end of getid3.changelog.txt for notes on known issues with +getID3(), encoders, players, etc. + + +Disclaimer +========== + +getID3() has been tested on many systems, on many types of files, +under many operating systems, and is generally believe to be stable +and safe. That being said, there is still the chance there is an +undiscovered and/or unfixed bug that may potentially corrupt your +file, especially within the writing functions. By using getID3() you +agree that it's not my fault if any of your files are corrupted. +In fact, I'm not liable for anything :) + + +///////////////////////////////////////////////////////////////////// + +GNU General Public License - see license.txt + +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: +Free Software Foundation, Inc. +59 Temple Place - Suite 330 +Boston, MA 02111-1307, USA. + + +///////////////////////////////////////////////////////////////////// + +Reference material: +[www.id3.org material now mirrored at http://id3lib.sourceforge.net/id3/] +* http://www.id3.org/id3v2.4.0-structure.txt +* http://www.id3.org/id3v2.4.0-frames.txt +* http://www.id3.org/id3v2.4.0-changes.txt +* http://www.id3.org/id3v2.3.0.txt +* http://www.id3.org/id3v2-00.txt +* http://www.id3.org/mp3frame.html +* http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html <mathewhendry@hotmail.com> +* http://www.dv.co.yu/mpgscript/mpeghdr.htm +* http://www.mp3-tech.org/programmer/frame_header.html +* http://users.belgacom.net/gc247244/extra/tag.html +* http://gabriel.mp3-tech.org/mp3infotag.html +* http://www.id3.org/iso4217.html +* http://www.unicode.org/Public/MAPPINGS/ISO8859/8859-1.TXT +* http://www.xiph.org/ogg/vorbis/doc/framing.html +* http://www.xiph.org/ogg/vorbis/doc/v-comment.html +* http://leknor.com/code/php/class.ogg.php.txt +* http://www.id3.org/iso639-2.html +* http://www.id3.org/lyrics3.html +* http://www.id3.org/lyrics3200.html +* http://www.psc.edu/general/software/packages/ieee/ieee.html +* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html +* http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html +* http://www.jmcgowan.com/avi.html +* http://www.wotsit.org/ +* http://www.herdsoft.com/ti/davincie/davp3xo2.htm +* http://www.mathdogs.com/vorbis-illuminated/bitstream-appendix.html +* "Standard MIDI File Format" by Dustin Caldwell (from www.wotsit.org) +* http://midistudio.com/Help/GMSpecs_Patches.htm +* http://www.xiph.org/archives/vorbis/200109/0459.html +* http://www.replaygain.org/ +* http://www.lossless-audio.com/ +* http://download.microsoft.com/download/winmediatech40/Doc/1.0/WIN98MeXP/EN-US/ASF_Specification_v.1.0.exe +* http://mediaxw.sourceforge.net/files/doc/Active%20Streaming%20Format%20(ASF)%201.0%20Specification.pdf +* http://www.uni-jena.de/~pfk/mpp/sv8/ +* http://jfaul.de/atl/ +* http://www.uni-jena.de/~pfk/mpp/ +* http://www.libpng.org/pub/png/spec/png-1.2-pdg.html +* http://www.real.com/devzone/library/creating/rmsdk/doc/rmff.htm +* http://www.fastgraph.com/help/bmp_os2_header_format.html +* http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm +* http://flac.sourceforge.net/format.html +* http://www.research.att.com/projects/mpegaudio/mpeg2.html +* http://www.audiocoding.com/wiki/index.php?page=AAC +* http://libmpeg.org/mpeg4/doc/w2203tfs.pdf +* http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt +* http://developer.apple.com/techpubs/quicktime/qtdevdocs/RM/frameset.htm +* http://www.nullsoft.com/nsv/ +* http://www.wotsit.org/download.asp?f=iso9660 +* http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html +* http://www.cdroller.com/htm/readdata.html +* http://www.speex.org/manual/node10.html +* http://www.harmony-central.com/Computer/Programming/aiff-file-format.doc +* http://www.faqs.org/rfcs/rfc2361.html +* http://ghido.shelter.ro/ +* http://www.ebu.ch/tech_t3285.pdf +* http://www.sr.se/utveckling/tu/bwf +* http://ftp.aessc.org/pub/aes46-2002.pdf +* http://cartchunk.org:8080/ +* http://www.broadcastpapers.com/radio/cartchunk01.htm +* http://www.hr/josip/DSP/AudioFile2.html +* http://home.attbi.com/~chris.bagwell/AudioFormats-11.html +* http://www.pure-mac.com/extkey.html +* http://cesnet.dl.sourceforge.net/sourceforge/bonkenc/bonk-binary-format-0.9.txt +* http://www.headbands.com/gspot/ +* http://www.openswf.org/spec/SWFfileformat.html +* http://j-faul.virtualave.net/ +* http://www.btinternet.com/~AnthonyJ/Atari/programming/avr_format.html +* http://cui.unige.ch/OSG/info/AudioFormats/ap11.html +* http://sswf.sourceforge.net/SWFalexref.html +* http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt +* http://www-lehre.informatik.uni-osnabrueck.de/~fbstark/diplom/docs/swf/Flash_Uncovered.htm +* http://developer.apple.com/quicktime/icefloe/dispatch012.html +* http://www.csdn.net/Dev/Format/graphics/PCD.htm +* http://tta.iszf.irk.ru/ +* http://www.atsc.org/standards/a_52a.pdf +* http://www.alanwood.net/unicode/ +* http://www.freelists.org/archives/matroska-devel/07-2003/msg00010.html +* http://www.its.msstate.edu/net/real/reports/config/tags.stats +* http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt +* http://brennan.young.net/Comp/LiveStage/things.html +* http://www.multiweb.cz/twoinches/MP3inside.htm +* http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended +* http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/ +* http://www.unicode.org/unicode/faq/utf_bom.html +* http://tta.corecodec.org/?menu=format +* http://www.scvi.net/nsvformat.htm
\ No newline at end of file diff --git a/modules/id3/structure.txt b/modules/id3/structure.txt new file mode 100644 index 00000000..0d506cf8 --- /dev/null +++ b/modules/id3/structure.txt @@ -0,0 +1,2247 @@ +///////////////////////////////////////////////////////////////// +/// getID3() by James Heinrich <info@getid3.org> // +// available at http://getid3.sourceforge.net // +// or http://www.getid3.org // +///////////////////////////////////////////////////////////////// +// // +// changelog.txt - part of getID3() // +// See readme.txt for more details // +// /// +///////////////////////////////////////////////////////////////// + +What does the returned data structure look like? +================================================ + +Note that what is described below is only a rough guide to what data +is actually returned by getID3(), since the actual data returned +depends entirely on what data is in your file, what type of file it +is, what kind of data is in the tags, etc. In addition, some formats +(Quicktime for example) use a freeform recursive structure that is +impossible to document completely. + +In the vast majority of cases, all the data you'll need is located +in the root of the array or the special arrays described below in +Section 1 (['audio'], ['video'], ['tags_html'], ['replay_gain']). + +It is suggested that for most applications you should use tag data +from the root ['tags_html'] array, as this is the only location +where data is stored in a consistant format: HTML-compatible +character entities (ie Ӓ) for characters outside the 0x20-0x7F +range (printable ISO-8859-1 characters). This data can be used as-is +for output in HTML, and can be converted to whatever character set +you wish to use if the output is not HTML. + +If you want to merge all available tags (for example, ID3v2 + ID3v1) +into one array, you can call +getid3_lib::CopyTagsToComments($ThisFileInfo) +and you'll then have ['comments'] and ['comments_html'] which are +identical to ['tags'] and ['tags_html'] except the array is one +dimension shorter (no tag type array keys). For example, artist is: +['tags_html']['id3v1']['artist'][0] or ['comments_html']['artist'][0] + + +Some commonly-used information is found in these locations: + +File type: ['fileformat'] // ex 'mp3' +Song length: ['playtime_string'] // ex '3:45' (minutes:seconds) + ['playtime_seconds'] // ex 225.13 (seconds) +Overall bitrate: ['bitrate'] // ex 113485.71 (bits-per-second - divide by 1000 for kbps) +Audio frequency: ['audio']['sample_rate'] // ex 44100 (Hertz) +Artist name: ['comments_html']['artist'][0] // ex 'Elvis' (if CopyTagsToComments() is used - see above) + // more than one artist may be present, you may want to use implode: + // implode(' & ', ['comments_html']['artist']) + + +///////////////////////////////////////////////////////////////// + +array() { + // SECTION 1: Values that are present for most or all file types + + ['getID3version']=>string() // version of getID3() that scanned this file (ex: '1.6.2') + ['error']=>array() // if present, contains one or more fatal error messages + ['warning']=>array() // if present, contains one or more non-fatal warning messages + ['exist']=>boolean() // does this file actually exist? + ['fileformat']=>string() // one of the standard filetype abbreviations ('mp3', 'riff', 'quicktime', etc) + ['filename']=>string() // filename only, no path + ['filenamepath']=>string() // full filename with path + ['filepath']=>string() // path to file, not including filename + ['filesize']=>integer() // filesize in bytes + ['md5_file']=>string() // md5 hash of entire file + ['md5_data']=>string() // md5 hash of portion of file excluding prepended and appeneded metainformation tags (ID3, APE, etc) - may be identical to ['md5_file'] + ['md5_data_source']=>string() // md5 hash of original source file before compression (currently used by FLAC, OptimFROG, WavPack v4+) + ['sha1_file']=>string() // sha1 hash of entire file + ['sha1_data']=>string() // sha1 hash of portion of file excluding prepended and appeneded metainformation tags (ID3, APE, etc) - may be identical to ['md5_file'] + ['avdataoffset']=>integer() // offset in bytes where audio/video data starts and prepended tags end + ['avdataend']=>integer() // offset in bytes where audio/video data ends and appended tags start + ['bitrate']=>double() // average bitrate for entire file (all audio/video streams), in bits per second + ['mime_type']=>string() // if present, MIME type of scanned file + ['playtime_seconds']=>double() // playing time of file, in seconds + ['playtime_string']=>string() // playing time of file, formatted as <minutes>:<seconds> + ['tags']=>array() // array of all metainformation tags present in file ('id3v1', 'id3v2', 'ape', 'riff', 'asf', etc) + ['audio']=>array() { + ['bitrate']=>double() // average bitrate for audio portion of file (all audio streams), in bits per second + ['bitrate_mode']=>string() // 'cbr' (Constant Bit Rate) or 'vbr' (Variable Bit Rate) + ['bits_per_sample']=>integer() // + ['channelmode']=>string() // 'mono' or 'stereo' + ['channels']=>integer() // number of audio channels + ['codec']=>string() // name of audio compression codec + ['compression_ratio']=>double() // ratio of compressed byte size of audio to uncompressed size + ['dataformat']=>string() // one of the standard filetype abbreviations ('mp3', 'wma', etc) + ['encoder']=>string() // name and version of encoder used to create file, if known + ['lossless']=>boolean() // true = lossless compression; false = lossy compression + ['sample_rate']=>integer() + } + ['video']=>array() { + ['bitrate']=>integer() // average bitrate for video portion of file (all video streams), in bits per second + ['bitrate_mode']=>string() // 'cbr' (Constant Bit Rate) or 'vbr' (Variable Bit Rate) + ['bits_per_sample']=>integer() // + ['codec']=>string() // name of video compression codec + ['compression_ratio']=>double() // ratio of compressed byte size of video to uncompressed size + ['dataformat']=>string() // one of the standard filetype abbreviations ('avi', 'mpeg', etc) + ['encoder']=>string() // name and version of encoder used to create file, if known + ['frame_rate']=>double() // frames per second + ['lossless']=>boolean() // true = lossless compression; false = lossy compression + ['resolution_x']=>integer() // horizontal dimension of video/image in pixels + ['resolution_y']=>integer() // vertical dimension of video/image in pixels + ['pixel_aspect_ratio']=>double() // pixel display aspect ratio + } + ['tags']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + [<key name>]=>array() // <key name> can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } + ['tags_html']=>array() { // identical to ['tags'], but with all entries converted to HTML entities as appropriate from various source encodings + [<key name>]=>array() // + } + ['replay_gain']=>array() { // replay gain information combined from any source that contains this information (LAME, ID3v2, Vorbis, APE, etc) + ['audiophile']=>array() { + ['adjustment']=>double() + ['originator']=>string() + ['peak']=>double() + } + ['radio']=>array() { + ['adjustment']=>double() + ['originator']=>string() + ['peak']=>double() + } + } + + + // SECTION 2: Values that are present for specific file types only + + ['aac']=>array() { // AAC - Advanced Audio Coding / MPEG-4 + ['bitrate_distribution']=>array() // + ['header']=>array() { // + ['channel_configuration']=>integer() // + ['crc_present']=>boolean() // + ['home']=>boolean() // + ['layer']=>integer() // + ['mpeg_version']=>integer() // + ['original']=>boolean() // + ['private']=>boolean() // + ['profile_id']=>integer() // + ['profile_text']=>string() // + ['sample_frequency']=>integer() // + ['sample_frequency_index']=>integer() // + ['synch']=>integer() // + } // + ['header_type']=>string() // + } // + // + ['ape']=>array() // + { // + ['comments']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + [<key name>]=>array() // <key name> can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } // + ['footer']=>array() // + { // + ['flags']=>array() // + ['raw']=>array() // + ['tag_version']=>integer() // + } // + ['header']=>array() // + { // + ['flags']=>array() // + ['raw']=>array() // + ['tag_version']=>integer() // + } // + ['items']=>array() { // array of array of strings containing metainformation + [<key name>]=>array() { // <key name> can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + ['data']=>array() { // array of one or more Unicode values + ['data_ascii']=>array() { // array of values converted approximately from Unicode to ASCII + ['flags']=>array() // + } // + } // + ['tag_offset_end']=>integer() // + ['tag_offset_start']=>integer() // + } // + + + ['asf']=>array() { // ASF - Advanced Streaming Format (ASF, Windows Media Audio (WMA), Windows Media Video (WMV)) + ['audio_media']=>array() { // + [<x>]=>array() { // + ['bitrate']=>integer() // + ['bits_per_sample']=>integer() // + ['channels']=>integer() // + ['codec']=>string() // + ['codec_data']=>string() // + ['codec_data_size']=>integer() // + ['raw']=>array() { // + ['nAvgBytesPerSec']=>integer() // + ['wBitsPerSample']=>integer() // + ['nBlockAlign']=>integer() // + ['nChannels']=>integer() // + ['nSamplesPerSec']=>integer() // + ['wFormatTag']=>integer() // + } // + ['sample_rate']=>integer() // + } // + } // + ['codec_list']=>array() { // + ['codec_entries']=>array() { // + [<x>]=>array() { // + ['description']=>string() // + ['description_ascii']=>string() // + ['information']=>string() // + ['name']=>string() // + ['name_ascii']=>string() // + ['type']=>string() // + ['type_raw']=>integer() // + } // + } // + ['codec_entries_count']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['reserved']=>string() // + ['reserved_guid']=>string() // + } // + ['comments']=>array() { // array of comment values, derived from ['content_description'] + ['album']=>string() // + ['artist']=>string() // + ['comment']=>string() // + ['copyright']=>string() // + ['genre']=>string() // + ['title']=>string() // + ['track']=>string() // + ['year']=>string() // + } // + ['content_description']=>array() { // raw values - should use values from ['comments'] instead + ['author']=>string() // + ['author_ascii']=>string() // + ['author_length']=>integer() // + ['copyright']=>string() // + ['copyright_ascii']=>string() // + ['copyright_length']=>integer() // + ['description']=>string() // + ['description_ascii']=>string() // + ['description_length']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['rating']=>string() // + ['rating_ascii']=>string() // + ['rating_length']=>integer() // + ['title']=>string() // + ['title_ascii']=>string() // + ['title_length']=>integer() // + } // + ['data_object']=>array() { // + ['fileid']=>string() // + ['fileid_guid']=>string() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['reserved']=>integer() // + ['total_data_packets']=>integer() // + } // + ['extended_content_description']=>array() { // + ['content_descriptors']=>array() { // + [<x>]=>array() { // + ['name']=>string() // + ['name_ascii']=>string() // + ['name_length']=>integer() // + ['value']=>string() // + ['value_ascii']=>string() // + ['value_length']=>integer() // + ['value_type']=>integer() // + } // + } // + ['content_descriptors_count']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + } // + ['file_properties_object']=>array() { // + ['creation_date']=>double() // + ['creation_date_unix']=>double() // + ['data_packets']=>integer() // + ['fileid']=>string() // + ['fileid_guid']=>string() // + ['filesize']=>integer() // + ['flags']=>array() { // + ['broadcast']=>boolean() // + ['seekable']=>boolean() // + } // + ['flags_raw']=>integer() // + ['max_bitrate']=>integer() // + ['max_packet_size']=>integer() // + ['min_packet_size']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['play_duration']=>double() // + ['preroll']=>integer() // + ['send_duration']=>double() // + } // + ['header_extension_object']=>array() { // + ['extension_data']=>integer() // + ['extension_data_size']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['reserved_1']=>string() // + ['reserved_1_guid']=>string() // + ['reserved_2']=>integer() // + } // + ['header_object']=>array() { // + ['headerobjects']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['reserved1']=>integer() // + ['reserved2']=>integer() // + } // + ['marker_object']=>array() { // + ['markers_count']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['reserved']=>string() // + ['reserved_2']=>integer() // + ['reserved_guid']=>string() // + } // + ['stream_bitrate_properties']=>array() { // + ['bitrate_records']=>array() { // + [<x>]=>array() { // + ['bitrate']=>integer() // + ['flags_raw']=>integer() // + ['flags']=>array() { // + ['stream_number']=>integer() // + } // + } // + } // + ['bitrate_records_count']=>integer() // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + } // + ['stream_properties_object']=>array() { // + [<x>]=>array() { // + ['error_correct_data']=>string() // + ['error_correct_guid']=>string() // + ['error_correct_type']=>string() // + ['error_data_length']=>integer() // + ['flags_raw']=>integer() // + ['flags']=>array() { // + ['encrypted']=>boolean() // + } // + ['objectid']=>string() // + ['objectid_guid']=>string() // + ['objectsize']=>integer() // + ['stream_type']=>string() // + ['stream_type_guid']=>string() // + ['time_offset']=>integer() // + ['type_data_length']=>integer() // + ['type_specific_data']=>string() // + } // + } // + ['video_media']=>array() { // + [<x>]=>array() { // + ['flags']=>integer() // + ['format_data']=>array() { // + ['bits_per_pixel']=>integer() // + ['codec']=>string() // + ['codec_data']=>boolean() // + ['codec_fourcc']=>string() // + ['colors_important']=>integer() // + ['colors_used']=>integer() // + ['format_data_size']=>integer() // + ['horizontal_pels']=>integer() // + ['image_height']=>integer() // + ['image_size']=>integer() // + ['image_width']=>integer() // + ['reserved']=>integer() // + ['vertical_pels']=>integer() // + } // + ['format_data_size']=>integer() // + ['image_height']=>integer() // + ['image_width']=>integer() // + } // + } // + } // + + + ['au']=>array() { // AU - Next/Sun AUdio format + ['bits_per_sample']=>integer() // + ['channels']=>integer() // + ['comment']=>string() // + ['data_format']=>string() // + ['data_format_id']=>integer() // + ['data_size']=>integer() // + ['header_length']=>integer() // + ['sample_rate']=>integer() // + ['used_bits_per_sample']=>integer() // + } // + + + ['bmp']=>array() { // BMP - OS/2 or Windows BitMaP + ['header']=>array() { // + ['compression']=>string() // + ['raw']=>array() { // + ['bits_per_pixel']=>integer() // + ['bmp_data_size']=>integer() // + ['colors_important']=>integer() // + ['colors_used']=>integer() // + ['compression']=>integer() // + ['data_offset']=>integer() // + ['filesize']=>integer() // + ['header_size']=>integer() // + ['height']=>integer() // + ['identifier']=>string() // + ['planes']=>integer() // + ['resolution_h']=>integer() // + ['resolution_v']=>integer() // + ['width']=>integer() // + } // + } // + ['type_os']=>string() // + ['type_version']=>integer() // + } // + + + ['bonk']=>array() { // BONK - lossy/lossless audio compression (www.bonkenc.org) + ['BONK']=>array() { // + ['channels']=>integer() // + ['downsampling_ratio']=>integer() // + ['joint_stereo']=>boolean() // + ['lossless']=>boolean() // + ['number_samples']=>integer() // + ['number_taps']=>integer() // + ['offset']=>integer() // + ['sample_rate']=>integer() // + ['samples_per_packet']=>integer() // + ['size']=>integer() // + ['version']=>integer() // + } // + ['INFO']=>array() { // + ['size']=>integer() // + ['offset']=>integer() // + ['version']=>integer() // + [<x>]=>array() { // + ['nextbit']=>integer() // + ['offset']=>integer() // + } // + } // + ['dataend']=>integer() // + ['dataoffset']=>integer() // + } // + + + ['flac']=>array() { // FLAC - Free Lossless Audio Compressor + ['SEEKTABLE']=>array() { // + [<x>]=>array() { // + ['offset']=>integer() // + ['samples']=>integer() // + } // + ['placeholders']=>integer() // + ['raw']=>array() { // + ['block_data']=>string() // + ['block_length']=>integer() // + ['block_type']=>integer() // + ['block_type_text']=>string() // + ['last_meta_block']=>boolean() // + ['offset']=>integer() // + } // + } // + ['STREAMINFO']=>array() { // + ['audio_signature']=>string() // + ['bits_per_sample']=>integer() // + ['channels']=>integer() // + ['max_block_size']=>integer() // + ['max_frame_size']=>integer() // + ['min_block_size']=>integer() // + ['min_frame_size']=>integer() // + ['raw']=>array() { // + ['block_data']=>string() // + ['block_length']=>integer() // + ['block_type']=>integer() // + ['block_type_text']=>string() // + ['last_meta_block']=>boolean() // + ['offset']=>integer() // + } // + ['sample_rate']=>integer() // + ['samples_stream']=>integer() // + } // + ['VORBIS_COMMENT']=>array() { // + ['raw']=>array() { // + ['block_data']=>string() // + ['block_length']=>integer() // + ['block_type']=>integer() // + ['block_type_text']=>string() // + ['last_meta_block']=>boolean() // + ['offset']=>integer() // + } // + } // + ['compressed_audio_bytes']=>integer() // + ['compression_ratio']=>double() // + ['uncompressed_audio_bytes']=>integer() // + } // + + + ['gif']=>array() { // GIF - Graphics Interchange Format + ['global_color_table']=>array() { // + [<x>]=>integer() // + } // + ['header']=>array() { // + ['bits_per_pixel']=>integer() // + ['flags']=>array() { // + ['global_color_sorted']=>boolean() // + ['global_color_table']=>boolean() // + } // + ['global_color_size']=>integer() // + ['raw']=>array() { // + ['aspect_ratio']=>integer() // + ['bg_color_index']=>integer() // + ['flags']=>integer() // + ['height']=>integer() // + ['identifier']=>string() // + ['version']=>string() // + ['width']=>integer() // + } // + } // + ['version']=>string() // + } // + + + ['id3v1']=>array() { // ID3v1 + ['album']=>string() // + ['artist']=>string() // + ['comment']=>string() // + ['genre']=>string() // + ['genreid']=>integer() // + ['title']=>string() // + ['track']=>integer() // + ['year']=>string() // + ['padding_valid']=>boolean() // + ['comments']=>array() // + ['tag_offset_start']=>integer() // + ['tag_offset_end']=>integer() // + } // + + + ['id3v2']=>array() { // ID3v2 - www.id3.org + [<frame name>]=>array() { // <frame name> can be any of the 4-character (3-character in ID3v2.2) frame names allowed in the ID3v2 spec. Exact contents of returned array data varies with frame type. + [<x>]=>array() { // some frames types allow multiple values ('COMM' for example), others do not and do not have this array level + ['asciidata']=>boolean() // + ['asciidescription']=>string() // + ['data']=>boolean() // + ['datalength']=>integer() // + ['dataoffset']=>integer() // + ['description']=>string() // + ['encoding']=>string() // + ['encodingid']=>integer() // + ['flags']=>array() { // + ['Encryption']=>boolean() // + ['FileAlterPreservation']=>boolean() // + ['GroupingIdentity']=>boolean() // + ['ReadOnly']=>boolean() // + ['TagAlterPreservation']=>boolean() // + ['compression']=>boolean() // + } // + ['framenamelong']=>string() // + ['language']=>string() // + ['languagename']=>string() // + } // + } // + ['comments']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + [<key name>]=>array() // <key name> can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } // + ['flags']=>array() { // + ['experim']=>string() // + ['exthead']=>string() // + ['unsynch']=>string() // + } // + ['header']=>boolean() // + ['headerlength']=>integer() // + ['majorversion']=>integer() // + ['minorversion']=>integer() // + ['padding']=>array() { // + ['length']=>integer() // + ['start']=>integer() // + ['valid']=>boolean() // + } // + ['tag_offset_end']=>integer() // + ['tag_offset_start']=>integer() // + } // + + + ['iso']=>array() { // ISO-9660 - CD-ROM Image + ['directories']=>array() { // + [<x>]=>array() { // + [<x>]=>array() { // + ['file_flags']=>array() { // + ['associated']=>boolean() // + ['directory']=>boolean() // + ['extended']=>boolean() // + ['hidden']=>boolean() // + ['multiple']=>boolean() // + ['permissions']=>boolean() // + } // + ['file_identifier_ascii']=>string() // + ['filename']=>string() // + ['filesize']=>integer() // + ['offset_bytes']=>integer() // + ['raw']=>array() { // + ['extended_attribute_length']=>integer() // + ['file_flags']=>integer() // + ['file_identifier']=>string() // + ['file_identifier_length']=>integer() // + ['file_unit_size']=>integer() // + ['filesize']=>integer() // + ['interleave_gap_size']=>integer() // + ['length']=>integer() // + ['offset_logical']=>integer() // + ['recording_date_time']=>string() // + ['volume_sequence_number']=>integer() // + } // + ['recording_timestamp']=>integer() // + } // + } // + } // + ['files']=>array() { // multidimensional tree-structure array listing of all files and directories in image + [<directory name>]=>array() // entries of type array are directories (key is directory name), may contain files and/or other subdirectories + [<file name>]=>integer() // entries of type integer are files (key is file name, value is file size in bytes) + } // + ['path_table']=>array() { // + ['directories']=>array() { // + [<x>]=>array() { // + ['extended_length']=>integer() // + ['full_path']=>string() // + ['length']=>integer() // + ['location_bytes']=>integer() // + ['location_logical']=>integer() // + ['name']=>string() // + ['name_ascii']=>string() // + ['parent_directory']=>integer() // + } // + } // + ['offset']=>integer() // + ['raw']=>string() // + } // + ['primary_volume_descriptor']=>array() { // + ['abstract_file_identifier']=>string() // + ['application_identifier']=>string() // + ['bibliographic_file_identifier']=>string() // + ['copyright_file_identifier']=>string() // + ['data_preparer_identifier']=>string() // + ['offset']=>integer() // + ['publisher_identifier']=>string() // + ['raw']=>array() { // + ['abstract_file_identifier']=>string() // + ['application_data']=>string() // + ['application_identifier']=>string() // + ['bibliographic_file_identifier']=>string() // + ['copyright_file_identifier']=>string() // + ['data_preparer_identifier']=>string() // + ['file_structure_version']=>integer() // + ['logical_block_size']=>integer() // + ['path_table_l_location']=>integer() // + ['path_table_l_opt_location']=>integer() // + ['path_table_m_location']=>integer() // + ['path_table_m_opt_location']=>integer() // + ['path_table_size']=>integer() // + ['publisher_identifier']=>string() // + ['root_directory_record']=>string() // + ['standard_identifier']=>string() // + ['system_identifier']=>string() // + ['unused_1']=>string() // + ['unused_2']=>string() // + ['unused_3']=>string() // + ['unused_4']=>integer() // + ['volume_creation_date_time']=>string() // + ['volume_descriptor_type']=>integer() // + ['volume_descriptor_version']=>integer() // + ['volume_effective_date_time']=>string() // + ['volume_expiration_date_time']=>string() // + ['volume_identifier']=>string() // + ['volume_modification_date_time']=>string() // + ['volume_sequence_number']=>integer() // + ['volume_set_identifier']=>string() // + ['volume_set_size']=>integer() // + ['volume_space_size']=>integer() // + } // + ['system_identifier']=>string() // + ['volume_creation_date_time']=>integer() // + ['volume_effective_date_time']=>boolean() // + ['volume_expiration_date_time']=>boolean() // + ['volume_identifier']=>string() // + ['volume_modification_date_time']=>integer() // + ['volume_set_identifier']=>string() // + } // + ['supplementary_volume_descriptor']=>array() { // + ['abstract_file_identifier']=>string() // + ['application_identifier']=>string() // + ['bibliographic_file_identifier']=>string() // + ['copyright_file_identifier']=>string() // + ['data_preparer_identifier']=>string() // + ['offset']=>integer() // + ['publisher_identifier']=>string() // + ['raw']=>array() { // + ['abstract_file_identifier']=>string() // + ['application_data']=>string() // + ['application_identifier']=>string() // + ['bibliographic_file_identifier']=>string() // + ['copyright_file_identifier']=>string() // + ['data_preparer_identifier']=>string() // + ['file_structure_version']=>integer() // + ['logical_block_size']=>integer() // + ['path_table_l_location']=>integer() // + ['path_table_l_opt_location']=>integer() // + ['path_table_m_location']=>integer() // + ['path_table_m_opt_location']=>integer() // + ['path_table_size']=>integer() // + ['publisher_identifier']=>string() // + ['root_directory_record']=>string() // + ['standard_identifier']=>string() // + ['system_identifier']=>string() // + ['unused_1']=>string() // + ['unused_2']=>string() // + ['unused_3']=>string() // + ['unused_4']=>integer() // + ['volume_creation_date_time']=>string() // + ['volume_descriptor_type']=>integer() // + ['volume_descriptor_version']=>integer() // + ['volume_effective_date_time']=>string() // + ['volume_expiration_date_time']=>string() // + ['volume_identifier']=>string() // + ['volume_modification_date_time']=>string() // + ['volume_sequence_number']=>integer() // + ['volume_set_identifier']=>string() // + ['volume_set_size']=>integer() // + ['volume_space_size']=>integer() // + } // + ['system_identifier']=>string() // + ['volume_creation_date_time']=>integer() // + ['volume_effective_date_time']=>boolean() // + ['volume_expiration_date_time']=>boolean() // + ['volume_identifier']=>string() // + ['volume_modification_date_time']=>integer() // + ['volume_set_identifier']=>string() // + } // + } // + + + ['jpg']=>array() { // JPEG - still image + ['exif']=>array() // data returned from PHP's exif_read_data() function + } // + + + ['la']=>array() { // LA - Lossless Audio (www.lossless-audio.com) + ['raw']=>array() { + ['format']=>integer() // + ['flags']=>integer() // + } // + ['flags']=>array() { // + ['seekable']=>boolean() // + ['high_compression']=>boolean() // + } // + ['bits_per_sample']=>integer() // + ['bytes_per_sample']=>integer() // + ['bytes_per_second']=>integer() // + ['channels']=>integer() // + ['compression_ratio']=>double() // + ['format_size']=>integer() // + ['header_size']=>integer() // + ['original_crc']=>double() // + ['sample_rate']=>integer() // + ['samples']=>integer() // + ['uncompressed_size']=>integer() // + ['version']=>double() // + ['version_major']=>integer() // + ['version_minor']=>integer() // + ['footerstart']=>double() // + } + + + ['lpac']=>array() { // LPAC - Lossless Predictive Audio Compressor + ['block_length']=>integer() // + ['file_version']=>integer() // + ['flags']=>array() { // + ['16_bit']=>boolean() // + ['24_bit']=>boolean() // + ['adaptive_prediction_order']=>boolean() // + ['adaptive_quantization']=>boolean() // + ['fast_compress']=>boolean() // + ['is_wave']=>boolean() // + ['joint_stereo']=>boolean() // + ['max_prediction_order']=>integer() // + ['quantization']=>integer() // + ['random_access']=>boolean() // + ['stereo']=>boolean() // + } // + ['raw']=>array() { // + ['audio_type']=>integer() // + ['parameters']=>double() // + } // + ['total_samples']=>integer() // + } // + + + ['lyrics3']=>array() { // Lyrics3 - metainformation tags + ['comments']=>array() { // + ['album']=>string() // + ['artist']=>string() // + ['author']=>string() // + ['comment']=>string() // + ['title']=>string() // + } // + ['flags']=>array() { // + ['lyrics']=>boolean() // + ['timestamps']=>boolean() // + } // + ['images']=>array() { // + [<x>]=>array() { // + ['description']=>string() // + ['filename']=>string() // + ['timestamp']=>integer() // + } // + } // + ['raw']=>array() { // + ['offset_start']=>integer() // + ['offset_end']=>integer() // + ['AUT']=>string() // + ['EAL']=>string() // + ['EAR']=>string() // + ['ETT']=>string() // + ['IMG']=>string() // + ['IND']=>string() // + ['INF']=>string() // + ['LYR']=>string() // + ['lyrics3tagsize']=>integer() // + ['lyrics3version']=>integer() // + ['unparsed']=>string() // + } // + ['synchedlyrics']=>array() { // + [<x>]=>string() // + } // + ['unsynchedlyrics']=>string() // + } // + + + ['midi']=>array() { // MIDI (Musical Instrument Digital Interface) - sequenced music + ['comments']=>array() { // + ['comment']=>string() // + ['copyright']=>string() // + } // + ['keysignature']=>array() { // + [<x>]=>string() // + } // + ['raw']=>array() { // + ['events']=>array() { // + [<x>]=>array() { // + [<x>]=>array() { // + ['us_qnote']=>integer() // + } // + } // + } // + ['fileformat']=>integer() // + ['headersize']=>integer() // + ['ticksperqnote']=>integer() // + ['track']=>array() { // + [<x>]=>array() { // + ['instrument']=>string() // + ['instrumentid']=>integer() // + ['name']=>string() // + } // + } // + ['tracks']=>integer() // + } // + ['timesignature']=>array() { // + [<x>]=>string() // + } // + ['totalticks']=>integer() // + } // + + + ['monkeys_audio']=>array() { // Monkey's Audio - lossless audio compression + ['bitrate']=>double() // + ['bits_per_sample']=>integer() // + ['channels']=>integer() // + ['compressed_size']=>integer() // + ['compression']=>string() // + ['compression_ratio']=>double() // + ['flags']=>array() { // + ['24-bit']=>boolean() // + ['8-bit']=>boolean() // + ['crc-32']=>boolean() // + ['no_wav_header']=>boolean() // + ['peak_level']=>boolean() // + ['seek_elements']=>boolean() // + } // + ['frames']=>integer() // + ['peak_level']=>integer() // + ['peak_ratio']=>double() // + ['playtime']=>double() // + ['raw']=>array() { // + ['header_tag']=>string() // + ['nChannels']=>integer() // + ['nCompressionLevel']=>integer() // + ['nFinalFrameSamples']=>integer() // + ['nFormatFlags']=>integer() // + ['nPeakLevel']=>integer() // + ['nSampleRate']=>integer() // + ['nSeekElements']=>integer() // + ['nTotalFrames']=>integer() // + ['nVersion']=>integer() // + ['nWAVHeaderBytes']=>integer() // + ['nWAVTerminatingBytes']=>integer() // + } // + ['sample_rate']=>integer() // + ['samples']=>integer() // + ['samples_per_frame']=>integer() // + ['uncompressed_size']=>integer() // + ['version']=>double() // + } // + + + ['mpc']=>array() { // MPC (Musepack) - lossy audio compression + ['header']=>array() { // + ['album_gain_db']=>integer() // + ['album_peak']=>integer() // + ['album_peak_db']=>boolean() // + ['title_gain_db']=>integer() // + ['title_peak']=>integer() // + ['title_peak_db']=>boolean() // + ['begin_loud']=>boolean() // + ['end_loud']=>boolean() // + ['encoder_version']=>string() // + ['frame_count']=>integer() // + ['intensity_stereo']=>boolean() // + ['last_frame_length']=>integer() // + ['max_level']=>integer() // + ['max_subband']=>integer() // + ['mid_side_stereo']=>boolean() // + ['profile']=>string() // + ['sample_rate']=>integer() // + ['samples']=>integer() // + ['size']=>integer() // + ['stream_major_version']=>integer() // + ['stream_minor_version']=>integer() // + ['true_gapless']=>boolean() // + ['raw']=>array() { // + ['album_gain']=>integer() // + ['album_peak']=>integer() // + ['encoder_version']=>integer() // + ['preamble']=>string() // + ['profile']=>integer() // + ['sample_rate']=>integer() // + ['title_gain']=>integer() // + ['title_peak']=>integer() // + } // + } // + } // + + + ['mpeg']=>array() { // MPEG (Motion Picture Experts Group) - MPEG video and/or MPEG audio (MP3/MP2/MP1) + ['audio']=>array() { // + ['LAME']=>array() { // + ['RGAD']=>array() { // + ['peak_amplitude']=>double() // + } // + ['ath_type']=>integer() // + ['audio_bytes']=>integer() // + ['bitrate_min']=>integer() // + ['encoder_delay']=>integer() // + ['encoding_flags']=>array() { // + ['nogap_next']=>boolean() // + ['nogap_prev']=>boolean() // + ['nspsytune']=>boolean() // + ['nssafejoint']=>boolean() // + } // + ['end_padding']=>integer() // + ['lame_tag_crc']=>integer() // + ['lowpass_frequency']=>integer() // + ['mp3_gain_db']=>double() // + ['mp3_gain_factor']=>double() // + ['mp3_gain_raw']=>integer() // + ['music_crc']=>integer() // + ['noise_shaping']=>integer() // + ['noise_shaping_raw']=>integer() // + ['not_optimal_quality']=>boolean() // + ['not_optimal_quality_raw']=>integer() // + ['preset_used_id']=>integer() // + ['short_version']=>string() // ex: "LAME 3.93" + ['long_version']=>string() // (pre-v3.90 only) ex: "LAME 3.88 (alpha)" + ['source_sample_freq']=>string() // + ['source_sample_freq_raw']=>integer() // + ['stereo_mode']=>string() // + ['stereo_mode_raw']=>integer() // + ['surround_info']=>string() // + ['surround_info_id']=>integer() // + ['tag_revision']=>integer() // + ['vbr_method']=>string() // + ['vbr_method_raw']=>integer() // + } // + ['VBR_bitrate']=>double() // + ['VBR_bytes']=>integer() // + ['VBR_frames']=>integer() // + ['VBR_method']=>string() // + ['VBR_scale']=>integer() // + ['bitrate']=>integer() // + ['bitrate_distribution']=>array() { // + ['free']=>integer() // + ['8']=>integer() // + ['16']=>integer() // + ['24']=>integer() // + ['32']=>integer() // + ['40']=>integer() // + ['48']=>integer() // + ['56']=>integer() // + ['64']=>integer() // + ['80']=>integer() // + ['96']=>integer() // + ['112']=>integer() // + ['128']=>integer() // + ['144']=>integer() // + ['160']=>integer() // + } // + ['bitrate_mode']=>string() // + ['channelmode']=>string() // + ['channels']=>integer() // + ['copyright']=>boolean() // + ['crc']=>integer() // + ['emphasis']=>string() // + ['frame_count']=>integer() // + ['framelength']=>integer() // + ['layer']=>integer() // + ['modeextension']=>string() // + ['original']=>boolean() // + ['padding']=>boolean() // + ['private']=>boolean() // + ['protection']=>boolean() // + ['raw']=>array() { // + ['bitrate']=>integer() // + ['channelmode']=>integer() // + ['copyright']=>integer() // + ['emphasis']=>integer() // + ['layer']=>integer() // + ['modeextension']=>integer() // + ['original']=>integer() // + ['padding']=>integer() // + ['private']=>integer() // + ['protection']=>integer() // + ['sample_rate']=>integer() // + ['synch']=>integer() // + ['version']=>integer() // + } // + ['sample_rate']=>integer() // + ['stereo_distribution']=>array() { // + ['dual channel']=>integer() // + ['joint stereo']=>integer() // + ['mono']=>integer() // + ['stereo']=>integer() // + } // + ['toc']=>array() { // + [<x>]=>integer() // + } // + ['version']=>string() // + ['version_distribution']=>array() { // + [<x>]=>integer() // + [<x>]=>integer() // + ['2.5']=>integer() // + } // + ['xing_flags']=>array() { // + ['bytes']=>boolean() // + ['frames']=>boolean() // + ['toc']=>boolean() // + ['vbr_scale']=>boolean() // + } // + ['xing_flags_raw']=>string() // + } // + ['video']=>array() { // + ['bitrate']=>integer() // + ['bitrate_mode']=>string() // + ['frame_rate']=>double() // + ['framesize_horizontal']=>integer() // + ['framesize_vertical']=>integer() // + ['pixel_aspect_ratio']=>double() // + ['pixel_aspect_ratio_text']=>string() // + ['raw']=>array() { // + ['bitrate']=>integer() // + ['constrained_param_flag']=>integer() // + ['frame_rate']=>integer() // + ['framesize_horizontal']=>integer() // + ['framesize_vertical']=>integer() // + ['intra_quant_flag']=>integer() // + ['marker_bit']=>integer() // + ['pixel_aspect_ratio']=>integer() // + ['vbv_buffer_size']=>integer() // + } // + } // + } // + + + ['nsv']=>array() { // NSV - Nullsoft Streaming Video + ['NSVf']=>array() { // + ['TOC_entries_1']=>integer() // + ['TOC_entries_2']=>integer() // + ['file_size']=>integer() // + ['header_length']=>integer() // + ['identifier']=>string() // + ['meta_size']=>integer() // + ['metadata']=>string() // + ['playtime_ms']=>integer() // + } // + ['NSVs']=>array() { // + ['audio_codec']=>string() // + ['frame_rate']=>double() // + ['framerate_index']=>integer() // + ['identifier']=>string() // + ['offset']=>integer() // + ['resolution_x']=>integer() // + ['resolution_y']=>integer() // + ['unknown1b']=>integer() // + ['unknown1c']=>integer() // + ['unknown1d']=>integer() // + ['unknown2a']=>integer() // + ['unknown2b']=>integer() // + ['unknown2c']=>integer() // + ['unknown2d']=>integer() // + ['unknown3a']=>integer() // + ['unknown3b']=>integer() // + ['unknown3c']=>integer() // + ['unknown3d']=>integer() // + ['video_codec']=>string() // + } // + ['comments']=>array() { // + ['aspect']=>string() // + ['title']=>string() // + } // + } // + + + ['ofr']=>array() { // OFR (OptimFROG) - lossless audio compression + ['COMP']=>array() { // + [<x>]=>array() { // + ['channel_configuration']=>string() // + ['crc_32']=>boolean() // + ['encoder']=>string() // + ['offset']=>integer() // + ['raw']=>array() { // + ['algorithm_id']=>integer() // + ['channel_configuration']=>integer() // + ['encoder_id']=>integer() // + ['sample_type']=>integer() // + } // + ['sample_count']=>integer() // + ['sample_type']=>string() // + ['size']=>integer() // + } // + } // + ['HEAD']=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + ['OFR ']=>array() { // + ['channel_config']=>integer() // + ['channels']=>integer() // + ['compression']=>string() // + ['encoder']=>string() // + ['offset']=>integer() // + ['raw']=>array() { // + ['compression']=>integer() // + ['encoder_id']=>integer() // + ['sample_type']=>integer() // + } // + ['sample_rate']=>integer() // + ['sample_type']=>string() // + ['size']=>integer() // + ['total_samples']=>integer() // + } // + ['TAIL']=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + + + ['ogg']=>array() { // OGG - container format for Ogg Vorbis, OggFLAC, Speex, etc + ['bitrate_average']=>double() // + ['bitrate_max']=>integer() // + ['bitrate_min']=>integer() // + ['bitrate_nominal']=>integer() // + ['bitstreamversion']=>integer() // + ['blocksize_large']=>integer() // + ['blocksize_small']=>integer() // + ['comments']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + [<key name>]=>array() // <key name> can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } // + ['comments_raw']=>array() { // + [<x>]=>array() { // + ['dataoffset']=>integer() // + ['key']=>string() // + ['size']=>integer() // + ['value']=>string() // + } // + } // + ['numberofchannels']=>integer() // + ['pageheader']=>array() { // + [<x>]=>array() { // + ['flags']=>array() { // + ['bos']=>boolean() // + ['eos']=>boolean() // + ['fresh']=>boolean() // + } // + ['flags_raw']=>integer() // + ['header_end_offset']=>integer() // + ['packet_type']=>integer() // + ['page_checksum']=>double() // + ['page_end_offset']=>integer() // + ['page_length']=>integer() // + ['page_segments']=>integer() // + ['page_seqno']=>integer() // + ['page_start_offset']=>integer() // + ['pcm_abs_position']=>integer() // + ['segment_table']=>array() { // + [<x>]=>integer() // + } // + ['stream_serialno']=>integer() // + ['stream_structver']=>integer() // + ['stream_type']=>string() // + } // + ['eos']=>array() { // + ['flags']=>array() { // + ['bos']=>boolean() // + ['eos']=>boolean() // + ['fresh']=>boolean() // + } // + ['flags_raw']=>integer() // + ['header_end_offset']=>integer() // + ['page_checksum']=>double() // + ['page_end_offset']=>integer() // + ['page_length']=>integer() // + ['page_segments']=>integer() // + ['page_seqno']=>integer() // + ['page_start_offset']=>integer() // + ['pcm_abs_position']=>integer() // + ['segment_table']=>array() { // + [<x>]=>integer() // + } // + ['stream_serialno']=>integer() // + ['stream_structver']=>integer() // + } // + } // + ['samplerate']=>integer() // + ['samples']=>integer() // + ['stop_bit']=>integer() // + ['vendor']=>string() // + } // + + + ['png']=>array() { // PNG (Portable Network Graphics) - still image + ['IDAT']=>array() { // + [<x>]=>array() { // + ['header']=>array() { // + ['crc']=>integer() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + } // + } // + ['IEND']=>array() { // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + } // + ['IHDR']=>array() { // + ['color_type']=>array() { // + ['alpha']=>boolean() // + ['palette']=>boolean() // + ['true_color']=>boolean() // + } // + ['compression_method_text']=>string() // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['height']=>integer() // + ['raw']=>array() { // + ['bit_depth']=>integer() // + ['color_type']=>integer() // + ['compression_method']=>integer() // + ['filter_method']=>integer() // + ['interlace_method']=>integer() // + } // + ['width']=>integer() // + } // + ['PLTE']=>array() { // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + [<x>]=>integer() // + } // + ['comments']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + [<key name>]=>array() // <key name> can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } // + ['gAMA']=>array() { // + ['gamma']=>double() // + ['header']=>array() { // + ['crc']=>integer() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + } // + ['oFFs']=>array() { // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['position_x']=>integer() // + ['position_y']=>integer() // + ['unit']=>string() // + ['unit_specifier']=>integer() // + } // + ['pHYs']=>array() { // + ['header']=>array() { // + ['crc']=>integer() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['pixels_per_unit_x']=>integer() // + ['pixels_per_unit_y']=>integer() // + ['unit']=>string() // + ['unit_specifier']=>integer() // + } // + ['pcLb']=>array() { // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + } // + ['tEXt']=>array() { // + ['header']=>array() { // + ['crc']=>integer() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['keyword']=>string() // + ['text']=>string() // + } // + ['tIME']=>array() { // + ['day']=>integer() // + ['header']=>array() { // + ['crc']=>integer() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['hour']=>integer() // + ['minute']=>integer() // + ['month']=>integer() // + ['second']=>integer() // + ['unix']=>integer() // + ['year']=>integer() // + } // + ['tRNS']=>array() { // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['transparent_color_blue']=>integer() // + ['transparent_color_green']=>integer() // + ['transparent_color_red']=>integer() // + } // + ['zTXt']=>array() { // + ['compressed_text']=>string() // + ['compression_method']=>integer() // + ['compression_method_text']=>string() // + ['header']=>array() { // + ['crc']=>double() // + ['data']=>string() // + ['data_length']=>integer() // + ['flags']=>array() { // + ['ancilliary']=>boolean() // + ['private']=>boolean() // + ['reserved']=>boolean() // + ['safe_to_copy']=>boolean() // + } // + ['type_raw']=>double() // + ['type_text']=>string() // + } // + ['keyword']=>string() // + ['text']=>string() // + } // + } // + + + ['quicktime']=>array() { // Quicktime - video/audio + ['']=>array() { // + ['name']=>boolean() // + ['offset']=>integer() // + ['size']=>integer() // + } // + ['audio']=>array() { // + ['bit_depth']=>integer() // + ['channels']=>integer() // + ['codec']=>string() // + ['sample_rate']=>double() // + } // + ['free']=>array() { // + ['name']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + ['mdat']=>array() { // + ['name']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + ['moov']=>array() { // + ['hierarchy']=>string() // + ['name']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + ['subatoms']=>array() // This is an undocumentably-complex recursive array, typically containing a huge amount of seemingly disorganized data. Avoid this like the plague. + } // + ['time_scale']=>integer() // + ['display_scale']=>integer() // 1 = normal; 0.5 = half; 2 = double + ['video']=>array() { // + ['codec']=>string() // + ['color_depth']=>integer() // + ['color_depth_name']=>string() // + ['resolution_x']=>double() // + ['resolution_y']=>double() // + } // + ['wide']=>array() { // + ['name']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + + + ['real']=>array() { // Real (RealAudio / RealVideo) - audio/video + ['chunks']=>array() { // + [<x>]=>array() { // + ['file_version']=>integer() // + ['headers_count']=>integer() // + ['length']=>integer() // + ['name']=>string() // + ['object_version']=>integer() // + ['offset']=>integer() // + } // + [<x>]=>array() { // + ['avg_bit_rate']=>integer() // + ['avg_packet_size']=>integer() // + ['data_offset']=>integer() // + ['duration']=>integer() // + ['flags']=>array() { // + ['live_broadcast']=>boolean() // + ['perfect_play']=>boolean() // + ['save_enabled']=>boolean() // + } // + ['flags_raw']=>integer() // + ['index_offset']=>integer() // + ['length']=>integer() // + ['max_bit_rate']=>integer() // + ['max_packet_size']=>integer() // + ['name']=>string() // + ['num_packets']=>integer() // + ['num_streams']=>integer() // + ['object_version']=>integer() // + ['offset']=>integer() // + ['preroll']=>integer() // + } // + } // + ['comments']=>array() { // + ['artist']=>string() // + ['comment']=>string() // + ['title']=>string() // + } // + } // + + + ['riff']=>array() { // RIFF (Resource Interchange File Format) - audio/video container format (AVI, WAV, CDDA, etc) + ['AIFC']=>array() { // + ['COMM']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['FVER']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['INST']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['MARK']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['SSND']=>array() { // + [<x>]=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + } // + ['AIFF']=>array() { // + ['(c) ']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['COMM']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['SSND']=>array() { // + [<x>]=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + } // + ['AVI ']=>array() { // + ['JUNK']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['hdrl']=>array() { // + ['avih']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['odml']=>array() { // + ['dmlh']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + } // + ['strl']=>array() { // + ['JUNK']=>array() { // + [<x>]=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['strf']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['strh']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['strn']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + } // + } // + ['idx1']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['movi']=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['CDDA']=>array() { // + ['fmt ']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['disc_id']=>integer() // + ['offset']=>integer() // + ['playtime_frames']=>integer() // + ['playtime_seconds']=>double() // + ['size']=>integer() // + ['start_offset_frame']=>integer() // + ['start_offset_seconds']=>double() // + ['track_num']=>integer() // + ['unknown1']=>integer() // + ['unknown6']=>integer() // + ['unknown7']=>integer() // + } // + } // + } // + ['WAVE']=>array() { // + ['DISP']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['INFO']=>array() { // + ['IART']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ICMT']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ICOP']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['IENG']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['IGNR']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['IKEY']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['IMED']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['INAM']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ISBJ']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ISFT']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ISRC']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ISRF']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['ITCH']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + } // + ['MEXT']=>array() { // + [<x>]=>array() { // + ['anciliary_data_length']=>integer() // + ['data']=>string() // + ['flags']=>array() { // + ['anciliary_data_free']=>boolean() // + ['anciliary_data_left']=>boolean() // + ['anciliary_data_right']=>boolean() // + ['homogenous']=>boolean() // + } // + ['offset']=>integer() // + ['raw']=>array() { // + ['anciliary_data_def']=>integer() // + ['sound_information']=>integer() // + } // + ['size']=>integer() // + } // + } // + ['bext']=>array() { // + [<x>]=>array() { // + ['author']=>string() // + ['bwf_version']=>integer() // + ['coding_history']=>array() { // + [<x>]=>string() // + } // + ['data']=>string() // + ['offset']=>integer() // + ['origin_date']=>string() // + ['origin_date_unix']=>integer() // + ['origin_time']=>string() // + ['reference']=>string() // + ['reserved']=>integer() // + ['size']=>integer() // + ['time_reference']=>integer() // + ['title']=>string() // + } // + } // + ['cart']=>array() { // + [<x>]=>array() { // + ['artist']=>string() // + ['category']=>string() // + ['classification']=>string() // + ['client_id']=>string() // + ['cut_id']=>string() // + ['data']=>string() // + ['end_date']=>string() // + ['end_time']=>string() // + ['offset']=>integer() // + ['out_cue']=>string() // + ['post_time']=>array() { // + [<x>]=>array() { // + ['timer_value']=>integer() // + ['usage_fourcc']=>string() // + } // + } // + ['producer_app_id']=>string() // + ['producer_app_version']=>string() // + ['size']=>integer() // + ['start_date']=>string() // + ['start_time']=>string() // + ['tag_text']=>array() { // + [<x>]=>string() // + } // + ['title']=>string() // + ['url']=>string() // + ['user_defined_text']=>string() // + ['version']=>string() // + ['zero_db_reference']=>integer() // + } // + } // + ['data']=>array() { // + [<x>]=>array() { // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['fact']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['fmt ']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + ['rgad']=>array() { // + [<x>]=>array() { // + ['data']=>string() // + ['offset']=>integer() // + ['size']=>integer() // + } // + } // + } // + ['audio']=>array() { // + [<x>]=>array() { // + ['bitrate']=>integer() // + ['bits_per_sample']=>integer() // + ['channels']=>integer() // + ['codec']=>string() // + ['sample_rate']=>integer() // + } // + ['bits_per_sample']=>integer() // + ['channels']=>integer() // + ['codec_fourcc']=>string() // + ['codec_name']=>string() // + ['sample_rate']=>integer() // + ['total_samples']=>integer() // + } // + ['comments']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + [<key name>]=>array() // <key name> can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } // + ['header_size']=>integer() // + ['raw']=>array() { // + ['avih']=>array() { // + ['dwFlags']=>integer() // + ['dwHeight']=>integer() // + ['dwInitialFrames']=>integer() // + ['dwLength']=>integer() // + ['dwMaxBytesPerSec']=>integer() // + ['dwMicroSecPerFrame']=>integer() // + ['dwPaddingGranularity']=>integer() // + ['dwRate']=>integer() // + ['dwScale']=>integer() // + ['dwStart']=>integer() // + ['dwStreams']=>integer() // + ['dwSuggestedBufferSize']=>integer() // + ['dwTotalFrames']=>integer() // + ['dwWidth']=>integer() // + ['flags']=>array() { // + ['capturedfile']=>boolean() // + ['copyrighted']=>boolean() // + ['hasindex']=>boolean() // + ['interleaved']=>boolean() // + ['mustuseindex']=>boolean() // + ['trustcktype']=>boolean() // + } // + } // + ['fact']=>array() { // + ['NumberOfSamples']=>integer() // + } // + ['fmt ']=>array() { // + ['nAvgBytesPerSec']=>integer() // + ['wBitsPerSample']=>integer() // + ['nBlockAlign']=>integer() // + ['nChannels']=>integer() // + ['nSamplesPerSec']=>integer() // + ['wFormatTag']=>integer() // + } // + ['rgad']=>array() { // + ['audiophile']=>array() { // + ['adjustment']=>integer() // + ['name']=>integer() // + ['originator']=>integer() // + ['signbit']=>integer() // + } // + ['fPeakAmplitude']=>double() // + ['nAudiophileRgAdjust']=>integer() // + ['nRadioRgAdjust']=>integer() // + ['radio']=>array() { // + ['adjustment']=>integer() // + ['name']=>integer() // + ['originator']=>integer() // + ['signbit']=>integer() // + } // + } // + ['strf']=>array() { // + ['auds']=>array() { // + [<x>]=>array() { // + ['nAvgBytesPerSec']=>integer() // + ['wBitsPerSample']=>integer() // + ['nBlockAlign']=>integer() // + ['nChannels']=>integer() // + ['nSamplesPerSec']=>integer() // + ['wFormatTag']=>integer() // + } // + } // + ['vids']=>array() { // + [<x>]=>array() { // + ['biBitCount']=>integer() // + ['biClrImportant']=>integer() // + ['biClrUsed']=>integer() // + ['biHeight']=>integer() // + ['biPlanes']=>integer() // + ['biSize']=>integer() // + ['biSizeImage']=>integer() // + ['biWidth']=>integer() // + ['biXPelsPerMeter']=>integer() // + ['biYPelsPerMeter']=>integer() // + ['fourcc']=>string() // + } // + } // + } // + ['strh']=>array() { // + [<x>]=>array() { // + ['dwFlags']=>integer() // + ['dwInitialFrames']=>integer() // + ['dwLength']=>integer() // + ['dwQuality']=>integer() // + ['dwRate']=>integer() // + ['dwSampleSize']=>integer() // + ['dwScale']=>integer() // + ['dwStart']=>integer() // + ['dwSuggestedBufferSize']=>integer() // + ['fccHandler']=>string() // + ['fccType']=>string() // + ['rcFrame']=>integer() // + ['wLanguage']=>integer() // + ['wPriority']=>integer() // + } // + } // + } // + ['rgad']=>array() { // + ['audiophile']=>array() { // + ['adjustment']=>double() // + ['name']=>string() // + ['originator']=>string() // + } // + ['peakamplitude']=>double() // + ['radio']=>array() { // + ['adjustment']=>double() // + ['name']=>string() // + ['originator']=>string() // + } // + } // + ['video']=>array() { // + [<x>]=>array() { // + ['codec']=>string() // + ['frame_height']=>integer() // + ['frame_rate']=>double() // + ['frame_width']=>integer() // + } // + } // + ['litewave']=>array() { // http://www.clearjump.com + ['raw']=>array() { // + ['compression_method']=>integer() // 1=lossy; 2=lossless + ['compression_flags']=>integer() // + ['m_dwScale']=>integer() // scalefactor for lossy compression - related to m_wQuality as: $m_wQuality = round((2000 - $m_dwScale) / 20) + ['m_dwBlockSize']=>integer() // number of samples in encoded blocks + ['m_wQuality']=>integer() // quality factor (0=most compressed lossy; 99=best quality lossy; 100=lossless) + ['m_wMarkDistance']=>integer() // distance between marks in bytes + ['m_wReserved']=>integer() // + ['m_dwOrgSize']=>integer() // original file size in bytes + ['m_bFactExists']=>integer() // indicates if 'fact' chunk exists in the original file + ['m_dwRiffChunkSize']=>integer() // riff chunk size in the original file + } // + ['quality_factor']=>integer() // alias of ['raw']['m_wQuality'] + } // + } // + + + ['shn']=>array() { // Shorten - lossless audio compression + ['seektable']=>array() { // + ['length']=>integer() // + ['offset']=>integer() // + ['present']=>boolean() // + } // + ['version']=>integer() // + } // + + + ['swf']=>array() { // SWF - ShockWave Flash (www.openswf.org) + ['header']=>array() { // + ['frame_count']=>integer() // + ['frame_height']=>integer() // + ['frame_width']=>integer() // + ['length']=>integer() // + ['signature']=>string() // + ['version']=>integer() // + } // + ['bgcolor']=>string() // + ['tags']=>array() // + } // + + + ['voc']=>array() { // VOC - SoundBlaster VOC audio format + ['blocks']=>array() { // + [<x>]=>array() { // + ['bits_per_sample']=>integer() // + ['block_offset']=>integer() // + ['block_size']=>integer() // + ['block_type_id']=>integer() // + ['channels']=>integer() // + ['compression_name']=>string() // + ['compression_type']=>integer() // + ['pack_method']=>integer() // + ['sample_rate']=>integer() // + ['sample_rate_id']=>integer() // + ['stereo']=>boolean() // + ['time_constant']=>integer() // + ['wFormat']=>integer() // + } // + } // + ['compressed_bits_per_sample']=>integer() // + ['header']=>array() { // + ['datablock_offset']=>integer() // + ['major_version']=>integer() // + ['minor_version']=>integer() // + } // + } // + + + ['vqf']=>array() { // VQF - transform-domain weighted interleave Vector Quantization Format (lossy audio) + ['COMM']=>array() { // + ['bitrate']=>integer() // + ['channel_mode']=>integer() // + ['sample_rate']=>integer() // + ['security_level']=>integer() // + } // + ['DSIZ']=>integer() // + ['comments']=>array() { // array of array of strings containing best data from any available metainformation tag (APE, ID3v2, ID3v1, Lyrics3, Vorbis, ASF, RIFF, Real, etc.) + [<key name>]=>array() // <key name> can be anything, usually 'artist', 'title', etc. Contains array of one or more values (eg: multiple artists are possible) + } // + ['raw']=>array() { // + ['header_tag']=>string() // + ['size']=>integer() // + ['version']=>string() // + } // + } // + + + ['wavpack']=>array() { // WavPack - lossless audio compression + ['bits']=>integer() // + ['crc1']=>double() // + ['crc2']=>integer() // + ['extension']=>string() // + ['extra_bc']=>string() // + ['extras']=>string() // + ['flags_raw']=>integer() // + ['offset']=>integer() // + ['shift']=>integer() // + ['size']=>integer() // + ['total_samples']=>integer() // + ['version']=>integer() // + } // + + + ['zip']=>array() { // ZIP - lossless data compression + ['central_directory']=>array() { // + [<x>]=>array() { // + ['compressed_size']=>integer() // + ['compression_method']=>string() // + ['create_version']=>string() // + ['entry_offset']=>integer() // + ['extract_version']=>string() // + ['filename']=>string() // + ['flags']=>array() { // + ['compression_speed']=>string() // + ['data_descriptor_used']=>boolean() // + ['encrypted']=>boolean() // + } // + ['host_os']=>string() // + ['last_modified_timestamp']=>integer() // + ['offset']=>integer() // + ['raw']=>array() { // + ['compressed_size']=>integer() // + ['compression_method']=>integer() // + ['crc_32']=>double() // + ['create_version']=>integer() // + ['disk_number_start']=>integer() // + ['external_file_attrib']=>double() // + ['extra_field_length']=>integer() // + ['extract_version']=>integer() // + ['file_comment_length']=>integer() // + ['filename_length']=>integer() // + ['general_flags']=>integer() // + ['internal_file_attrib']=>integer() // + ['last_mod_file_date']=>integer() // + ['last_mod_file_time']=>integer() // + ['local_header_offset']=>integer() // + ['signature']=>integer() // + ['uncompressed_size']=>integer() // + } // + ['uncompressed_size']=>integer() // + } // + } // + ['comments']=>array() { // + ['comment']=>string() // + } // + ['compressed_size']=>integer() // + ['compression_method']=>string() // + ['compression_speed']=>string() // + ['end_central_directory']=>array() { // + ['comment']=>string() // + ['comment_length']=>integer() // + ['directory_entries_this_disk']=>integer() // + ['directory_entries_total']=>integer() // + ['directory_offset']=>integer() // + ['directory_size']=>integer() // + ['disk_number_current']=>integer() // + ['disk_number_start_directory']=>integer() // + ['offset']=>integer() // + ['signature']=>integer() // + } // + ['entries']=>array() { // + [<x>]=>array() { // + ['compressed_size']=>integer() // + ['compression_method']=>string() // + ['extract_version']=>string() // + ['filename']=>string() // + ['flags']=>array() { // + ['compression_speed']=>string() // + ['data_descriptor_used']=>boolean() // + ['encrypted']=>boolean() // + } // + ['host_os']=>string() // + ['last_modified_timestamp']=>integer() // + ['offset']=>integer() // + ['raw']=>array() { // + ['compressed_size']=>integer() // + ['compression_method']=>integer() // + ['crc_32']=>integer() // + ['extra_field_length']=>integer() // + ['extract_version']=>integer() // + ['filename_length']=>integer() // + ['general_flags']=>integer() // + ['last_mod_file_date']=>integer() // + ['last_mod_file_time']=>integer() // + ['signature']=>integer() // + ['uncompressed_size']=>integer() // + } // + ['uncompressed_size']=>integer() // + } // + } // + ['entries_count']=>integer() // + ['files']=>array() { // multidimensional tree-structure array listing of all files and directories in image + [<directory name>]=>array() // entries of type array are directories (key is directory name), may contain files and/or other subdirectories + [<file name>]=>integer() // entries of type integer are files (key is file name, value is file size in bytes) + } // + ['uncompressed_size']=>integer() // + } // +} // diff --git a/modules/init.php b/modules/init.php new file mode 100644 index 00000000..537beb6e --- /dev/null +++ b/modules/init.php @@ -0,0 +1,300 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header INIT file + Take care of our init grunt work so we don't scatter paths + all over the place. + +*/ + +// Set the Error level manualy... I'm to lazy to fix notices +error_reporting(E_ALL ^ E_NOTICE); + +// This makes this file nolonger need customization +// the config file is in the same dir as this (init.php) file. +$ampache_path = dirname(__FILE__); +$prefix = realpath($ampache_path . "/../"); +$configfile = "$prefix/config/ampache.cfg.php"; +require_once($prefix . "/lib/general.php"); + + +/*********************STOP EDITING*********************************/ + +/* + Check to see if this is Http or https +*/ +if ($_SERVER['HTTPS'] == 'on') { + $http_type = "https://"; +} +else { + $http_type = "http://"; +} + +/* + See if the Config File Exists if it doesn't + then go ahead and move them over to the install + script +*/ +if (!file_exists($configfile)) { + $path = preg_replace("/(.*)\/(\w+\.php)$/","\${1}", $_SERVER['PHP_SELF']); + $link = $http_type . $_SERVER['HTTP_HOST'] . $path . "/install.php"; + header ("Location: $link"); + exit(); +} + +/* + Try to read the config file, if it fails give them + an explanation +*/ +if (!$results = read_config($configfile,0)) { + $path = preg_replace("/(.*)\/(\w+\.php)$/","\${1}", $_SERVER['PHP_SELF']); + $link = $http_type . $_SERVER['HTTP_HOST'] . $path . "/test.php"; + header ("Location: $link"); + exit(); +} + + + +// Cheat a little to setup the extra vars needed by libglue + +//FIXME: Untile we have a config updater force stream as allowed playback method +if (!$results['conf']['allow_stream_playback']) { + $results['conf']['allow_stream_playback'] = "true"; +} + +$results['conf']['web_path'] = $http_type . $_SERVER['HTTP_HOST'] . $results['conf']['web_path']; +$results['conf']['version'] = '3.3.1-Beta2'; +$results['conf']['catalog_file_pattern']= 'mp3|mpc|m4p|m4a|mp4|aac|ogg|rm|wma|asf|flac|spx'; +$results['libglue']['local_table'] = 'session'; +$results['libglue']['local_sid'] = 'id'; +$results['libglue']['local_expirecol'] = 'expire'; +$results['libglue']['local_usercol'] = 'username'; +$results['libglue']['local_typecol'] = 'type'; +$results['libglue']['local_datacol'] = 'value'; +$results['libglue']['mysql_table'] = 'user'; +$results['libglue']['mysql_usercol'] = 'username'; +$results['libglue']['mysql_passwdcol'] = 'password'; +$results['libglue']['local_dbh_name'] = 'local_dbh'; +$results['libglue']['auth_methods'] = 'mysql'; +$results['libglue']['mysql_fields'] = 'id,username,fullname,email,access,offset_limit'; +$results['libglue']['mysql_host'] = $results['libglue']['local_host']; +$results['libglue']['mysql_db'] = $results['libglue']['local_db']; +$results['libglue']['mysql_username'] = $results['libglue']['local_username']; +$results['libglue']['mysql_user'] = $results['libglue']['local_username']; +$results['libglue']['mysql_passwd'] = $results['libglue']['local_pass']; +$results['libglue']['mysql_pass'] = $results['libglue']['local_pass']; +$results['libglue']['mysql_passcol'] = 'password'; +$results['libglue']['dbh'] = $results['libglue']['local_dbh_name']; +$results['libglue']['auth_page'] = $results['conf']['web_path']; +$results['libglue']['login_page'] = $results['conf']['web_path'] . "/login.php"; +$results['conf']['http_port'] = $_SERVER['SERVER_PORT']; +if (!$results['conf']['prefix']) { + $results['conf']['prefix'] = $prefix; +} +if (!$results['libglue']['stop_auth']) { + $results['libglue']['stop_auth'] = $results['conf']['prefix'] . "/libglue/gone.fishing"; +} +if (!$results['libglue']['libglue_path']) { + $results['libglue']['libglue_path']= $results['conf']['prefix'] . "/libglue"; +} +if (!$results['conf']['http_port']) { + $results['conf']['http_port'] = '80'; +} +if (!$results['conf']['site_charset']) { + $results['conf']['site_charset'] = "iso-8859-1"; +} +if (!$results['conf']['log_path']) { + $results['conf']['log_path'] = '/tmp'; +} +if (!$results['conf']['ellipse_threshold_album']) { + $results['conf']['ellipse_threshold_album'] = 27; +} +if (!$results['conf']['ellipse_threshold_artist']) { + $results['conf']['ellipse_threshold_artist'] = 27; +} +if (!$results['conf']['ellipse_threshold_title']) { + $results['conf']['ellipse_threshold_title'] = 27; +} + + +/* Temp Fixes */ +$results['conf'] = fix_preferences($results['conf']); + + +// Setup Static Arrays +libglue_param($results['libglue']); +conf($results['conf']); + +// Libglue Requires +require_once(libglue_param('libglue_path') . "/auth.php"); +require_once(libglue_param('libglue_path') . "/session.php"); +require_once(libglue_param('libglue_path') . "/dbh.php"); + +// Librarys +require_once(conf('prefix') . "/lib/album.php"); +require_once(conf('prefix') . "/lib/artist.php"); +require_once(conf('prefix') . "/lib/song.php"); +require_once(conf('prefix') . "/lib/search.php"); +require_once(conf('prefix') . "/lib/preferences.php"); +require_once(conf('prefix') . "/lib/rss.php"); +require_once(conf('prefix') . "/lib/log.php"); +require_once(conf('prefix') . "/lib/ui.php"); +require_once(conf('prefix') . "/lib/gettext.php"); +require_once(conf('prefix') . "/lib/batch.php"); +require_once(conf('prefix') . "/lib/themes.php"); +require_once(conf('prefix') . "/modules/lib.php"); +require_once(conf('prefix') . "/modules/admin.php"); +require_once(conf('prefix') . "/modules/catalog.php"); + +// Modules (These are conditionaly included depending upon config values) +require_once(conf('prefix') . "/modules/id3/audioinfo.class.php"); +require_once(conf('prefix') . "/modules/amazon/Snoopy.class.php"); +require_once(conf('prefix') . "/modules/amazon/AmazonSearchEngine.class.php"); + +if (conf('xml_rpc')) { + require_once(conf('prefix') . "/modules/xmlrpc/xmlrpc.inc"); + require_once(conf('prefix') . "/lib/xmlrpc.php"); +} + +if (conf('allow_slim_playback')) { + require_once(conf('prefix') . "/modules/slimserver/slim.class.php"); +} + +if (conf('allow_mpd_playback')) { + require_once(conf('prefix') . "/modules/mpd/mpd.class.php"); + require_once(conf('prefix') . "/lib/mpd.php"); +} + +// Classes +require_once(conf('prefix') . "/modules/class/catalog.php"); +require_once(conf('prefix') . "/modules/class/stream.php"); +require_once(conf('prefix') . "/modules/class/playlist.php"); +require_once(conf('prefix') . "/modules/class/song.php"); +require_once(conf('prefix') . "/modules/class/view.php"); +require_once(conf('prefix') . "/modules/class/update.php"); +require_once(conf('prefix') . "/modules/class/user.php"); +require_once(conf('prefix') . "/modules/class/album.php"); +require_once(conf('prefix') . "/modules/class/artist.php"); +require_once(conf('prefix') . "/modules/class/access.php"); +require_once(conf('prefix') . "/modules/class/error.php"); + +/* Some Libglue Hacks */ +$array['dbh_name'] = 'stupid_pos'; +$array['stupid_pos'] = check_sess_db('local'); +libglue_param($array); +/* End Libglue Hacks */ + +/* Set a new Error Handler */ +$old_error_handler = set_error_handler("ampache_error_handler"); + + + +/* Check their PHP Vars to make sure we're cool here */ +if ($results['conf']['memory_limit'] < 16) { + $results['conf']['memory_limit'] = 16; +} +set_memory_limit($results['conf']['memory_limit']); + +if (ini_get('short_open_tag') != "On") { + ini_set (short_open_tag, "On"); +} + +// Check Session GC mojo, increase if need be +$gc_probability = @ini_get('session.gc_probability'); +$gc_divisor = @ini_get('session.gc_divisor'); + +if (!$gc_divisor) { + $gc_divisor = '100'; +} + +// Force GC on 1:5 page loads +if (($gc_divisor / $gc_probability) > 5) { + $new_gc_probability = $gc_divisor * .2; + ini_set('session.gc_probability',$new_gc_probability); +} +/* END Set PHP Vars */ + +/* Overwrite them with the DB preferences */ +set_site_preferences(); + +/* Seed the random number */ +srand((double) microtime() * 1000003); + +// If we don't want a session +if (!isset($no_session) AND conf('use_auth')) { + if (!check_session()) { logout(); exit(); } + get_preferences(); + set_theme(); + $user = new User($_SESSION['userdata']['username']); + $user->update_last_seen(); +} +if (!conf('use_auth')) { + $auth['success'] = 1; + $auth['info']['username'] = "Ampache"; + $auth['info']['fullname'] = "Ampache User"; + $auth['info']['id'] = 0; + $auth['info']['access'] = "admin"; + $auth['info']['offset_limit'] = 50; + if (!check_session()) { make_local_session_only($auth); } + $user = new User(0); + $user->fullname = $auth['info']['fullname']; + $user->id = $auth['info']['id']; + $user->offset_limit = $auth['info']['offset_limit']; + $user->username = $auth['info']['username']; + $user->access = $auth['info']['access']; + $_SESSION['userdata']['access'] = $auth['info']['access']; + $_SESSION['userdata']['username'] = $auth['info']['username']; + $_SESSION['userdata']['offset_limit'] = $auth['info']['offset_limit']; + $_SESSION['userdata']['id'] = $auth['info']['id']; + $user->set_preferences(); + get_preferences(); + set_theme(); +} + +// Load gettext mojo +load_gettext(); + +/* Set CHARSET */ +header ("Content-Type: text/html; charset=" . conf('site_charset')); + +/* Clean up a bit */ +unset($array); +unset($results); + +/* Setup the flip class */ +flip_class(array('odd','even')); + +/* Setup the Error Class */ +$error = new Error(); + +if (! preg_match('/update\.php/', $_SERVER['PHP_SELF'])) { + $update = new Update(); + if ($update->need_update()) { + header("Location: " . conf('web_path') . "/update.php"); + exit(); + } +} + + +unset($update); +?> diff --git a/modules/lib.php b/modules/lib.php new file mode 100644 index 00000000..735f579a --- /dev/null +++ b/modules/lib.php @@ -0,0 +1,1518 @@ +<?php +/* + + Copyright (c) 2004 ampache.org + All rights reserved. + + All of the main functions for Ampache. + FIXME: Remove this file... shouldn't be used anymore + +*/ + + +/* + * show_local_catalog_info() + * + */ +function show_local_catalog_info() { + global $settings, $username; + $dbh = dbh(); + + $query = "SELECT count(*) AS songs, SUM(size) AS size, SUM(time) as time FROM song"; + $db_result = mysql_query($query, $dbh); + $songs = mysql_fetch_array($db_result); + + $query = "SELECT count(*) FROM album"; + $db_result = mysql_query($query, $dbh); + $albums = mysql_fetch_row($db_result); + + $query = "SELECT count(*) FROM artist"; + $db_result = mysql_query($query, $dbh); + $artists = mysql_fetch_row($db_result); + + $sql = "SELECT count(*) FROM user"; + $db_result = mysql_query($sql, $dbh); + $users = mysql_fetch_row($db_result); + + $time = time(); + $last_seen_time = $time - 1200; + $sql = "SELECT count(DISTINCT s.username) FROM session AS s " . + "INNER JOIN user AS u ON s.username = u.id " . + "WHERE s.expire > " . $time . " " . + "AND u.last_seen > " . $last_seen_time; + $db_result = mysql_query($sql, $dbh); + $connected_users = mysql_fetch_row($db_result); + + $hours = floor($songs['time']/3600); + $size = $songs['size']/1048576; + + $days = floor($hours/24); + $hours = $hours%24; + + $time_text = "$days "; + $time_text .= ($days == 1) ? _("day") : _("days"); + $time_text .= ", $hours "; + $time_text .= ($hours == 1) ? _("hour") : _("hours"); + + if ( $size > 1024 ) { + $total_size = sprintf("%.2f", ($size/1024)); + $size_unit = "GB"; + } + else { + $total_size = sprintf("%.2f", $size); + $size_unit = "MB"; + } + + + print ' +<table class="border" cellspacing="1" cellpadding="3" width="100%" border="0"> + <tr class="table-header"> + <td colspan="2">' . _("Catalog Statistics") . '</td> + </tr> + <tr class="even"> + <td> ' . _("Total Users") . ' </td> + <td> <b>' . $users[0] .'</b> </td> + </tr> + <tr class="even"> + <td> ' . _("Connected Users") . ' </td> + <td> <b>' . $connected_users[0] .'</b> </td> + </tr> + <tr class="even"> + <td> ' . _("Albums") . ' </td> + <td> <b>' . $albums[0] .'</b> </td> + </tr> + <tr class="even"> + <td> ' . _("Artists") . ' </td> + <td> <b>' . $artists[0] .'</b> </td> + </tr> + <tr class="even"> + <td> ' . _("Songs") . ' </td> + <td> <b>' . $songs[0] .'</b> </td> + </tr> + <tr class="even"> + <td> ' . _("Catalog Size") . ' </td> + <td> <b>' . $total_size .' ' . $size_unit .'</b> </td> + </tr> + <tr class="even"> + <td> ' . _("Catalog Time") . ' </td> + <td> <b>' . $time_text .'</b> </td> + </tr> +</table> +'; + +} // show_local_catalog_info() + + +/* + * get_popular_songs() + * + */ + +function get_popular_songs( $threshold, $type, $user_id = '' ) { + + $dbh = dbh(); + + if ( $type == 'your' ) { + $sql = "SELECT object_id FROM object_count" . + " WHERE object_type = 'song'" . + " AND userid = '$user_id'" . + " ORDER BY count DESC LIMIT $threshold"; + } + else { + $sql = "SELECT object_id FROM object_count" . + " WHERE object_type = 'song'" . + " ORDER BY count DESC LIMIT $threshold"; + } + + $db_result = mysql_query($sql, $dbh); + $songs = array(); + + while ( $id = mysql_fetch_array($db_result) ) { + $songs[] = $id[0]; + } + + return $songs; +} // get_popular_songs() + + +/* + * show_random_play() + * + */ + +function show_random_play() { + $web_path = conf('web_path'); + + print ' + <form name="random" method="post" enctype="multipart/form-data" action="' . $web_path . '/song.php"> + <input type="hidden" name="action" value="m3u" /> + <table class="border" border="0" cellpadding="3" cellspacing="1" width="100%"> + <tr class="table-header"> + <td colspan="4">' . _("Play Random Selection") . '</td> + + </tr> + <tr class="even"> + <td> + <table border="0"> + <tr class="even"> + <td>' . _("Item count") .'</td> + <td> + <select name="random"> + <option value="1">1</option> + <option value="5">5</option> + <option value="10">10</option> + <option value="20">20</option> + <option value="30">30</option> + <option value="50">50</option> + <option value="100">100</option> + <option value="500">500</option> + <option value="1000">1000</option> + <option value="-1">' . _("All") . '</option> + </select></td> + <td rowspan="3" valign="top"> ' . _("From genre") . '</td> + <td rowspan="3"> +'; + + show_genre_pulldown( -1, 0 ); + + print ' + </td></tr> + <tr class="even"> + <td> + ' . _("Favor Unplayed") . ' <br /> + ' . _("Full Albums") . ' <br /> + ' . _("Full Artist") . ' + </td> + <td> + <input type="checkbox" id="unplayed" name="unplayed" value="1" onclick="flipField(\'album\');flipField(\'artist\')" /><br /> + <input type="checkbox" id="album" name="full_album" value="1" onclick="flipField(\'unplayed\');flipField(\'artist\')" /><br /> + <input type="checkbox" id="artist" name="full_artist" value="1" onclick="flipField(\'unplayed\');flipField(\'album\')" /> + </td> + </tr> + <tr class="even"> + <td nowrap> ' . _("from catalog") . '</td> + <td> +'; + + show_catalog_pulldown( -1, 0); + + print ' + </tr> + <tr> + <td colspan="4"> + <input type="hidden" name="aaction" value="Play!" /> + <input class="button" type="submit" name="aaction" value="' . _("Play Random Songs") . '" /> + </td> + </tr> + </table> + </td></tr> + </table> + </form> +'; + +} // show_random_play() + + +/* + * show_artist_pulldown() + * + * Helper functions for album and artist functions + * + */ + +function show_artist_pulldown ($artist) { + + global $settings; + $dbh = dbh(); + + $query = "SELECT id,name FROM artist ORDER BY name"; + $db_result = mysql_query($query, $dbh); + echo "<select name=\"artist\">\n"; + + while ( $r = mysql_fetch_row($db_result) ) { + // $r[0] = id, $r[1] = name + if ( $artist == $r[0] ) { + echo "<option value=\"$r[0]\" selected=\"selected\">$r[1]</option>\n"; + } + else { + echo "<option value=\"$r[0]\">$r[1]</option>\n"; + } + } + + echo " </select>"; +} // show_artist_pulldown() + + +/* + * show_album_pulldown() + * + */ + +function show_album_pulldown ($album) { + + global $settings; + $dbh = dbh(); + + $sql = "SELECT id,name FROM album ORDER BY name"; + $db_result = mysql_query($sql, $dbh); + + echo "<select name=\"album\">\n"; + + while ( $r = mysql_fetch_row($db_result) ) { + // $r[0] = id, $r[1] = name + if ( $album == $r[0] ) { + echo "\t <option value=\"${r[0]}\" selected=\"selected\">".htmlspecialchars($r[1])."</option>\n"; + } + else { + echo "\t <option value=\"${r[0]}\">".htmlspecialchars($r[1])."</option>\n"; + } + }//while + + echo "</select>\n"; +} // show_album_pulldown() + + +/* + * show_flagged_popup($reason); + * + * Shows a listing of the flagged_types for when people want to mark + * a song as being broken in some way. + */ + +function show_flagged_popup($reason,$label='value', $name='flagged_type', $other='') { + + global $settings; + $dbh = dbh(); + + $access = $_SESSION['userdata']['access']; + + $query = "SELECT type,value FROM flagged_types"; + if ($access !== 'admin') { + $query .= " WHERE access = '$access'"; + } + $db_result = mysql_query($query, $dbh); + + echo "<select name=\"$name\" $other>\n"; + + while ( $r = mysql_fetch_array($db_result) ) { + // $r[0] = id, $r[1] = type + if ( $reason === $r['type'] ) { + echo "\t<option value=\"".$r['type']."\" selected=\"selected\">".$r[$label]."</option>\n"; + } + else { + echo "\t<option value=\"".$r['type']."\">".$r[$label]."</option>\n"; + } + } + + echo "</select>\n"; +} // show_flagged_popup() + + +/* + * show_genre_pulldown() + * + * Set complete=1 if you want the entire genre list + * + */ + +function show_genre_pulldown ($genre, $complete) { + global $settings; + $dbh = dbh(); + + // find the genres we have in use + if ( $complete ) { + $sql = "SELECT id FROM genre ORDER BY name"; + } + else { + $sql = "SELECT DISTINCT song.genre FROM genre, song" . + " WHERE song.genre = genre.id" . + " ORDER BY genre.name"; + } + + $db_result = mysql_query($sql, $dbh); + + echo "<select name=\"genre[]\" MULTIPLE size=\"7\">\n"; + + if ( ! $complete ) { + $genre_info = get_genre_info( -1 ); + if ( $genre == -1 ) { + echo " <option value=\"-1\" selected=\"selected\">${genre_info[0]} - (${genre_info[1]})</option>\n"; + } + else { + echo " <option value=\"-1\">${genre_info[0]} - (${genre_info[1]})</option>\n"; + } + } + + while ( $r = mysql_fetch_row($db_result) ) { + // $r[0] = genre id + list($genre_name, $genre_count) = get_genre_info($r[0]); + $genre_name = htmlspecialchars($genre_name); + + if ( $genre == $r[0] ) { + echo " <option value=\"${r[0]}\" selected=\"selected\">$genre_name - ($genre_count)</option>\n"; + } + else { + echo " <option value=\"${r[0]}\">$genre_name - ($genre_count)</option>\n"; + } + } + + echo "</select>"; +} // show_genre_pulldown() + +/* + * show_catalog_pulldown() + * + * Set complete=1 if you want the entire catalog list (including disabled) + * + */ + +function show_catalog_pulldown ($catalog, $complete) { + global $settings; + // find the genres we have in use + $sql = "SELECT id,name FROM catalog ORDER BY name"; + + $db_result = mysql_query($sql, dbh()); + + echo "<select name=\"catalog\">\n"; + + echo " <option value=\"-1\" selected=\"selected\">All</option>\n"; + + while ( $r = mysql_fetch_row($db_result) ) + { + // $r[0] = genre id + $catalog_name = htmlspecialchars($r[1]); + + if ( $catalog == $r[0] ) + { + echo " <option value=\"${r[0]}\" selected=\"selected\">$catalog_name</option>\n"; + } + else + { + echo " <option value=\"${r[0]}\">$catalog_name</option>\n"; + } + } + echo "</select>"; +} // show_catalog_pulldown() + + +/* + * update_counter() + * + * update what song/album/artist has just been played + * + */ + +function update_counter ($type, $id, $dbh=0) { + + global $settings; + if (!is_resource($dbh)) { + $dbh = dbh(); + } + + // from hopson: these queries will be very useful for generating overall statistics: + /* + SELECT song.title,SUM(object_count.count) FROM song,object_count WHERE object_count.object_type = 'song' AND object_count.object_id = song.id GROUP BY song.id; + + SELECT album.name,SUM(object_count.count) FROM album,object_count WHERE object_count.object_type = 'album' AND object_count.object_id = album.id GROUP BY album.id; + + SELECT artist.name,SUM(object_count.count) FROM artist,object_count WHERE object_count.object_type = 'artist' AND object_count.object_id = artist.id GROUP BY artist.id; + + SELECT playlist.name,SUM(object_count.count) FROM playlist,object_count WHERE object_count.object_type = 'playlist' AND object_count.object_id = playlist.id GROUP BY playlist.id; + */ + + if ( $type == 'song' ) { + $sql = "UPDATE $type SET times_played = times_played + 1 WHERE id = '$id'"; + } + else { + $sql = "UPDATE $type SET times_played = times_played + 1 WHERE id = '$id'"; + } + + $db_result = mysql_query($sql, $dbh); +} // update_counter() + + + +/* + * delete_user_stats() + * + * just delete stats for specific users or all of them + * + */ + +function delete_user_stats ($user) { + + $dbh = dbh(); + + if ( $user == 'all' ) { + $sql = "DELETE FROM object_count"; + } + else { + $sql = "DELETE FROM object_count WHERE userid = '$user'"; + } + $db_result = mysql_query($sql, $dbh); +} // delete_user_stats() + + +/* + * insert_flagged_song() + * + */ + +function insert_flagged_song($song, $reason, $comment) { + + $user = $_SESSION['userdata']['id']; + $time = time(); + $sql = "INSERT INTO flagged (user,song,type,comment,date)" . + " VALUES ('$user','$song', '$reason', '$comment', '$time')"; + $db_result = mysql_query($sql, dbh()); + +} // insert_flagged_song() + + +/* + * get_flagged(); + * + * Get all of the songs from the flagged table. These are songs that + * may or may not be broken. + * Deprecated by hopson on 7/27 + */ + +function get_flagged() { + + $dbh = dbh(); + + $sql = "SELECT flagged.id, user.username, type, song, date, comment" . + " FROM flagged, user" . + " WHERE flagged.user = user.id" . + " ORDER BY date"; + $db_result = mysql_query($sql, $dbh); + + $arr = array(); + + while ( $flag = mysql_fetch_object($db_result) ) { + $arr[] = $flag; + } + + return $arr; +} // get_flagged() + + +/* + * get_flagged_type($type); + * + * Return the text associated with this type. + */ + +function get_flagged_type($type) { + + $dbh = dbh(); + + $sql = "SELECT value FROM flagged_types WHERE type = '$type'"; + echo $sql; + $db_result = mysql_query($sql, $dbh); + + if ($flagged_type = mysql_fetch_object($db_result)) { + return $flagged_type->value; + } + else { + return FALSE; + } +} // get_flagged_type() + + +/* + * delete_flagged( $flag ); + * + */ + +function delete_flagged($flag) { + + $dbh = dbh(); + + $sql = "DELETE FROM flagged WHERE id = '$flag'"; + $db_result = mysql_query($sql, $dbh); +} // delete_flagged() + + +/*********************************************************/ +/* Functions for getting songs given artist, album or id */ +/*********************************************************/ +// TODO : albums should be always gruoped by +// id, like 'greatest hits' album is below, never by name. +// Other catalog functions should take care of assigning all +// songs with same name album to the same album id. It should +// not be done here. +// I'm commenting all this out to always sort by ID, to +// see how bad it is. -Rubin +function get_songs_from_album ($album) { + + global $settings; + $dbh = dbh(); + + $songs = array(); + + $query = "SELECT track, id as song FROM song" . + " WHERE album = '$album'" . + " ORDER BY track, title"; + + $db_result = mysql_query($query, $dbh); + + while ( $r = mysql_fetch_array($db_result) ) { + $songs[] = $r; + } + + return $songs; +} + + +function get_song_ids_from_album ($album) { + + $dbh = dbh(); + + $song_ids = array(); + + $query = "SELECT id FROM song" . + " WHERE album = '$album'" . + " ORDER BY track, title"; + + $db_result = mysql_query($query, $dbh); + + while ( $r = mysql_fetch_object($db_result) ) { + $song_ids[] = $r->id; + } + + return $song_ids; + +} + + +function get_song_ids_from_artist ($artist) { + + global $settings; + $dbh = dbh(); + + $song_ids = array(); + $artist = sql_escape($artist); + + $query = "SELECT id FROM song" . + " WHERE artist = '$artist'" . + " ORDER BY album, track"; + + $db_result = mysql_query($query, $dbh); + + while ( $r = mysql_fetch_object($db_result) ) { + $song_ids[] = $r->id; + } + + return $song_ids; +} + + +/* + * get_song_ids_from_artist_and_album(); + * + * Get all of the songs that are from this album and artist + * + */ +function get_song_ids_from_artist_and_album ($artist, $album) { + + global $settings; + $dbh = dbh(); + + $sql = "SELECT id FROM song" . + " WHERE artist = '$artist'" . + " AND album = '$album'" . + " ORDER BY track, title"; + $db_result = mysql_query($sql, $dbh); + + $song_ids = array(); + + while ( $r = mysql_fetch_object($db_result) ) { + $song_ids[] = $r->id; + } + + return $song_ids; +} + + +// Used by playlist functions when you have an array of something of type +// and you want to extract the songs from it whether type is artists or albums +function get_songs_from_type ($type, $results, $artist_id = 0) { + + $dbh = dbh(); + + $count = 0; + $song = array(); + + foreach ($results as $value) { + + // special case from the album view where we don't want every orphan + if ($type == 'album' && ($value == 'orphans' || $artist_id != 0)) { + $sql = "SELECT id FROM song WHERE $type = '$value' AND artist = '$artist_id'"; + $db_result = mysql_query($sql, $dbh); + } + else { + $sql = "SELECT id FROM song WHERE $type = '$value'"; + $db_result = mysql_query($sql, $dbh); + } + + while ( $r = mysql_fetch_row($db_result) ) { + $song[$count] = $r[0]; + $count++; + } + } + return $song; +} + + +/*********************************************************/ +/* This is the main song display function. I found tieing it to the playlist functions + was really handy in getting added functionality at no cost. +/* Lets tie it to album too, so we can show art ;) */ +/*********************************************************/ +function show_songs ($song_ids, $playlist_id=0, $album=0) { + + $dbh = dbh(); + + // Get info about current user + $user = new User($_SESSION['userdata']['username']); + + // Get info about playlist owner + if (isset($playlist_id) && $playlist_id != 0) { + $sql = "SELECT owner FROM playlist WHERE id = '$playlist_id'"; + $db_result = mysql_query($sql, $dbh); + if ($r = mysql_fetch_array($db_result)) { + $pluser = get_user_byid($r[0]); + } + } + + $totaltime = 0; + $totalsize = 0; + + require (conf('prefix') . "/templates/show_songs.inc"); + + return TRUE; + +}// function show_songs + + + +function show_playlist_form () { + + print <<<ECHO +<table cellpadding="5" class="tabledata"> + <tr align="center" class="odd"> + <td> + <input type="button" name="select_all" value="Select All" onclick="this.value=check_results()" /> + </td> + <td> Playlist:</td> + <td> + <input name="action" class="button" type="submit" value="Add to" /> +ECHO; + + show_playlist_dropdown(); + + print <<<ECHO + <input name="action" class="button" type="submit" value="View" /> + <input name="action" class="button" type="submit" value="Edit" /> + + </td> + </tr> + <tr align="center" class="even"> + <td colspan="6"> + <input name="action" class="button" type="submit" value="Play Selected" /> + </td> + </tr> +</table> +ECHO; + +} + + +function get_artist_name ($artist, $dbh=0) { + + global $settings; + if (!is_resource($dbh)) { + $dbh = dbh(); + } + + $query = "SELECT name FROM artist WHERE id = '$artist'"; + $db_result = mysql_query($query, $dbh); + + if ($r = mysql_fetch_object($db_result)) { + return $r->name; + } + else { + return FALSE; + } +} + + +function get_artist_info ($artist_id) { + + $dbh = dbh(); + + $sql = "SELECT * FROM artist WHERE id = '$artist_id'"; + $db_result = mysql_query($sql, $dbh); + if ($info = mysql_fetch_array($db_result)) { + $sql = "SELECT COUNT(song.album) FROM song " . + " WHERE song.artist = '$artist_id'" . + " GROUP BY song.album"; + $db_result = mysql_query($sql, $dbh); + + $albums = 0; + $songs = 0; + while(list($song) = mysql_fetch_row($db_result)) { + $songs += $song; + $albums++; + } + $info['songs'] = $songs; + $info['albums'] = $albums; + //FIXME: Lame place to put this + //if ($songs < conf('min_artist_songs') || $albums < conf('min_artist_albums')) { + // return FALSE; + //} + return $info; + } + else { + return FALSE; + } +} + + +function get_artist_from_album ($album_id) { + + global $settings; + $dbh = dbh(); + + $query = "SELECT DISTINCT artist.id, artist.name FROM artist,song" . + " WHERE song.album = '$album_id' AND song.artist = artist.id"; + $db_result = mysql_query($query, $dbh); + $r = mysql_fetch_object($db_result); + return $r; +} + + +function get_artist_name_from_song ($song_id) { + + $dbh = dbh(); + + $sql = "SELECT artist.name AS name FROM artist, song" . + " WHERE artist.id = song.artist" . + " AND song.id = '$song_id'"; + $db_result = mysql_query($sql, $dbh); + + if ($r = mysql_fetch_object($db_result)) { + return $r->name; + } + else { + return FALSE; + } +} + + +function get_album_name ($album, $dbh = 0) { + + $album = new Album($album); + return $album->name; +} // get_album_name + + +function get_genre_info($genre_id) { + + global $settings; + $dbh = dbh(); + + $sql = "SELECT name FROM genre WHERE id = '$genre_id'"; + $db_result = mysql_query($sql, $dbh); + + // if its -1 then we're doing all songs + if ( $genre_id < 0 ) { + $sql = "SELECT count(*) FROM song"; + } + else { + $sql = "SELECT count(*) FROM song WHERE genre = '$genre_id'"; + } + + $genre_result = mysql_query($sql, $dbh); + + $genre_count = mysql_fetch_row($genre_result); + + $r = mysql_fetch_row($db_result); + + // Crude hack for non-standard genre types + if ($genre_id == -1) { + return array('All', $genre_count[0]); + } + elseif ($genre_id == 0) { + return array('N/A', $genre_count[0]); + } + else { + return array($r[0], $genre_count[0]); + } +} + + +function get_genre($id) { + + global $settings; + $dbh = dbh(); + + $query = "SELECT * FROM genre WHERE id = '$id'"; + $db_result = mysql_query($query, $dbh); + + $r = mysql_fetch_object($db_result); + return $r; +} + + +// Utility function to help move things along +function get_song_info ($song, $dbh = 0) { + + $song = new Song($song); + return $song; + +} // get_song_info + + +/*! + @function show_albums + @discussion show many albums, uses view class +*/ +function show_albums ($albums,$view=0) { + + $dbh = libglue_param(libglue_param('dbh_name')); + + if (!$view) { + $view = new View($_SESSION['view_base_sql'], $_SESSION['script'], $total_items,$_SESSION['view_offset_limit']); + } + + if ($albums) { + require (conf('prefix') . "/templates/show_albums.inc"); + } + else { + echo "<p><font color=\"red\">No Albums Found</font></p>"; + } + +} // show_albums + + +// Had to tweak this so it would show both public and private playlists +// Defaults to showing both although you could pass type=private|adminprivate|public +// to see only those +function show_playlists ($type = 'all') { + + $dbh = dbh(); + + $user = $GLOBALS['user']; + + $web_path = conf('web_path'); + + // mapping of types to pretty names + $typemap = array( "public" => _("Public"), + "private" => _("Your Private"), + "adminprivate" => _("Other Private") + ); + + if ($type == 'all') { + show_playlists('private'); + if ( $user->access === 'admin' ) { + show_playlists('adminprivate'); + } + show_playlists('public'); + return true; + } + elseif ($type == 'public') { + $sql = "SELECT id,name,owner,date ". + " FROM playlist ". + " WHERE type='public'". + " ORDER BY name"; + } + elseif ($type == 'private') { + $sql = "SELECT id,name,owner,date ". + " FROM playlist ". + " WHERE type='private'" . + " AND owner = '$user->id'" . + " AND name <> 'Temporary'". + " ORDER BY name"; + } + elseif ($type == 'adminprivate') { + if ( $user->access === 'admin' ) { + $sql = "SELECT id,name,owner,date ". + " FROM playlist ". + " WHERE type='private'" . + " AND owner != '$user->id'" . + " AND name <> 'Temporary'". + " ORDER BY name"; + } + else { + // No admin access + $sql = 'SELECT 1+1'; + } + } + else { + echo "** Error ** Call to show_playlists with unknown type $type ". + "in file ".$_SERVER['PHP_SELF']." ** <br />\n"; + $sql = 'SELECT 1+1'; + } + + $db_result = mysql_query($sql, $dbh); + + print <<<ECHO +<h3>$typemap[$type] Playlists</h3> + +<table class="tabledata" cellspacing="0" cellpadding="0" border="0"> + <tr class="table-header"> + <th>Playlist Name</th> + <th># Songs</th> + <th>Owner</th> + <th colspan="6">Actions</th> + </tr> + +ECHO; + + flip_class(array('even','odd')); + + if ( mysql_num_rows($db_result) ) { + while ( $r = mysql_fetch_array($db_result) ) { + $plname = $r['name']; + $plid = $r['id']; + $pluser = get_user_byid($r['owner']); + $plfullname = $pluser->fullname; + $plowner = $pluser->username; + + // find out how many songs in this playlist + $count_query = "SELECT count(*) ". + " FROM playlist_data ". + " WHERE playlist = '$plid'"; + $count_result = mysql_query($count_query, $dbh); + list($count) = mysql_fetch_row($count_result); + $class = flip_class(); + echo " <tr class=\"$class\">\n"; + echo " <td><a href=\"$web_path/playlist.php?playlist_id=$plid&action=view_list\">$plname</a></td>\n"; + echo " <td>$count</td>\n"; + echo " <td>$plfullname</td>\n"; + echo " <td><a href=\"$web_path/playlist.php?playlist_id=$plid&action=view_list\">" . _("View") . "</a></td>\n"; + + if ($user->id == $pluser->id || $user->access === 'admin') { + echo " <td><a href=\"$web_path/playlist.php?playlist_id=$plid&action=edit\">" . _("Edit") . "</a></td>\n"; + echo " <td><a href=\"$web_path/playlist.php?playlist_id=$plid&action=delete_playlist\">" . _("Delete") . "</a></td>\n"; + } + else { + echo " <td> </td>\n"; + echo " <td> </td>\n"; + } + + if ( $count[0] ) { + echo " <td><a href=\"$web_path/song.php?action=m3u&playlist_id=$plid\">" . _("Play") . "</a> | " . + "<a href=\"$web_path/song.php?action=random&playlist_id=$plid\">" . _("Random") . "</a></td>\n"; + } + else { + echo " <td> </td>\n"; + } + if( batch_ok() ) { + echo" <td><a href=\"$web_path/batch.php?action=pl&id=$plid\">" . _("Download") . "</a></td>\n"; + } else { + echo" <td> </td>\n"; + } + + echo " </tr>\n"; + } + echo "</ul>\n"; + } //if rows in result + else { + echo " <tr class=\"even\">\n"; + echo " <td colspan=\"7\">" . _("There are no playlists of this type") . "</td>\n"; + echo " </tr>\n"; + } + + echo "</table>\n"; + echo "<br>\n"; + +} + +function get_playlist_track_from_song ( $playlist_id, $song_id ) { + + $dbh = dbh(); + + $sql = "SELECT track FROM playlist_data" . + " WHERE playlist = '$playlist_id'" . + " AND song = '$song_id'"; + $db_result = mysql_query($sql, $dbh); + if ($r = mysql_fetch_array($db_result)) { + return $r[0]; + } + else { + return FALSE; + } +} + +//FIXME: Pull this and put it in a template +function show_playlist_create () { + + $web_path = conf('web_path'); + + print <<<ECHO +<form name="songs" method="post" action="$web_path/playlist.php"> +<table class="border"><tr class="table-header"><td colspan="2" align="center"> +ECHO; + +print _("Create a new playlist"); + print <<<ECHO + </td> + </tr> + <tr class="even"> + <td align="left"> Name: </td> + <td align="left"><input type="text" name="playlist_name" size="20" /></td> + </tr> + <tr class="odd"> + <td align="left"> Type: </td> + <td align="left"> + <select name="type"> + <option value="private"> Private </option> + <option value="public"> Public </option> + </select> + </td> + </tr> + <tr class="even"> + <td align="left"> </td> + <td align="left"> + <input type="submit" name="action" value="Create" /> + <input type="reset" name="Reset" /> + </td> + </tr> +</table> +</form> + +ECHO; + +} + + +function show_playlist_edit ( $playlist ) { + + $username = $_SESSION['userdata']['username']; + if (check_playlist_access($playlist->id,$username) == false) { + show_playlist_access_error($playlist, $username); + return; + } + + $plname = $playlist->name; + $self = $_SERVER['PHP_SELF']; + + print <<<ECHO +<form name="songs" method="post" action="$self"> +<input type="hidden" name="playlist_id" value="$playlist->id" /> +<table class="border"> + <tr class="table-header"> + <td colspan="2">Editing Playlist</td> + </tr> + <tr> + <td align="left"> Name: </td> + <td align="left"> + <input type="text" name="new_playlist_name" value="$plname" size="20" /> + </td> + </tr> + <tr> + <td align="left"> Type: </td> + <td align="left"> + <select name="type"> +ECHO; + + if ($playlist->type == 'public') { + echo "<option value=\"public\" selected=\"selected\">Public</option>"; + } + else { + echo "<option value=\"public\">Public</option>"; + } + + if ($playlist->type == 'private') { + echo "<option value=\"private\" selected=\"selected\">Private</option>"; + } + else { + echo "<option value=\"private\">Private</option>"; + } + + print <<<ECHO + </select> + </td> + </tr> + <tr> + <td align="left"> </td> + <td align="left"> + <input type="submit" name="action" value="Update" /> + </td> + </tr> +</table> +</form> +ECHO; + +} + + +// See if this user has access to work on this list +function check_playlist_access ($playlist_id, $username) { + + $dbh = dbh(); + + $sql = "SELECT playlist.id FROM playlist, user" . + " WHERE playlist.id = '$playlist_id'" . + " AND playlist.owner = user.id" . + " AND user.username = '$username'"; + $db_result = mysql_query($sql, $dbh); + + if ( mysql_num_rows($db_result) == 1) { + return TRUE; + } + else { + if (!conf('use_auth')) { + return TRUE; + } + // check to see if this user is an admin + if ($user = get_user($username)) { + if ( $user->access == 'admin' ) { + return TRUE; + } + } + } + + // If we get here, access is denied + return FALSE; + +} + + +function show_playlist_dropdown ($playlist_id=0) { + + global $settings; + $dbh = dbh(); + + $userid = scrub_in($_SESSION['userdata']['id']); + $sql = "SELECT * FROM playlist" . + " WHERE owner = '$userid'" . + " AND name <> 'Temporary'" . + " ORDER BY name"; + $db_result = @mysql_query($sql, $dbh); + + print <<<ECHO +<select name="playlist_id"> +<option value="0"> -New Playlist- </option> + +ECHO; + + while ( $r = @mysql_fetch_object($db_result) ) { + if ( $playlist_id == $r->id ) { + echo "<option value=\"" . $r->id . "\" selected=\"selected\">" . $r->name . "</option>\n"; + } + else { + echo "<option value=\"" . $r->id . "\">" . $r->name . "</option>\n"; + } + } + echo "</select>\n"; +} + + +// Used to show when we have an access error for a playlist +function show_playlist_access_error ($playlist, $username) { + + $plname = $playlist->name; + $pluser = new User($playlist->owner); + $plowner = $pluser->username; + + print <<<ECHO +<p style="font: 12px bold;"> Playlist Access Error </p> +<p>$username doesn't have access to update the '$plname' playlist, it is owned by $plowner.</p> + +ECHO; + +} + + +// Used to show a form with confirm action button on it (for deleting playlists, users, etc) +/*! + @function show_confirm_action + @discussion shows a confirmation of an action, gives a YES/NO choice +*/ +function show_confirm_action ($text, $script, $arg) { + + $web_path = conf('web_path'); + require (conf('prefix') . "/templates/show_confirm_action.inc.php"); + +} // show_confirm_action + + +// search functions +function search_by_type ($type, $search) { + + $dbh = dbh(); + + // supported types are album, artist and song + if ( $type == 'Album' ) { + $query = "SELECT id FROM album WHERE name LIKE '%$search%'"; + } + elseif ( $type == 'Artist' ) { + $query = "SELECT id FROM artist WHERE name LIKE '%$search%'"; + } + elseif ( $type == 'Song title' ) { + $query = "SELECT id FROM song WHERE title LIKE '%$search%'"; + } + elseif ( $type == 'Genre' ) { + $query = "SELECT song.id as id FROM song, genre" . + " WHERE song.genre = genre.id" . + " AND genre.name LIKE '%$search%'"; + } + + $db_result = mysql_query($query, $dbh); + + $search_result = array(); + + while ( $r = mysql_fetch_array($db_result) ) { + $search_result[] = $r; + } + + return ($search_result); +} + + +function get_global_popular($type) { + + global $settings; + $dbh = dbh(); + + $sql = "SELECT object_id, SUM(count) as count FROM object_count" . + " WHERE object_type = '$type'" . + " GROUP BY object_id" . + " ORDER BY count DESC LIMIT " . conf('popular_threshold'); + $db_result = mysql_query($sql, $dbh); + + $items = array(); + $web_path = conf('web_path'); + + while ( $r = @mysql_fetch_object($db_result) ) { + if ( $type == 'song' ) { + $song = new Song($r->object_id); + $artist = $song->get_artist_name(); + $text = "$artist - $song->title"; + /* Add to array */ + $items[] = "<li> <a href=\"$web_path/song.php?action=m3u&song=$song->id\" title=\"$text\">" . truncate_with_ellipse($text, conf('ellipse_threshold_title')+3) . " ($r->count)</a> </li>"; + + } // if it's a song + + elseif ( $type == 'artist' ) { + $artist = get_artist_name($r->object_id); + if ($artist) { + $items[] = "<li> <a href=\"$web_path/artists.php?action=show&artist=$r->object_id\" title=\"$artist\">" . truncate_with_ellipse($artist, conf('ellipse_threshold_artist')+3) . " ($r->count)</a> </li>"; + } // if no artist found + } // if type isn't artist + elseif ( $type == 'album' ) { + $album = new Album($r->object_id); + if ($album) { + $items[] = "<li> <a href=\"$web_path/albums.php?action=show&album=$r->object_id\" title=\"$album->name\">" . truncate_with_ellipse($album->name,conf('ellipse_threshold_album')+3) . " ($r->count)</a> </li>"; + } + } + } // end while + + return $items; +} + + +// Get a list of newest $type (which can then be handed to show_info_box +function get_newest ($type = 'artist') { + + $dbh = dbh(); + + if (conf('popular_threshold') < 1) { conf(array('popular_threshold'=>'10'),1); } + + $sql = "SELECT DISTINCT $type FROM song ORDER BY addition_time " . + "DESC LIMIT " . conf('popular_threshold'); + $db_result = mysql_query($sql, $dbh); + + $items = array(); + $web_path = conf('web_path'); + + while ( $item = mysql_fetch_row($db_result) ) { + if ( $type == 'artist' ) { + $artist = new Artist($item[0]); + $artist->format_artist(); + $items[] = "<li>" . $artist->link . "</li>\n"; + } + elseif ( $type == 'album' ) { + $album = new Album($item[0]); + $album->format_album(); + $items[] = "<li>" . $album->f_name . "</li>"; + } + } + return $items; +} + + +function show_info_box ($title, $type, $items) { + + $web_path = conf('web_path'); + $popular_threshold = conf('popular_threshold'); + + echo "<table class=\"border\" cellspacing=\"1\" cellpadding=\"3\" width=\"100%\" border=\"0\">"; + echo " <tr class=\"table-header\">"; + + + if ($type == 'your_song') { + echo "<td>$title - <a href=\"$web_path/song.php?action=m3u&your_popular_songs=$popular_threshold\">Play</a></td>\n"; + } + elseif ($type == 'song') { + echo "<td>$title - <a href=\"$web_path/song.php?action=m3u&popular_songs=$popular_threshold\">Play</a></td>\n"; + } + else { + echo "<td>$title</td>\n"; + } + + print <<<ECHO + </tr> + <tr class="even"> + <td align="left"> + <ol> + +ECHO; + + foreach ($items as $item) { + echo "$item\n"; + } + + print <<<ECHO + </ol> + </td> + </tr> +</table> + +ECHO; + +} + + +/* + * show_add_user() + * + */ + +function show_add_user () { + + global $error_color; + + $web_path = conf('web_path'); + + print <<<ECHO +<h3> 2. Add the Admin user for Ampache </h3> +<p><font color="$error_color">$error_text</font></p> +<p>This will be the administration user to get you started. You can delete/chage it later. </p> +<form name="user" method="post" action="$web_path/setup.php"> +<table border="0" cellpadding="2" cellspacing="0"> + <tr> + <td>Username:</td> + <td><input type="text" name="username" value="" size="30" /></td> + </tr> + <tr> + <td>Fullname:</td> + <td><input type="text" name="fullname" value="" size="30" /></td> + </tr> + <tr> + <td>Password:</td> + <td><input type="password" name="password_1" value="" size="30" /></td> + </tr> + <tr> + <td>Password: (again)</td> + <td><input type="password" name="password_2" value="" size="30" /></td> + </tr> +</table> +<input type="hidden" name="step" value="user" /><br /> +<input type="hidden" name="step_text" value="Add the Admin user for Ampache" /><br /> +<input type="submit" name="action" value="Add the Admin User" /><br /> +</form> +ECHO; + +} // show_add_user() + + +function scrub_out($str) { + + return stripslashes($str); +} + + +function unhtmlentities ($string) { + + $trans_tbl = get_html_translation_table (HTML_ENTITIES); + $trans_tbl = array_flip ($trans_tbl); + $ret = strtr ($string, $trans_tbl); + return preg_replace('/&#(\d+);/me', "chr('\\1')",$ret); +} + + +function insert_album($album) { + + global $settings; + $dbh = dbh(); + + preg_match("/^(A |An |The ){0,1}(.*?)$/i",$album, $matches); + $album = sql_escape($matches[2]); + + switch($matches[1]) { + case 'The ': + case 'the ': + $prefix = 'The'; + break; + case 'A ': + case 'a ': + $prefix = 'A'; + break; + case 'An ': + case 'an ': + $prefix = 'An'; + break; + default: + $prefix = ''; + } + + $sql = "INSERT INTO album (name, prefix)" . + " VALUES ( '$album', '$prefix' )"; + $db_result = mysql_query($sql, $dbh); + + return (mysql_insert_id($dbh)); +} // insert_album + + +/* + * insert_artist() + * + */ + +function insert_artist($artist) { + + global $settings; + $dbh = dbh(); + + $matches = array(); + $var = preg_match('/^(A |An |The ){0,1}(.*?)$/i',$artist, $matches); + $artist = sql_escape($matches[2]); + + switch($matches[1]) { + case 'The ': + case 'the ': + $prefix = 'The'; + break; + case 'A ': + case 'a ': + $prefix = 'A'; + break; + case 'An ': + case 'an ': + $prefix = 'An'; + break; + default: + $prefix = ''; + } + + $sql = "INSERT INTO artist (name, prefix)" . + " VALUES ( '$artist', '$prefix' )"; + $db_result = mysql_query($sql, $dbh); + return (mysql_insert_id($dbh)); +} // insert_artist +?> diff --git a/modules/mpd/CHANGELOG b/modules/mpd/CHANGELOG new file mode 100644 index 00000000..02453424 --- /dev/null +++ b/modules/mpd/CHANGELOG @@ -0,0 +1,38 @@ +========================================================
+* mpd.class.php - PHP Object Interface to the MPD Music Player Daemon
+* CHANGELOG
+* Copyright (C) 2003-2004 Benjamin Carlisle (bcarlisle@24oz.com)
+* http://mpd.24oz.com/ | http://www.musicpd.org/
+========================================================
+
+v1.2 - Released 05/05/2004
+ENHANCEMENTS
+* Program version compatibility table now does not allow incompatible commands to be sent to
+ the MPD. This is done to prevent sending outdated/incompatible commands.
+* Added new MPD v0.9.1 commands: PLMove(), SetRandom(), SeekTo()
+* Added MPD password authentication support.
+* Added GetArtists() and GetAlbums() functions.
+* Reflected 'volume' command deprecation in MPD v0.10.0
+
+BUGFIXES
+* None noted.
+
+
+v1.1 - Released 09/23/2003
+ENHANCEMENTS
+ * There is no longer the need to call RefreshInfo directly; it is automatically called upon
+ connection to MPD. Although this may be considered a bad move by some purists, I feel that
+ it substantially increases usability.
+ * Properties are now kept in sync with MPD. In other words, if a method call will modify a
+ MPD stateful variable (such as AdjustVolume()), it also will modify the object's property.
+ * Methods that manually retrieve MPD settings (GetVolume(), GetStatistics(), etc.) will soon
+ be deprecated. Instead, be a nice developer and use the object property (eg, $mpd->volume,
+ $mpd->uptime, etc.).
+ * Added the Find() method. Syntax is similar to Search().
+ * Although the command queuing (bulk command) methods were included in v1.0, they were not
+ documented. The QueueCommand() and SendCommandQueue() are now official methods.
+
+BUGFIXES
+ * None noted.
+
+v1.0 - Released 08/26/2003
diff --git a/modules/mpd/COPYING b/modules/mpd/COPYING new file mode 100644 index 00000000..d60c31a9 --- /dev/null +++ b/modules/mpd/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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 + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/modules/mpd/README b/modules/mpd/README new file mode 100644 index 00000000..a5e40267 --- /dev/null +++ b/modules/mpd/README @@ -0,0 +1,58 @@ +========================================================
+* mpd.class.php - PHP Object Interface to the MPD Music Player Daemon
+* README Version 1.2, released 05/05/2004
+* Copyright (C) 2003-2004 Benjamin Carlisle (bcarlisle@24oz.com)
+* http://mpd.24oz.com/ | http://www.musicpd.org/
+========================================================
+[ABOUT]
+
+This class/API is a PHP interface to the MPD (http://www.musicpd.org/). Using
+this interface, you may write your own custom web-based interface to the MPD.
+
+In the event you want to talk with me, you may email bcarlisle@24oz.com. I am
+also a relatively frequent visitor of #mpd on FreeNet, under the nick of 'moot'.
+
+More information can be found at the MPD-Class Homepage, http://mpd.24oz.com/.
+
+[CONTENTS]
+The distribution contains the following files:
+
+mpd.class.php The mpd object PHP file.
+mpd-class-example.php An example (simple!) interface using the class.
+README This file
+USAGE Brief class documentation
+COPYING GNU Public License
+
+
+[HISTORY]
+I wrote this program in order to supplement the wonderful MPD in my own home audio
+environment. The interface that is supplied alongside MPD, called phpMp, worked, but
+was not as customizable as I would have liked.
+
+I currently use Netjuke (http://netjuke.sourceforge.net/) in order to catalog and
+locally play my MP3s, but I wanted a way to play songs through a playlist on my
+server.
+
+This class solved that problem -- I could integrate both pieces of software from a
+look-and-feel perspective and also allow one to talk to the other.
+
+This class has been tested with
+ PHP 4.2.1, 4.2.3
+ MPD 0.8.x, 0.9.x, 0.10.x
+
+[INSTALLATION]
+To install this php class, you simply need to place it in a directory that may be
+include()ed by PHP, and call require('mpd.class.php'). You may also place it in your
+PHP Include Path.
+
+[DOCUMENTATION]
+Please see the USAGE document for class methods/properties and examples on how to use
+this class.
+
+[CREDITS]
+Thank you to the MPD folks. You've put together a very easy-to-use and stable music daemon.
+Another big thanks to Netjuke folks... absolutely great program!
+
+[LICENSE]
+MPD is released under the GNU Public License.
+
diff --git a/modules/mpd/USAGE b/modules/mpd/USAGE new file mode 100644 index 00000000..fad03635 --- /dev/null +++ b/modules/mpd/USAGE @@ -0,0 +1,378 @@ +========================================================
+* mpd.class.php - PHP Object Interface to the MPD Music Player Daemon
+* USAGE Version 1.2, released 05/05/2004
+* Copyright (C) 2003-2004 Benjamin Carlisle (bcarlisle@24oz.com)
+* http://mpd.24oz.com/ | http://www.musicpd.org/
+========================================================
+
+[NOTES]
+
+The following document outlines the object properties and methods of the mpd.class.php
+PHP class. Please note that this PHP class relies heavily on the functionality within
+MPD, and that this document reflects the functionality as of the current release of
+MPD (0.10.3).
+
+There are other object functions/properties that are not included in this documentation.
+They are either unsupported, untested, or, most likely, intended for calling within the
+object itself. You should be able to get along fine using only what is described below.
+
+
+[OBJECT INSTANTIATION]
+
+mpd(host,port,[password])
+Object constructor. When called, it will initialize all object variables and attempt to
+connect using the arguments supplied. If the connection attempt succeeds, data will be
+retrieved from the server and all appropriate class properties will be set.
+Note: if you're using MPD with a password, you must supply a password with at least read
+access to properly connect.
+
+
+ ARGUMENTS
+ host - the hostname upon which MPD is running
+ port - the TCP port on which MPD is listening
+ password (optional) - Authentication password for MPD server
+ RETURNS
+ mpd Object, upon success
+ EXAMPLE
+ $mpdObject = new mpd('localhost',2100);
+
+
+
+[PROPERTIES]
+
+The MPD Object, once instantiated, has the following properties:
+
+ mpd_class_version - The current version of MPD-Class.
+ mpd_version - The version string as returned by MPD.
+ connected - TRUE if it has properly connected to MPD, FALSE otherwise.
+ volume - The volume setting on MPD (1-100).
+ repeat - The status of the repeat (loop) flag. Either 1 (on) or 0 (off).
+ uptime - The number of seconds since the MPD server was started.
+ playtime - The number of elapsed seconds MPD has been actively playing.
+ num_songs - The number of tracks in the MPD database.
+ num_artists - The number of artists in the MPD database.
+ num_albums - The number of albums in the MPD database.
+ playlist_count - The number of tracks in the MPD playlist.
+ state - The current state of the MPD. Use constants:
+ MPD_STATE_PLAYING, MPD_STATE_STOPPED, MPD_STATE_PAUSED
+ num_songs_played - Number of songs played since MPD was started.
+ current_track_id - The playlist index of the currently playing track.
+ current_track_length - The length, in seconds, of the playing track.
+ current_track_pos - The position, in elapsed seconds, of the playing track.
+ errStr - The last error message returned. Empty if there was no error.
+ playlist - An multidimensional array containing the current playlist.
+
+
+
+[METHODS]
+
+Methods available for this class have been classified into several groups.
+
+ - Mixer Control Methods - For configuring mixer settings
+ - Player Control Methods - For controlling playback
+ - Playlist Maintenence Methods - For maintaining the MPD playlist, as well as stored M3U playlists.
+ - Searching/Browsing Methods - For locating tracks
+ - MPD Control Methods - Miscellaneous MPD control
+ - Other/Advanced Methods - Stuff that shouldn't be necessary, but are included ;)
+
+For the most part, you will not need the Other methods. They are included for Compatibility, as well
+as for use in my own code. You can do anything you need without using them, but use at your own risk!
+
+Mixer Control Methods
+SetVolume(vol) -
+ Sets the mixer volume on the MPD to <vol>.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->SetVolume(75);
+
+
+AdjustVolume(vol) -
+ Adjusts the mixer volume on the MPD by <vol>.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->AdjustVolume(-20);
+
+
+SetRepeat(1|0) -
+ Sets the repeat (loop) status to 1 (ON) or 0 (OFF).
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->SetRepeat(1);
+
+
+Player Control Methods
+Play() -
+ Begins playing the songs in the MPD playlist.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->Play();
+
+
+Pause() -
+ Toggles pausing on the MPD. Calling it once will pause the player, calling it again
+ will unpause.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->Pause();
+
+
+Stop() -
+ Stops playing the MPD.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->Stop();
+
+
+Next() -
+ Skips to the next song in the MPD playlist. If not playing, returns an error.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->Next();
+
+
+Previous() -
+ Skips to the previous song in the MPD playlist. If not playing, returns an error.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->Previous();
+
+
+SkipTo(idx) -
+ Skips directly to the <idx> song in the playlist.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->SkipTo(4);
+
+
+Playlist Maintenence Methods
+PLAdd(file) -
+ Adds the file <file> to the end of the playlist. <file> must be a song in the
+ MPD database.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->PLAdd("U2 - Pride.mp3");
+
+
+PLAddBulk(fileArray) -
+ Adds each track listed in a single-dimensional <fileArray>, which contains filenames
+ of tracks to add, to the end of the playlist. This is used to add many, many songs
+ to the playlist in one swoop.
+
+ RETURNS
+ NULL, upon failure adding any track.
+ EXAMPLE
+ $mpdObject->PLAddBulk($songArray);
+
+
+PLRemove(idx) -
+ Removes the track located at position <idx> from the playlist. This will shift
+ the tracks behind it up one position.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->PLRemove(2);
+
+
+PLClear() -
+ Clears the playlist entirely and stops playing MPD (if appropriate).
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->PLClear();
+
+
+PLSave(file) -
+ Saves the playlist to <file>.m3u for later retrieval.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->PLSave("mysongs");
+
+
+PLLoad(file) -
+ Retrieves the playlist from <file>.m3u and loads it into the current playlist.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->PLLoad("mysongs");
+
+
+PLShuffle() -
+ Randomly reorders the songs in the playlist.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->PLShuffle();
+
+
+MPD Control Methods
+Disconnect() -
+ Close the connection to the MPD server.
+
+ RETURNS
+ Nothing
+ EXAMPLE
+ $mpdObject->Disconnect();
+
+
+Shutdown() -
+ Shuts down the MPD server (aka sends the KILL command). This closes the current
+ connection, and prevents future communication with the server.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->Shutdown();
+
+
+DBRefresh() -
+ Causes MPD to refresh the database of its tracks.
+
+ RETURNS
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->DBRefresh();
+
+
+Searching/Browsing Methods
+Search(type,string) -
+ Searches the MPD database. The search <type> should be one of the following:
+ MPD_SEARCH_ARTIST, MPD_SEARCH_TITLE, MPD_SEARCH_ALBUM
+ The search <string> is a case-insensitive locator string. Anything that
+ contains <string> will be returned in the results.
+
+ RETURNS
+ Array containing search results, upon success.
+ NULL, upon failure
+ EXAMPLE
+ $results = $mpdObject->Search(MPD_SEARCH_ARTIST,"Met");
+
+
+Find(type,string) -
+ Similar to Search(), Find() looks for exact matches in the MPD database. The find <type> should be one of the following:
+ MPD_SEARCH_ARTIST, MPD_SEARCH_TITLE, MPD_SEARCH_ALBUM
+ The find <string> is a case-insensitive locator string. Anything that
+ exactly matches <string> will be returned in the results.
+
+ RETURNS
+ Array containing find results, upon success.
+ NULL, upon failure
+ EXAMPLE
+ $results = $mpdObject->Find(MPD_SEARCH_ARTIST,"Metallica");
+
+
+GetDir([dir]) -
+ Retrieves a database directory listing of the <dir> directory. If no
+ directory is specified, the directory listing is at the base of the
+ database directory path.
+
+ RETURNS
+ Array containing directory results, upon success.
+ NULL, upon failure
+ EXAMPLE
+ $humorArray = $mpdObject->GetDir("Humor");
+
+
+GetArtists() -
+ Retrieves a list of all artists in the database.
+
+ RETURNS
+ Array single-dimensional containing the list of artists, upon success.
+ NULL, upon failure
+ EXAMPLE
+ $artistArray = $mpdObject->GetArtists();
+
+
+GetAlbums([artist]) -
+ Retrieves a list of all albums in the database by a particular <artist> If no
+ artist is specified, all albums in the database are returned.
+
+ RETURNS
+ Array single-dimensional containing list of albums, upon success.
+ NULL, upon failure
+ EXAMPLE
+ $allAlbumArray = $mpdObject->GetAlbums();
+
+
+
+Other/Advanced Methods
+SendCommand(cmd,arg1,arg2...) -
+ Sends a generic command to the MPD server. Several command constants
+ are pre-defined for use (see MPD_CMD_* constant definitions in
+ mpd.class.php).
+
+ RETURNS
+ String containing server response, upon success
+ NULL, upon failure.
+ EXAMPLE
+ $response = $mpdObject->SendCommand("mycommand");
+
+
+
+QueueCommand(cmd,arg1,arg2...) -
+ Queues a generic command for later sending to the MPD server. The CommandQueue can
+ hold as many commands as needed, and are sent all at once, in the order they are queued,
+ using the SendCommandQueue() method. The syntax for queueing
+ commands is identical to SendCommand().
+
+ RETURNS
+ NULL, upon failure.
+ TRUE, otherwise.
+ EXAMPLE
+ $response = $mpdObject->QueueCommand(MPD_CMD_ADD,"myfile.mp3");
+
+
+
+SendCommandQueue() -
+ Sends all commands in the Command Queue to the MPD server.
+
+ RETURNS
+ NULL, upon the failure of any command in the queue.
+ TRUE, otherwise.
+ EXAMPLE
+ $mpdObject->QueueCommand(MPD_CMD_ADD,"myfile.mp3");
+ $mpdObject->QueueCommand(MPD_CMD_VOL,"+20");
+ $mpdObject->SendCommandQueue();
+
+
+
+RefreshInfo() -
+ Retrieves all object data from the MPD and stores it in each of the object
+ properties. Note: As of version 1.1, this is automatically called upon initial connection
+ to the MPD server; there is little need to use it.
+
+ RETURNS
+ TRUE, upon success
+ NULL, upon failure
+ EXAMPLE
+ $mpdObject->RefreshInfo();
+
+
+
+
+
diff --git a/modules/mpd/mpd-class-example.php b/modules/mpd/mpd-class-example.php new file mode 100644 index 00000000..802153de --- /dev/null +++ b/modules/mpd/mpd-class-example.php @@ -0,0 +1,144 @@ +<?php
+/*
+ * mpd-class-example.php - Example interface using mpd.class.php
+ * Version 1.2, released 05/05/2004
+ * Copyright (C) 2003-2004 Benjamin Carlisle (bcarlisle@24oz.com)
+ * http://mpd.24oz.com/ | http://www.musicpd.org/
+ *
+ * This program illustrates the basic commands and usage of the MPD class.
+ *
+ * *** PLEASE NOTE *** My intention in including this file is not to provide you with an
+ * out-of-the-box MPD jukebox, but instead to provide a general understanding of how I saw
+ * the class as being utilized. If you'd like to see more examples, please let me know. But
+ * this should provide you with a good starting point for your own program development.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+?>
+<HTML>
+<style type="text/css"><!-- .defaultText { font-family: Arial, Helvetica, sans-serif; font-size: 9pt; font-style: normal; font-weight: normal; color: #111111} .err { color: #DD3333 } --></style>
+<BODY class="defaultText">
+<?php
+ include('mpd.class.php');
+ $myMpd = new mpd('localhost',2100);
+
+ if ( $myMpd->connected == FALSE ) {
+ echo "Error Connecting: " . $myMpd->errStr;
+ } else {
+ switch ($_REQUEST[m]) {
+ case "add":
+ if ( is_null($myMpd->PLAdd($_REQUEST[filename])) ) echo "<SPAN CLASS=err>ERROR: " .$myMpd->errStr."</SPAN>";
+ break;
+ case "rem":
+ if ( is_null($myMpd->PLRemove($_REQUEST[id])) ) echo "<SPAN CLASS=err>ERROR: " .$myMpd->errStr."</SPAN>";
+ break;
+ case "setvol":
+ if ( is_null($myMpd->SetVolume($_REQUEST[vol])) ) echo "<SPAN CLASS=err>ERROR: " .$myMpd->errStr."</SPAN>";
+ break;
+ case "play":
+ if ( is_null($myMpd->Play()) ) echo "<SPAN CLASS=err>ERROR: " .$myMpd->errStr."</SPAN>";
+ break;
+ case "stop":
+ if ( is_null($myMpd->Stop()) ) echo "<SPAN CLASS=err>ERROR: " .$myMpd->errStr."</SPAN>";
+ break;
+ case "pause":
+ if ( is_null($myMpd->Pause()) ) echo "<SPAN CLASS=err>ERROR: " .$myMpd->errStr."</SPAN>";
+ break;
+ default:
+ break;
+ }
+?>
+<DIV ALIGN=CENTER>[ <A HREF="<?php echo $_SERVER[PHP_SELF] ?>">Refresh Page</A> ]</DIV>
+<HR>
+<B>Connected to MPD Version <?php echo $myMpd->mpd_version ?> at <?php echo $myMpd->host ?>:<?php echo $myMpd->port ?></B><BR>
+State:
+<?php
+ switch ($myMpd->state) {
+ case MPD_STATE_PLAYING: echo "MPD is Playing [<A HREF='".$_SERVER[PHP_SELF]."?m=pause'>Pause</A>] [<A HREF='".$_SERVER[PHP_SELF]."?m=stop'>Stop</A>]"; break;
+ case MPD_STATE_PAUSED: echo "MPD is Paused [<A HREF='".$_SERVER[PHP_SELF]."?m=pause'>Unpause</A>]"; break;
+ case MPD_STATE_STOPPED: echo "MPD is Stopped [<A HREF='".$_SERVER[PHP_SELF]."?m=play'>Play</A>]"; break;
+ default: echo "(Unknown State!)"; break;
+ }
+?>
+<BR>
+Volume: <?php echo $myMpd->volume ?> [ <A HREF='<?php echo $_SERVER[PHP_SELF] ?>?m=setvol&vol=0'>0</A> | <A HREF='<?php echo $_SERVER[PHP_SELF] ?>?m=setvol&vol=25'>25</A> | <A HREF='<?php echo $_SERVER[PHP_SELF] ?>?m=setvol&vol=75'>75</A> | <A HREF='<?php echo $_SERVER[PHP_SELF] ?>?m=setvol&vol=100'>100</A> ]<BR>
+Uptime: <?php echo secToTimeStr($myMpd->uptime) ?><BR>
+Playtime: <?php echo secToTimeStr($myMpd->playtime) ?><BR>
+
+<?php if ( $myMpd->state == MPD_STATE_PLAYING or $myMpd->state == MPD_STATE_PAUSED ) { ?>
+ Currently Playing: <?php echo $myMpd->playlist[$myMpd->current_track_id]['Artist']." - ".$myMpd->playlist[$myMpd->current_track_id]['Title'] ?><BR>
+ Track Position: <?php echo $myMpd->current_track_position."/".$myMpd->current_track_length." (".(round(($myMpd->current_track_position/$myMpd->current_track_length),2)*100)."%)" ?><BR>
+ Playlist Position: <?php echo ($myMpd->current_track_id+1)."/".$myMpd->playlist_count." (".(round((($myMpd->current_track_id+1)/$myMpd->playlist_count),2)*100)."%)" ?><BR>
+<?php } ?>
+<HR>
+
+<B>Playlist - Total: <?php echo $myMpd->playlist_count ?> tracks (Click to Remove)</B><BR>
+<?php
+ if ( is_null($myMpd->playlist) ) echo "ERROR: " .$myMpd->errStr."\n";
+ else {
+ foreach ($myMpd->playlist as $id => $entry) {
+ echo ( $id == $myMpd->current_track_id ? "<B>" : "" ) . ($id+1) . ". <A HREF='".$_SERVER[PHP_SELF]."?m=rem&id=".$id."'>".$entry['Artist']." - ".$entry['Title']."</A>".( $id == $myMpd->current_track_id ? "</B>" : "" )."<BR>\n";
+ }
+ }
+?>
+<HR>
+<B>Sample Search for the String 'U2' (Click to Add to Playlist)</B><BR>
+<?php
+ $sl = $myMpd->Search(MPD_SEARCH_ARTIST,'U2');
+ if ( is_null($sl) ) echo "ERROR: " .$myMpd->errStr."\n";
+ else {
+ foreach ($sl as $id => $entry) {
+ echo ($id+1) . ": <A HREF='".$_SERVER[PHP_SELF]."?m=add&filename=".urlencode($entry['file'])."'>".$entry['Artist']." - ".$entry['Title']."</A><BR>\n";
+ }
+ }
+ if ( count($sl) == 0 ) echo "<I>No results returned from search.</I>";
+
+
+ // Example of how you would use Bulk Add features of MPD
+ // $myarray = array();
+ // $myarray[0] = "ACDC - Thunderstruck.mp3";
+ // $myarray[1] = "ACDC - Back In Black.mp3";
+ // $myarray[2] = "ACDC - Hells Bells.mp3";
+
+ // if ( is_null($myMpd->PLAddBulk($myarray)) ) echo "ERROR: ".$myMpd->errStr."\n";
+?>
+<HR>
+<B>Artist List</B><BR>
+<?php
+ if ( is_null($ar = $myMpd->GetArtists()) ) echo "ERROR: " .$myMpd->errStr."\n";
+ else {
+ while(list($key, $value) = each($ar) ) {
+ echo ($key+1) . ". " . $value . "<BR>";
+ }
+ }
+
+ $myMpd->Disconnect();
+ }
+
+ // ---------------------------------------------------------------------------------
+ // Used to make number of seconds perty.
+ function secToTimeStr($secs) {
+ $days = ($secs%604800)/86400;
+ $hours = (($secs%604800)%86400)/3600;
+ $minutes = ((($secs%604800)%86400)%3600)/60;
+ $seconds = (((($secs%604800)%86400)%3600)%60);
+ if (round($days)) $timestring .= round($days)."d ";
+ if (round($hours)) $timestring .= round($hours)."h ";
+ if (round($minutes)) $timestring .= round($minutes)."m";
+ if (!round($minutes)&&!round($hours)&&!round($days)) $timestring.=" ".round($seconds)."s";
+ return $timestring;
+ } // --------------------------------------------------------------------------------
+?>
+</BODY></HTML>
diff --git a/modules/mpd/mpd.class.php b/modules/mpd/mpd.class.php new file mode 100644 index 00000000..ed324ba3 --- /dev/null +++ b/modules/mpd/mpd.class.php @@ -0,0 +1,968 @@ +<?php
+/*
+ * mpd.class.php - PHP Object Interface to the MPD Music Player Daemon
+ * Version 1.2, Released 05/05/2004
+ * Copyright (C) 2003-2004 Benjamin Carlisle (bcarlisle@24oz.com)
+ * http://mpd.24oz.com/ | http://www.musicpd.org/
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+// Create common command definitions for MPD to use
+define("MPD_CMD_STATUS", "status");
+define("MPD_CMD_STATISTICS", "stats");
+define("MPD_CMD_VOLUME", "volume");
+define("MPD_CMD_SETVOL", "setvol");
+define("MPD_CMD_PLAY", "play");
+define("MPD_CMD_STOP", "stop");
+define("MPD_CMD_PAUSE", "pause");
+define("MPD_CMD_NEXT", "next");
+define("MPD_CMD_PREV", "previous");
+define("MPD_CMD_PLLIST", "playlistinfo");
+define("MPD_CMD_PLADD", "add");
+define("MPD_CMD_PLREMOVE", "delete");
+define("MPD_CMD_PLCLEAR", "clear");
+define("MPD_CMD_PLSHUFFLE", "shuffle");
+define("MPD_CMD_PLLOAD", "load");
+define("MPD_CMD_PLSAVE", "save");
+define("MPD_CMD_KILL", "kill");
+define("MPD_CMD_REFRESH", "update");
+define("MPD_CMD_REPEAT", "repeat");
+define("MPD_CMD_LSDIR", "lsinfo");
+define("MPD_CMD_SEARCH", "search");
+define("MPD_CMD_START_BULK", "command_list_begin");
+define("MPD_CMD_END_BULK", "command_list_end");
+define("MPD_CMD_FIND", "find");
+define("MPD_CMD_RANDOM", "random");
+define("MPD_CMD_SEEK", "seek");
+define("MPD_CMD_PLSWAPTRACK", "swap");
+define("MPD_CMD_PLMOVETRACK", "move");
+define("MPD_CMD_PASSWORD", "password");
+define("MPD_CMD_TABLE", "list");
+
+// Predefined MPD Response messages
+define("MPD_RESPONSE_ERR", "ACK");
+define("MPD_RESPONSE_OK", "OK");
+
+// MPD State Constants
+define("MPD_STATE_PLAYING", "play");
+define("MPD_STATE_STOPPED", "stop");
+define("MPD_STATE_PAUSED", "pause");
+
+// MPD Searching Constants
+define("MPD_SEARCH_ARTIST", "artist");
+define("MPD_SEARCH_TITLE", "title");
+define("MPD_SEARCH_ALBUM", "album");
+
+// MPD Cache Tables
+define("MPD_TBL_ARTIST","artist");
+define("MPD_TBL_ALBUM","album");
+
+class mpd {
+ // TCP/Connection variables
+ var $host;
+ var $port;
+ var $password;
+
+ var $mpd_sock = NULL;
+ var $connected = FALSE;
+
+ // MPD Status variables
+ var $mpd_version = "(unknown)";
+
+ var $state;
+ var $current_track_position;
+ var $current_track_length;
+ var $current_track_id;
+ var $volume;
+ var $repeat;
+ var $random;
+
+ var $uptime;
+ var $playtime;
+ var $db_last_refreshed;
+ var $num_songs_played;
+ var $playlist_count;
+
+ var $num_artists;
+ var $num_albums;
+ var $num_songs;
+
+ var $playlist = array();
+
+ // Misc Other Vars
+ var $mpd_class_version = "1.2";
+
+ var $debugging = FALSE; // Set to TRUE to turn extended debugging on.
+ var $errStr = ""; // Used for maintaining information about the last error message
+
+ var $command_queue; // The list of commands for bulk command sending
+
+ // =================== BEGIN OBJECT METHODS ================
+
+ /* mpd() : Constructor
+ *
+ * Builds the MPD object, connects to the server, and refreshes all local object properties.
+ */
+ function mpd($srv,$port,$pwd = NULL) {
+ $this->host = $srv;
+ $this->port = $port;
+ $this->password = $pwd;
+
+ $resp = $this->Connect();
+ if ( is_null($resp) ) {
+ $this->errStr = "Could not connect";
+ return;
+ } else {
+ list ( $this->mpd_version ) = sscanf($resp, MPD_RESPONSE_OK . " MPD %s\n");
+ if ( ! is_null($pwd) ) {
+ if ( is_null($this->SendCommand(MPD_CMD_PASSWORD,$pwd)) ) {
+ $this->connected = FALSE;
+ return; // bad password or command
+ }
+ if ( is_null($this->RefreshInfo()) ) { // no read access -- might as well be disconnected!
+ $this->connected = FALSE;
+ $this->errStr = "Password supplied does not have read access";
+ return;
+ }
+ } else {
+ if ( is_null($this->RefreshInfo()) ) { // no read access -- might as well be disconnected!
+ $this->connected = FALSE;
+ $this->errStr = "Password required to access server";
+ return;
+ }
+ }
+ }
+ }
+
+ /* Connect()
+ *
+ * Connects to the MPD server.
+ *
+ * NOTE: This is called automatically upon object instantiation; you should not need to call this directly.
+ */
+ function Connect() {
+ if ( $this->debugging ) echo "mpd->Connect() / host: ".$this->host.", port: ".$this->port."\n";
+ $this->mpd_sock = fsockopen($this->host,$this->port,$errNo,$errStr,10);
+ if (!$this->mpd_sock) {
+ $this->errStr = "Socket Error: $errStr ($errNo)";
+ return NULL;
+ } else {
+ while(!feof($this->mpd_sock)) {
+ $response = fgets($this->mpd_sock,1024);
+ if (strncmp(MPD_RESPONSE_OK,$response,strlen(MPD_RESPONSE_OK)) == 0) {
+ $this->connected = TRUE;
+ return $response;
+ break;
+ }
+ if (strncmp(MPD_RESPONSE_ERR,$response,strlen(MPD_RESPONSE_ERR)) == 0) {
+ $this->errStr = "Server responded with: $response";
+ return NULL;
+ }
+ }
+ // Generic response
+ $this->errStr = "Connection not available";
+ return NULL;
+ }
+ }
+
+ /* SendCommand()
+ *
+ * Sends a generic command to the MPD server. Several command constants are pre-defined for
+ * use (see MPD_CMD_* constant definitions above).
+ */
+ function SendCommand($cmdStr,$arg1 = "",$arg2 = "") {
+ if ( $this->debugging ) echo "mpd->SendCommand() / cmd: ".$cmdStr.", args: ".$arg1." ".$arg2."\n";
+ if ( ! $this->connected ) {
+ echo "mpd->SendCommand() / Error: Not connected\n";
+ } else {
+ // Clear out the error String
+ $this->errStr = "";
+ $respStr = "";
+
+ // Check the command compatibility:
+ if ( ! $this->_checkCompatibility($cmdStr) ) {
+ return NULL;
+ }
+
+ if (strlen($arg1) > 0) $cmdStr .= " \"$arg1\"";
+ if (strlen($arg2) > 0) $cmdStr .= " \"$arg2\"";
+ fputs($this->mpd_sock,"$cmdStr\n");
+ while(!feof($this->mpd_sock)) {
+ $response = fgets($this->mpd_sock,1024);
+
+ // An OK signals the end of transmission -- we'll ignore it
+ if (strncmp(MPD_RESPONSE_OK,$response,strlen(MPD_RESPONSE_OK)) == 0) {
+ break;
+ }
+
+ // An ERR signals the end of transmission with an error! Let's grab the single-line message.
+ if (strncmp(MPD_RESPONSE_ERR,$response,strlen(MPD_RESPONSE_ERR)) == 0) {
+ list ( $junk, $errTmp ) = split(MPD_RESPONSE_ERR . " ",$response );
+ $this->errStr = strtok($errTmp,"\n");
+ }
+
+ if ( strlen($this->errStr) > 0 ) {
+ return NULL;
+ }
+
+ // Build the response string
+ $respStr .= $response;
+ }
+ if ( $this->debugging ) echo "mpd->SendCommand() / response: '".$respStr."'\n";
+ }
+ return $respStr;
+ }
+
+ /* QueueCommand()
+ *
+ * Queues a generic command for later sending to the MPD server. The CommandQueue can hold
+ * as many commands as needed, and are sent all at once, in the order they are queued, using
+ * the SendCommandQueue() method. The syntax for queueing commands is identical to SendCommand().
+ */
+ function QueueCommand($cmdStr,$arg1 = "",$arg2 = "") {
+ if ( $this->debugging ) echo "mpd->QueueCommand() / cmd: ".$cmdStr.", args: ".$arg1." ".$arg2."\n";
+ if ( ! $this->connected ) {
+ echo "mpd->QueueCommand() / Error: Not connected\n";
+ return NULL;
+ } else {
+ if ( strlen($this->command_queue) == 0 ) {
+ $this->command_queue = MPD_CMD_START_BULK . "\n";
+ }
+ if (strlen($arg1) > 0) $cmdStr .= " \"$arg1\"";
+ if (strlen($arg2) > 0) $cmdStr .= " \"$arg2\"";
+
+ $this->command_queue .= $cmdStr ."\n";
+
+ if ( $this->debugging ) echo "mpd->QueueCommand() / return\n";
+ }
+ return TRUE;
+ }
+
+ /* SendCommandQueue()
+ *
+ * Sends all commands in the Command Queue to the MPD server. See also QueueCommand().
+ */
+ function SendCommandQueue() {
+ if ( $this->debugging ) echo "mpd->SendCommandQueue()\n";
+ if ( ! $this->connected ) {
+ echo "mpd->SendCommandQueue() / Error: Not connected\n";
+ return NULL;
+ } else {
+ $this->command_queue .= MPD_CMD_END_BULK . "\n";
+ if ( is_null($respStr = $this->SendCommand($this->command_queue)) ) {
+ return NULL;
+ } else {
+ $this->command_queue = NULL;
+ if ( $this->debugging ) echo "mpd->SendCommandQueue() / response: '".$respStr."'\n";
+ }
+ }
+ return $respStr;
+ }
+
+ /* AdjustVolume()
+ *
+ * Adjusts the mixer volume on the MPD by <modifier>, which can be a positive (volume increase),
+ * or negative (volume decrease) value.
+ */
+ function AdjustVolume($modifier) {
+ if ( $this->debugging ) echo "mpd->AdjustVolume()\n";
+ if ( ! is_numeric($modifier) ) {
+ $this->errStr = "AdjustVolume() : argument 1 must be a numeric value";
+ return NULL;
+ }
+
+ $this->RefreshInfo();
+ $newVol = $this->volume + $modifier;
+ $ret = $this->SetVolume($newVol);
+
+ if ( $this->debugging ) echo "mpd->AdjustVolume() / return\n";
+ return $ret;
+ }
+
+ /* SetVolume()
+ *
+ * Sets the mixer volume to <newVol>, which should be between 1 - 100.
+ */
+ function SetVolume($newVol) {
+ if ( $this->debugging ) echo "mpd->SetVolume()\n";
+ if ( ! is_numeric($newVol) ) {
+ $this->errStr = "SetVolume() : argument 1 must be a numeric value";
+ return NULL;
+ }
+
+ // Forcibly prevent out of range errors
+ if ( $newVol < 0 ) $newVol = 0;
+ if ( $newVol > 100 ) $newVol = 100;
+
+ // If we're not compatible with SETVOL, we'll try adjusting using VOLUME
+ if ( $this->_checkCompatibility(MPD_CMD_SETVOL) ) {
+ if ( ! is_null($ret = $this->SendCommand(MPD_CMD_SETVOL,$newVol))) $this->volume = $newVol;
+ } else {
+ $this->RefreshInfo(); // Get the latest volume
+ if ( is_null($this->volume) ) {
+ return NULL;
+ } else {
+ $modifier = ( $newVol - $this->volume );
+ if ( ! is_null($ret = $this->SendCommand(MPD_CMD_VOLUME,$modifier))) $this->volume = $newVol;
+ }
+ }
+
+ if ( $this->debugging ) echo "mpd->SetVolume() / return\n";
+ return $ret;
+ }
+
+ /* GetDir()
+ *
+ * Retrieves a database directory listing of the <dir> directory and places the results into
+ * a multidimensional array. If no directory is specified, the directory listing is at the
+ * base of the MPD music path.
+ */
+ function GetDir($dir = "") {
+ if ( $this->debugging ) echo "mpd->GetDir()\n";
+ $resp = $this->SendCommand(MPD_CMD_LSDIR,$dir);
+ $dirlist = $this->_parseFileListResponse($resp);
+ if ( $this->debugging ) echo "mpd->GetDir() / return ".print_r($dirlist)."\n";
+ return $dirlist;
+ }
+
+ /* PLAdd()
+ *
+ * Adds each track listed in a single-dimensional <trackArray>, which contains filenames
+ * of tracks to add, to the end of the playlist. This is used to add many, many tracks to
+ * the playlist in one swoop.
+ */
+ function PLAddBulk($trackArray) {
+ if ( $this->debugging ) echo "mpd->PLAddBulk()\n";
+ $num_files = count($trackArray);
+ for ( $i = 0; $i < $num_files; $i++ ) {
+ $this->QueueCommand(MPD_CMD_PLADD,$trackArray[$i]);
+ }
+ $resp = $this->SendCommandQueue();
+ $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->PLAddBulk() / return\n";
+ return $resp;
+ }
+
+ /* PLAdd()
+ *
+ * Adds the file <file> to the end of the playlist. <file> must be a track in the MPD database.
+ */
+ function PLAdd($fileName) {
+ if ( $this->debugging ) echo "mpd->PLAdd()\n";
+ if ( ! is_null($resp = $this->SendCommand(MPD_CMD_PLADD,$fileName))) $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->PLAdd() / return\n";
+ return $resp;
+ }
+
+ /* PLMoveTrack()
+ *
+ * Moves track number <origPos> to position <newPos> in the playlist. This is used to reorder
+ * the songs in the playlist.
+ */
+ function PLMoveTrack($origPos, $newPos) {
+ if ( $this->debugging ) echo "mpd->PLMoveTrack()\n";
+ if ( ! is_numeric($origPos) ) {
+ $this->errStr = "PLMoveTrack(): argument 1 must be numeric";
+ return NULL;
+ }
+ if ( $origPos < 0 or $origPos > $this->playlist_count ) {
+ $this->errStr = "PLMoveTrack(): argument 1 out of range";
+ return NULL;
+ }
+ if ( $newPos < 0 ) $newPos = 0;
+ if ( $newPos > $this->playlist_count ) $newPos = $this->playlist_count;
+
+ if ( ! is_null($resp = $this->SendCommand(MPD_CMD_PLMOVETRACK,$origPos,$newPos))) $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->PLMoveTrack() / return\n";
+ return $resp;
+ }
+
+ /* PLShuffle()
+ *
+ * Randomly reorders the songs in the playlist.
+ */
+ function PLShuffle() {
+ if ( $this->debugging ) echo "mpd->PLShuffle()\n";
+ if ( ! is_null($resp = $this->SendCommand(MPD_CMD_PLSHUFFLE))) $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->PLShuffle() / return\n";
+ return $resp;
+ }
+
+ /* PLLoad()
+ *
+ * Retrieves the playlist from <file>.m3u and loads it into the current playlist.
+ */
+ function PLLoad($file) {
+ if ( $this->debugging ) echo "mpd->PLLoad()\n";
+ if ( ! is_null($resp = $this->SendCommand(MPD_CMD_PLLOAD,$file))) $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->PLLoad() / return\n";
+ return $resp;
+ }
+
+ /* PLSave()
+ *
+ * Saves the playlist to <file>.m3u for later retrieval. The file is saved in the MPD playlist
+ * directory.
+ */
+ function PLSave($file) {
+ if ( $this->debugging ) echo "mpd->PLSave()\n";
+ $resp = $this->SendCommand(MPD_CMD_PLSAVE,$file);
+ if ( $this->debugging ) echo "mpd->PLSave() / return\n";
+ return $resp;
+ }
+
+ /* PLClear()
+ *
+ * Empties the playlist.
+ */
+ function PLClear() {
+ if ( $this->debugging ) echo "mpd->PLClear()\n";
+ if ( ! is_null($resp = $this->SendCommand(MPD_CMD_PLCLEAR))) $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->PLClear() / return\n";
+ return $resp;
+ }
+
+ /* PLRemove()
+ *
+ * Removes track <id> from the playlist.
+ */
+ function PLRemove($id) {
+ if ( $this->debugging ) echo "mpd->PLRemove()\n";
+ if ( ! is_numeric($id) ) {
+ $this->errStr = "PLRemove() : argument 1 must be a numeric value";
+ return NULL;
+ }
+ if ( ! is_null($resp = $this->SendCommand(MPD_CMD_PLREMOVE,$id))) $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->PLRemove() / return\n";
+ return $resp;
+ }
+
+ /* SetRepeat()
+ *
+ * Enables 'loop' mode -- tells MPD continually loop the playlist. The <repVal> parameter
+ * is either 1 (on) or 0 (off).
+ */
+ function SetRepeat($repVal) {
+ if ( $this->debugging ) echo "mpd->SetRepeat()\n";
+ $rpt = $this->SendCommand(MPD_CMD_REPEAT,$repVal);
+ $this->repeat = $repVal;
+ if ( $this->debugging ) echo "mpd->SetRepeat() / return\n";
+ return $rpt;
+ }
+
+ /* SetRandom()
+ *
+ * Enables 'randomize' mode -- tells MPD to play songs in the playlist in random order. The
+ * <rndVal> parameter is either 1 (on) or 0 (off).
+ */
+ function SetRandom($rndVal) {
+ if ( $this->debugging ) echo "mpd->SetRandom()\n";
+ $resp = $this->SendCommand(MPD_CMD_RANDOM,$rndVal);
+ $this->random = $rndVal;
+ if ( $this->debugging ) echo "mpd->SetRandom() / return\n";
+ return $resp;
+ }
+
+ /* Shutdown()
+ *
+ * Shuts down the MPD server (aka sends the KILL command). This closes the current connection,
+ * and prevents future communication with the server.
+ */
+ function Shutdown() {
+ if ( $this->debugging ) echo "mpd->Shutdown()\n";
+ $resp = $this->SendCommand(MPD_CMD_SHUTDOWN);
+
+ $this->connected = FALSE;
+ unset($this->mpd_version);
+ unset($this->errStr);
+ unset($this->mpd_sock);
+
+ if ( $this->debugging ) echo "mpd->Shutdown() / return\n";
+ return $resp;
+ }
+
+ /* DBRefresh()
+ *
+ * Tells MPD to rescan the music directory for new tracks, and to refresh the Database. Tracks
+ * cannot be played unless they are in the MPD database.
+ */
+ function DBRefresh() {
+ if ( $this->debugging ) echo "mpd->DBRefresh()\n";
+ $resp = $this->SendCommand(MPD_CMD_REFRESH);
+
+ // Update local variables
+ $this->RefreshInfo();
+
+ if ( $this->debugging ) echo "mpd->DBRefresh() / return\n";
+ return $resp;
+ }
+
+ /* Play()
+ *
+ * Begins playing the songs in the MPD playlist.
+ */
+ function Play() {
+ if ( $this->debugging ) echo "mpd->Play()\n";
+ if ( ! is_null($rpt = $this->SendCommand(MPD_CMD_PLAY) )) $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->Play() / return\n";
+ return $rpt;
+ }
+
+ /* Stop()
+ *
+ * Stops playing the MPD.
+ */
+ function Stop() {
+ if ( $this->debugging ) echo "mpd->Stop()\n";
+ if ( ! is_null($rpt = $this->SendCommand(MPD_CMD_STOP) )) $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->Stop() / return\n";
+ return $rpt;
+ }
+
+ /* Pause()
+ *
+ * Toggles pausing on the MPD. Calling it once will pause the player, calling it again
+ * will unpause.
+ */
+ function Pause() {
+ if ( $this->debugging ) echo "mpd->Pause()\n";
+ if ( ! is_null($rpt = $this->SendCommand(MPD_CMD_PAUSE) )) $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->Pause() / return\n";
+ return $rpt;
+ }
+
+ /* SeekTo()
+ *
+ * Skips directly to the <idx> song in the MPD playlist.
+ */
+ function SkipTo($idx) {
+ if ( $this->debugging ) echo "mpd->SkipTo()\n";
+ if ( ! is_numeric($idx) ) {
+ $this->errStr = "SkipTo() : argument 1 must be a numeric value";
+ return NULL;
+ }
+ if ( ! is_null($rpt = $this->SendCommand(MPD_CMD_PLAY,$idx))) $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->SkipTo() / return\n";
+ return $idx;
+ }
+
+ /* SeekTo()
+ *
+ * Skips directly to a given position within a track in the MPD playlist. The <pos> argument,
+ * given in seconds, is the track position to locate. The <track> argument, if supplied is
+ * the track number in the playlist. If <track> is not specified, the current track is assumed.
+ */
+ function SeekTo($pos, $track = -1) {
+ if ( $this->debugging ) echo "mpd->SeekTo()\n";
+ if ( ! is_numeric($pos) ) {
+ $this->errStr = "SeekTo() : argument 1 must be a numeric value";
+ return NULL;
+ }
+ if ( ! is_numeric($track) ) {
+ $this->errStr = "SeekTo() : argument 2 must be a numeric value";
+ return NULL;
+ }
+ if ( $track == -1 ) {
+ $track = $this->current_track_id;
+ }
+
+ if ( ! is_null($rpt = $this->SendCommand(MPD_CMD_SEEK,$track,$pos))) $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->SeekTo() / return\n";
+ return $pos;
+ }
+
+ /* Next()
+ *
+ * Skips to the next song in the MPD playlist. If not playing, returns an error.
+ */
+ function Next() {
+ if ( $this->debugging ) echo "mpd->Next()\n";
+ if ( ! is_null($rpt = $this->SendCommand(MPD_CMD_NEXT))) $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->Next() / return\n";
+ return $rpt;
+ }
+
+ /* Previous()
+ *
+ * Skips to the previous song in the MPD playlist. If not playing, returns an error.
+ */
+ function Previous() {
+ if ( $this->debugging ) echo "mpd->Previous()\n";
+ if ( ! is_null($rpt = $this->SendCommand(MPD_CMD_PREV))) $this->RefreshInfo();
+ if ( $this->debugging ) echo "mpd->Previous() / return\n";
+ return $rpt;
+ }
+
+ /* Search()
+ *
+ * Searches the MPD database. The search <type> should be one of the following:
+ * MPD_SEARCH_ARTIST, MPD_SEARCH_TITLE, MPD_SEARCH_ALBUM
+ * The search <string> is a case-insensitive locator string. Anything that contains
+ * <string> will be returned in the results.
+ */
+ function Search($type,$string) {
+ if ( $this->debugging ) echo "mpd->Search()\n";
+ if ( $type != MPD_SEARCH_ARTIST and
+ $type != MPD_SEARCH_ALBUM and
+ $type != MPD_SEARCH_TITLE ) {
+ $this->errStr = "mpd->Search(): invalid search type";
+ return NULL;
+ } else {
+ if ( is_null($resp = $this->SendCommand(MPD_CMD_SEARCH,$type,$string))) return NULL;
+ $searchlist = $this->_parseFileListResponse($resp);
+ }
+ if ( $this->debugging ) echo "mpd->Search() / return ".print_r($searchlist)."\n";
+ return $searchlist;
+ }
+
+ /* Find()
+ *
+ * Find() looks for exact matches in the MPD database. The find <type> should be one of
+ * the following:
+ * MPD_SEARCH_ARTIST, MPD_SEARCH_TITLE, MPD_SEARCH_ALBUM
+ * The find <string> is a case-insensitive locator string. Anything that exactly matches
+ * <string> will be returned in the results.
+ */
+ function Find($type,$string) {
+ if ( $this->debugging ) echo "mpd->Find()\n";
+ if ( $type != MPD_SEARCH_ARTIST and
+ $type != MPD_SEARCH_ALBUM and
+ $type != MPD_SEARCH_TITLE ) {
+ $this->errStr = "mpd->Find(): invalid find type";
+ return NULL;
+ } else {
+ if ( is_null($resp = $this->SendCommand(MPD_CMD_FIND,$type,$string))) return NULL;
+ $searchlist = $this->_parseFileListResponse($resp);
+ }
+ if ( $this->debugging ) echo "mpd->Find() / return ".print_r($searchlist)."\n";
+ return $searchlist;
+ }
+
+ /* Disconnect()
+ *
+ * Closes the connection to the MPD server.
+ */
+ function Disconnect() {
+ if ( $this->debugging ) echo "mpd->Disconnect()\n";
+ fclose($this->mpd_sock);
+
+ $this->connected = FALSE;
+ unset($this->mpd_version);
+ unset($this->errStr);
+ unset($this->mpd_sock);
+ }
+
+ /* GetArtists()
+ *
+ * Returns the list of artists in the database in an associative array.
+ */
+ function GetArtists() {
+ if ( $this->debugging ) echo "mpd->GetArtists()\n";
+ if ( is_null($resp = $this->SendCommand(MPD_CMD_TABLE, MPD_TBL_ARTIST))) return NULL;
+ $arArray = array();
+
+ $arLine = strtok($resp,"\n");
+ $arName = "";
+ $arCounter = -1;
+ while ( $arLine ) {
+ list ( $element, $value ) = split(": ",$arLine);
+ if ( $element == "Artist" ) {
+ $arCounter++;
+ $arName = $value;
+ $arArray[$arCounter] = $arName;
+ }
+
+ $arLine = strtok("\n");
+ }
+ if ( $this->debugging ) echo "mpd->GetArtists()\n";
+ return $arArray;
+ }
+
+ /* GetAlbums()
+ *
+ * Returns the list of albums in the database in an associative array. Optional parameter
+ * is an artist Name which will list all albums by a particular artist.
+ */
+ function GetAlbums( $ar = NULL) {
+ if ( $this->debugging ) echo "mpd->GetAlbums()\n";
+ if ( is_null($resp = $this->SendCommand(MPD_CMD_TABLE, MPD_TBL_ALBUM, $ar ))) return NULL;
+ $alArray = array();
+
+ $alLine = strtok($resp,"\n");
+ $alName = "";
+ $alCounter = -1;
+ while ( $alLine ) {
+ list ( $element, $value ) = split(": ",$alLine);
+ if ( $element == "Album" ) {
+ $alCounter++;
+ $alName = $value;
+ $alArray[$alCounter] = $alName;
+ }
+
+ $alLine = strtok("\n");
+ }
+ if ( $this->debugging ) echo "mpd->GetAlbums()\n";
+ return $alArray;
+ }
+
+ //*******************************************************************************//
+ //***************************** INTERNAL FUNCTIONS ******************************//
+ //*******************************************************************************//
+
+ /* _computeVersionValue()
+ *
+ * Computes a compatibility value from a version string
+ *
+ */
+ function _computeVersionValue($verStr) {
+ list ($ver_maj, $ver_min, $ver_rel ) = split("\.",$verStr);
+ return ( 100 * $ver_maj ) + ( 10 * $ver_min ) + ( $ver_rel );
+ }
+
+ /* _checkCompatibility()
+ *
+ * Check MPD command compatibility against our internal table. If there is no version
+ * listed in the table, allow it by default.
+ */
+ function _checkCompatibility($cmd) {
+ // Check minimum compatibility
+ $req_ver_low = $this->COMPATIBILITY_MIN_TBL[$cmd];
+ $req_ver_hi = $this->COMPATIBILITY_MAX_TBL[$cmd];
+
+ $mpd_ver = $this->_computeVersionValue($this->mpd_version);
+
+ if ( $req_ver_low ) {
+ $req_ver = $this->_computeVersionValue($req_ver_low);
+
+ if ( $mpd_ver < $req_ver ) {
+ $this->errStr = "Command '$cmd' is not compatible with this version of MPD, version ".$req_ver_low." required";
+ return FALSE;
+ }
+ }
+
+ // Check maxmum compatibility -- this will check for deprecations
+ if ( $req_ver_hi ) {
+ $req_ver = $this->_computeVersionValue($req_ver_hi);
+
+ if ( $mpd_ver > $req_ver ) {
+ $this->errStr = "Command '$cmd' has been deprecated in this version of MPD.";
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ }
+
+ /* _parseFileListResponse()
+ *
+ * Builds a multidimensional array with MPD response lists.
+ *
+ * NOTE: This function is used internally within the class. It should not be used.
+ */
+ function _parseFileListResponse($resp) {
+ if ( is_null($resp) ) {
+ return NULL;
+ } else {
+ $plistArray = array();
+ $plistLine = strtok($resp,"\n");
+ $plistFile = "";
+ $plCounter = -1;
+ while ( $plistLine ) {
+ list ( $element, $value ) = split(": ",$plistLine);
+ if ( $element == "file" ) {
+ $plCounter++;
+ $plistFile = $value;
+ $plistArray[$plCounter]["file"] = $plistFile;
+ } else {
+ $plistArray[$plCounter][$element] = $value;
+ }
+
+ $plistLine = strtok("\n");
+ }
+ }
+ return $plistArray;
+ }
+
+ /* RefreshInfo()
+ *
+ * Updates all class properties with the values from the MPD server.
+ *
+ * NOTE: This function is automatically called upon Connect() as of v1.1.
+ */
+ function RefreshInfo() {
+ // Get the Server Statistics
+ $statStr = $this->SendCommand(MPD_CMD_STATISTICS);
+ if ( !$statStr ) {
+ return NULL;
+ } else {
+ $stats = array();
+ $statLine = strtok($statStr,"\n");
+ while ( $statLine ) {
+ list ( $element, $value ) = split(": ",$statLine);
+ $stats[$element] = $value;
+ $statLine = strtok("\n");
+ }
+ }
+
+ // Get the Server Status
+ $statusStr = $this->SendCommand(MPD_CMD_STATUS);
+ if ( ! $statusStr ) {
+ return NULL;
+ } else {
+ $status = array();
+ $statusLine = strtok($statusStr,"\n");
+ while ( $statusLine ) {
+ list ( $element, $value ) = split(": ",$statusLine);
+ $status[$element] = $value;
+ $statusLine = strtok("\n");
+ }
+ }
+
+ // Get the Playlist
+ $plStr = $this->SendCommand(MPD_CMD_PLLIST);
+ $this->playlist = $this->_parseFileListResponse($plStr);
+ $this->playlist_count = count($this->playlist);
+
+ // Set Misc Other Variables
+ $this->state = $status['state'];
+ if ( ($this->state == MPD_STATE_PLAYING) || ($this->state == MPD_STATE_PAUSED) ) {
+ $this->current_track_id = $status['song'];
+ list ($this->current_track_position, $this->current_track_length ) = split(":",$status['time']);
+ } else {
+ $this->current_track_id = -1;
+ $this->current_track_position = -1;
+ $this->current_track_length = -1;
+ }
+
+ $this->repeat = $status['repeat'];
+ $this->random = $status['random'];
+
+ $this->db_last_refreshed = $stats['db_update'];
+
+ $this->volume = $status['volume'];
+ $this->uptime = $stats['uptime'];
+ $this->playtime = $stats['playtime'];
+ $this->num_songs_played = $stats['songs_played'];
+ $this->num_artists = $stats['num_artists'];
+ $this->num_songs = $stats['num_songs'];
+ $this->num_albums = $stats['num_albums'];
+ return TRUE;
+ }
+
+ /* ------------------ DEPRECATED METHODS -------------------*/
+ /* GetStatistics()
+ *
+ * Retrieves the 'statistics' variables from the server and tosses them into an array.
+ *
+ * NOTE: This function really should not be used. Instead, use $this->[variable]. The function
+ * will most likely be deprecated in future releases.
+ */
+ function GetStatistics() {
+ if ( $this->debugging ) echo "mpd->GetStatistics()\n";
+ $stats = $this->SendCommand(MPD_CMD_STATISTICS);
+ if ( !$stats ) {
+ return NULL;
+ } else {
+ $statsArray = array();
+ $statsLine = strtok($stats,"\n");
+ while ( $statsLine ) {
+ list ( $element, $value ) = split(": ",$statsLine);
+ $statsArray[$element] = $value;
+ $statsLine = strtok("\n");
+ }
+ }
+ if ( $this->debugging ) echo "mpd->GetStatistics() / return: " . print_r($statsArray) ."\n";
+ return $statsArray;
+ }
+
+ /* GetStatus()
+ *
+ * Retrieves the 'status' variables from the server and tosses them into an array.
+ *
+ * NOTE: This function really should not be used. Instead, use $this->[variable]. The function
+ * will most likely be deprecated in future releases.
+ */
+ function GetStatus() {
+ if ( $this->debugging ) echo "mpd->GetStatus()\n";
+ $status = $this->SendCommand(MPD_CMD_STATUS);
+ if ( ! $status ) {
+ return NULL;
+ } else {
+ $statusArray = array();
+ $statusLine = strtok($status,"\n");
+ while ( $statusLine ) {
+ list ( $element, $value ) = split(": ",$statusLine);
+ $statusArray[$element] = $value;
+ $statusLine = strtok("\n");
+ }
+ }
+ if ( $this->debugging ) echo "mpd->GetStatus() / return: " . print_r($statusArray) ."\n";
+ return $statusArray;
+ }
+
+ /* GetVolume()
+ *
+ * Retrieves the mixer volume from the server.
+ *
+ * NOTE: This function really should not be used. Instead, use $this->volume. The function
+ * will most likely be deprecated in future releases.
+ */
+ function GetVolume() {
+ if ( $this->debugging ) echo "mpd->GetVolume()\n";
+ $volLine = $this->SendCommand(MPD_CMD_STATUS);
+ if ( ! $volLine ) {
+ return NULL;
+ } else {
+ list ($vol) = sscanf($volLine,"volume: %d");
+ }
+ if ( $this->debugging ) echo "mpd->GetVolume() / return: $vol\n";
+ return $vol;
+ }
+
+ /* GetPlaylist()
+ *
+ * Retrieves the playlist from the server and tosses it into a multidimensional array.
+ *
+ * NOTE: This function really should not be used. Instead, use $this->playlist. The function
+ * will most likely be deprecated in future releases.
+ */
+ function GetPlaylist() {
+ if ( $this->debugging ) echo "mpd->GetPlaylist()\n";
+ $resp = $this->SendCommand(MPD_CMD_PLLIST);
+ $playlist = $this->_parseFileListResponse($resp);
+ if ( $this->debugging ) echo "mpd->GetPlaylist() / return ".print_r($playlist)."\n";
+ return $playlist;
+ }
+
+ /* ----------------- Command compatibility tables --------------------- */
+ var $COMPATIBILITY_MIN_TBL = array(
+ MPD_CMD_SEEK => "0.9.1" ,
+ MPD_CMD_PLMOVE => "0.9.1" ,
+ MPD_CMD_RANDOM => "0.9.1" ,
+ MPD_CMD_PLSWAPTRACK => "0.9.1" ,
+ MPD_CMD_PLMOVETRACK => "0.9.1" ,
+ MPD_CMD_PASSWORD => "0.10.0" ,
+ MPD_CMD_SETVOL => "0.10.0"
+ );
+
+ var $COMPATIBILITY_MAX_TBL = array(
+ MPD_CMD_VOLUME => "0.10.0"
+ );
+
+} // ---------------------------- end of class ------------------------------
+
+
+?>
diff --git a/modules/slimserver/slim.class.php b/modules/slimserver/slim.class.php new file mode 100755 index 00000000..4ac568b7 --- /dev/null +++ b/modules/slimserver/slim.class.php @@ -0,0 +1,163 @@ +<?php
+
+ /**
+ *
+ * Slimp3-Class
+ * for querying the slimp3 and the squeezebox players
+ *
+ * feel free to modify and use it whereever you want.
+ * would be nice if you could send me your changes,
+ *
+ * Homepage:
+ * http://trendwhores.de/slimclass.php
+ *
+ * Tobias Schlottke <tschlottke chr(64) virtualminds chr(46) de>
+ * http://www.trendwhores.de
+ *
+ * Modifications by Andreas <php chr(64) simply chr(46) nu>
+ * + Added more options to slimp3()
+ * + Added playlist() /w related options
+ * + Modified display() to get strings with spaces instead of +'s provided by urlencode
+ * + Modified _parse() to handle new options. Quick'n'dirty hack, could probably be prettier!
+ *
+ * License: GPL
+ *
+ */
+
+ class slim {
+
+ var $host = "localhost";
+ var $port = 9090;
+
+ var $_connection;
+
+ var $playerindex;
+ var $playercount;
+
+
+ function slim($host = NULL, $port = 9090) {
+
+ if ($host && $port) {
+ $this->host = $host;
+ $this->port = $port;
+ }
+
+ if (!$this->_connection = fsockopen($this->host, $this->port)) {
+ return false;
+ }
+
+ $this->playercount = $this->_psend("player count ?");
+ for($i = 0; $i < $this->playercount; $i++) {
+ $this->playerindex[$i]['name'] = $this->_psend("player name $i ?");
+ $this->playerindex[$i]['ip'] = $this->_psend("player ip $i ?");
+ $this->playerindex[$i]['address'] = $this->_psend("player address $i ?");
+ # Added some more options /andreas
+ $this->playerindex[$i]['mode'] = $this->_psend($this->playerindex[$i]["address"] . " mode ?");
+ $this->playerindex[$i]['power'] = $this->_psend($this->playerindex[$i]["address"] . " power ?");
+ $this->playerindex[$i]['volume'] = $this->_psend($this->playerindex[$i]["address"] . " mixer volume ?");
+ $this->playerindex[$i]['treble'] = $this->_psend($this->playerindex[$i]["address"] . " mixer treble ?");
+ $this->playerindex[$i]['bass'] = $this->_psend($this->playerindex[$i]["address"] . " mixer bass ?");
+ $this->playerindex[$i]['tracks'] = $this->_psend($this->playerindex[$i]["address"] . " info total songs ?");
+ $this->playerindex[$i]['albums'] = $this->_psend($this->playerindex[$i]["address"] . " info total albums ?");
+ $this->playerindex[$i]['artists'] = $this->_psend($this->playerindex[$i]["address"] . " info total artists ?");
+ $this->playerindex[$i]['genres'] = $this->_psend($this->playerindex[$i]["address"] . " info total genres ?");
+
+ }
+
+ return true;
+ }
+
+ function nowplaying($player = 0) {
+ $song = array(
+ "artist" => $this->_psend("artist $player ?"),
+ "title" => $this->_psend("title $player ?"),
+ "path" => $this->_psend("path $player ?"),
+ "duration" => $this->_psend("duration $player ?"),
+ "genre" => $this->_psend("genre $player ?"),
+ "album" => $this->_psend("album $player ?")
+ );
+ return $song;
+ }
+
+ # Added playlist() for related options /andreas
+ function playlist($player = 0) {
+ #Information related to playlist!
+ $index = $this->_psend("playlist index ?");
+ $index++;
+ $song = array(
+ "index" => $index,
+ "total" => $this->_psend("playlist tracks ?"),
+ # Spaces added to the end of the two first below, quick'n'dirty fix for parsing error... ;)
+ "nextartist" => $this->_psend("playlist artist " . $index . " "),
+ "nexttitle" => $this->_psend("playlist title " . $index . " "),
+ "shuffle" => $this->_psend("playlist shuffle ?"),
+ "repeat" => $this->_psend("playlist repeat ?")
+ );
+ return $song;
+ }
+
+
+ function display($l1, $l2, $duration = 5, $player = 0) {
+ #$this->_send("display ".urlencode($l1)." ".urlencode($l2)." ".$duration);
+ # above code gave me urlencoded strings on my displat, ie "Hello%20World", below did not... /andreas (php chr(64) simply chr(46) nu)
+ $l1 = str_replace(" ", "%20", $l1);
+ $l2 = str_replace(" ", "%20", $l2);
+ $this->_send("display ".$l1." ".$l2." ".$duration);
+ }
+
+ function cdisplay() {
+ return urldecode($this->_send("display ? ?"));
+ }
+
+ function close() {
+ $this->_send("exit");
+ return true;
+ }
+ # Modified by andreas (php chr(64) simply chr(46) nu)
+ # Don't ask why I did stuff here, don't remember ;)
+ function _parse($string, $cmd = NULL) {
+
+ if (!$cmd);
+ $cmd = $this->_lastcmd;
+
+ $quoted = preg_quote(substr($cmd, 0, -1), "\\");
+
+ if (preg_match("/^".$quoted."(.*)/i", $string, $matches)) {
+ $dec = urldecode(trim($matches[1]));
+ return $dec;
+ } elseif(preg_match("/^".substr($quoted, 0, -2)."(.*)/i", $string, $matches)) {
+ $dec = urldecode(trim($matches[1]));
+ if (substr($dec, -1, 1) == '?')
+ return substr($dec, 0, -1);
+ else
+ return $dec;
+
+ # extra parsing for cmd's where MAC address is involved. Me not good at regexps so... ;)
+ } elseif(preg_match("/^".$quoted."(.*)/i", urldecode(trim($string)), $matches)) {
+ $dec = trim($matches[1]);
+ if ($dec == "0")
+ return "play";
+ else
+ return $dec;
+ } else {
+ return "unable to parse reply: ".$string."<br>(cmd was: $cmd)";
+ }
+ }
+
+ function _send($string) {
+
+ $this->_lastcmd = $string;
+
+ if (fputs($this->_connection, $string."\n"))
+ return fgets($this->_connection);
+ else
+ return false;
+ }
+
+ function _psend($string) {
+ return $this->_parse($this->_send($string));
+ }
+
+ }
+
+?>
diff --git a/modules/xmlrpc/ChangeLog b/modules/xmlrpc/ChangeLog new file mode 100644 index 00000000..05cf571e --- /dev/null +++ b/modules/xmlrpc/ChangeLog @@ -0,0 +1,263 @@ +2003-01-12 Andres Salomon <dilinger@voxel.net> + + * released 1.0.99.2. + * Makefile: separate doc/Makefile a bit more from Makefile, + and add clean rules. + +2003-01-10 Andres Salomon <dilinger@voxel.net> + + * xmlrpc.inc: xmlrpcresp and parseResponse cleanups; variable + name renames ('xv' to 'val', for example), type checking, and + stricter default values. + * xmlrpc.inc: fix xmlrpcresp's faultcode; return -1 for FAULT + responses from the server whose faultcodes don't reflect any + errors. + +2003-01-08 Andres Salomon <dilinger@voxel.net> + + * xmlrpc.inc: rename $_xh[$parser]['ha'] to + $_xh[$parser]['headers']. + * xmlrpc.inc: fix bugs related to $_xh[$parser]['headers]; + some places treated this as an array, others as a scalar. + Treat unconditionally as an array. Also wrap header debugging + output in PRE tags. + +2002-12-17 Andres Salomon <dilinger@voxel.net> + + * released 1.0.99. + * Makefile: changed the tarball format/dist rule to a more + conventional form, as well as normal release updates. + * xmlrpc.inc: added setSSLVerifyPeer and setSSLVerifyHost; as + of curl 7.10, various certificate checks are done (by default). + The default for CURLOPT_SSL_VERIFYHOST is to ensure the common + name on the cert matches the provided hostname. This breaks a + lot of stuff, so allow users to override it. + * doc/xmlrpc_php.sgml: updated documentation accordingly. + +2002-09-06 Geoffrey T. Dairiki <dairiki@dairiki.org> + + Add support for system.multicall() to both the client + and the server. + + * testsuite.php: Add new tests 'testServerMulticall', + and 'testClientMulticall'. + + * xmlrpc.inc: Added new error messages for system.multicall(). + * xmlrpcs.inc: Added new procedure call system.multicall(). + See http://www.xmlrpc.com/discuss/msgReader$1208 for details. + + * xmlrpc.inc: Added system.multicall functionality to + xmlrpc_client. xmlrpc_client::send can now take an array of + xmlrpcmsg's as an argument. In that case it will attempt + to execute the whole array of procure calls in a single + HTTP request using system.multicall(). (If that attempt fails, + then the calls will be excuted one at a time.) The return + value will be an array of xmlrpcresp's (or 0 upon transport + failure.) + +2001-11-29 Edd Dumbill <edd@usefulinc.com> + + * xmlrpc.inc: fixed problem with processing HTTP headers that + broke any payload with more than one consecutive newline in it. + also initialise the 'ac' array member to empty string at start. + * testsuite.php: added unit test to exercise above bug + * xmlrpcs.inc: fixed uninitialized variable $plist + +2001-09-25 Edd Dumbill <edd@usefulinc.com> + + * xmlrpc.inc: applied urgent security fixes as identified by Dan + Libby + +2001-08-27 Edd Dumbill <edd@usefulinc.com> + + * xmlrpc.inc: Merged in HTTPS support from Justin Miller, with a + few additions for better traceability of failure conditions. Added + small fix from Giancarlo Pinerolo. Bumped rev to 1.0. Changed + license to BSD license. + +2001-06-15 Edd Dumbill <edd@usefulinc.com> + + * xmlrpcs.inc: Added \r into return MIME headers for server class + +2001-04-25 Edd Dumbill <edd@usefulinc.com> + + * server.php: Added interop suite of methods. + +2001-04-24 Edd Dumbill <edd@usefulinc.com> + + * testsuite.php: added in test case for string handling bug. + + * xmlrpc.inc: merged in minor fixes from G Giunta to fix + noninitialization. Created new method, getval(), which includes + experimental support for recreating nested arrays, from Giunta and + Sofer. Fixed string handling bug where characters after </string> + but before </value> weren't ignored. Added in support for native + boolean type into xmlrpc_encode (Giunta). + + * xmlrpcs.inc: updated copyright notice + +2001-01-15 Edd Dumbill <edd@usefulinc.com> + + * xmlrpc.inc: fixed bug with creation of booleans. Put checks in + to ensure that numbers were really numeric. Fixed bug with + non-escaping of dollar signs in strings. + + * testsuite.php: created test suite. + +2000-08-26 Edd Dumbill <edd@usefulinc.com> + + * xmlrpcs.inc: added xmlrpc_debugmsg() function which outputs + debug information in comments inside the return payload XML + + * xmlrpc.inc: merged in some changes from Dan Libby which fix up + whitespace handling. + + * xmlrpcs.inc: added Content-length header on response (bug from + Jan Varga <varga@utcru.sk>. This means you can no longer print + during processing + + * xmlrpc.inc: changed ereg_replace to str_replace in several + places (thanks to Dan Libby <dan@libby.com> for this). + + * xmlrpc.inc: added xmlrpc_encode() and xmlrpc_decode() from Dan + Libby--these helper routines make it easier to work in native PHP + data structures. + +2000-07-21 Edd Dumbill <edd@usefulinc.com> + + * xmlrpc.inc: added xmlrpc_client::setCredentials method to pass + in authorization information, and modified sendPayload* methods to + send this OK. Thanks to Grant Rauscher for the impetus to do this. + Also, made the client send empty <params></params> if there are no + parameters set by the user. + + * doc/xmlrpc_php.sgml: updated documentation to reflect recent + changes + + +2000-07-18 Edd Dumbill <edd@usefulinc.com> + + * server.php: added examples.invertBooleans method to server as a + useful test method for boolean values. + + * xmlrpc.inc: rearranged the way booleans are handled to fix + outstanding problems. Fixed calling addScalar() on arrays so it + works. Finally fixed backslashification issues to remove the + problem will dollar signs disappearing. + + * booltest.php: really fixed booleans this time. + +2000-06-03 Edd Dumbill <edd@usefulinc.com> + + * xmlrpcs.inc: made signature verification more useful - now + returns what it found was wrong + + * xmlrpc.inc: fixed bug with decoding dateTimes. Also fixed a bug + which meant a PHP syntax error happened when attempting to receive + empty arrays or structs. Also fixed bug with booleans always being + interpreted as 'true'. + + * server.php: Added validator1 suite of tests to test against + validator.xmlrpc.com + + +2000-05-06 Edd Dumbill <edd@usefulinc.com> + + * released 1.0b6 + + * added test.pl and test.py, Perl and Python scripts that exercise + server.php somewhat (but not a lot) + + * added extra fault condition for a non 200 OK response from the + remote server. + + * added iso8601_encode() and iso8601_decode() to give some support + for passing dates around. They translate to and from UNIX + timestamps. Updated documentation accordingly. + + * fixed string backslashification -- was previously a little + overzealous! new behavior is '\' --> '\\' and '"' --> + '\"'. Everything else gets left alone. + +2000-04-12 Edd Dumbill <edd@usefulinc.com> + + * updated and bugfixed the documentation + + * fixed base 64 encoding to only happen at serialize() time, + rather than when a base64 value is created. This fixes the double + encoding bug reported by Nicolay Mausz + <castor@flying-dog.com>. The same approach ought to be taken with + encoding XML entities in the data - this is a TODO. + + * integrated further code from Peter Kocks: used his new code for + send(), adding a second, optional, parameter which is a timeout + parameter to fsockopen() + +1999-10-11 Edd Dumbill <edd@usefulinc.com> + + * added bug fixes from Peter Kocks <peter.kocks@baygate.com> + +1999-10-10 Edd Dumbill <edd@usefulinc.com> + + * updated the documentation + +1999-10-08 Edd Dumbill <edd@usefulinc.com> + + * added system.* methods and dispatcher, plus documentation + + * fixed bug which meant request::getNumParams was returning an + incorrect value + + * added signatures into the dispatch map. This BREAKS + COMPATIBILITY with previous releases of this code + +1999-08-18 Edd Dumbill <edd@usefulinc.com> + + * made entity encoding and decoding transparent now on string + passing. + + * de-globalised the globals in the parse routines, using an + associative array to hold all parser state $_xh + + * changed default input encoding to be UTF-8 to match expectation + + * separated out parseResponse into parseResponse and + parseResponseFile so that you can call parseResponse on a string + if you have one handy + +1999-07-20 Edd Dumbill <edd@usefulinc.com> + + * Moved documentation into Docbook format + +1999-07-19 Edd Dumbill <edd@usefulinc.com> + + * Added an echo server into server.php and echotest.php, a client + which will exercise the new echo routine. + + * Added test for no valid value returned: in this case will now + throw the error "invalid payload" + + * Added serialize() method to xmlrpcresp to return a string with + the response serialized as XML + + * Added automatic encoding and decoding for base64 types + + * Added setDebug() method to client to enable HTML output + debugging in the client + +1999-07-08 Edd Dumbill <edd@usefulinc.com> + + * Improved XML parse error reporting on the server side to send it + back in a faultCode packet. expat errors now begin at 100 + +1999-07-07 Edd Dumbill <edd@usefulinc.com> + + * Changed the structmem and arraymem methods of xmlrpcval to always + return xmlrpc vals whether they referred to scalars or complex + types. + + * Added the server class and demonstrations + + * Fixed bugs in the XML parsing and reworked it + +$Id: ChangeLog,v 1.12 2003/01/13 08:34:18 dilinger Exp $ diff --git a/modules/xmlrpc/README b/modules/xmlrpc/README new file mode 100644 index 00000000..790bbbc6 --- /dev/null +++ b/modules/xmlrpc/README @@ -0,0 +1,7 @@ +HTML documentation can be found in the doc/ directory. + +Recent changes in the ChangeLog + +Use of this software is subject to the terms in doc/index.html + +The passphrase for the rsakey.pem certificate is 'test'. diff --git a/modules/xmlrpc/doc/Makefile b/modules/xmlrpc/doc/Makefile new file mode 100755 index 00000000..77a47180 --- /dev/null +++ b/modules/xmlrpc/doc/Makefile @@ -0,0 +1,17 @@ +WEB=/var/www/xmlrpc/doc + +all: index.html + +index.html: xmlrpc_php.sgml + jade -t sgml -d custom.dsl xmlrpc_php.sgml + +clean: + rm -f *.html + +install: + mkdir -p ${WEB} + cp *.html ${WEB} + +web: + mkdir -p ${WEB} + cp *.html ${WEB} diff --git a/modules/xmlrpc/doc/apidocs.html b/modules/xmlrpc/doc/apidocs.html new file mode 100644 index 00000000..6178742e --- /dev/null +++ b/modules/xmlrpc/doc/apidocs.html @@ -0,0 +1,598 @@ +<HTML +><HEAD +><TITLE +>Class documentation</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="PREVIOUS" +TITLE="The Jellyfish Book" +HREF="jellyfish.html"><LINK +REL="NEXT" +TITLE="xmlrpcmsg" +HREF="xmlrpcmsg.html"></HEAD +><BODY +CLASS="CHAPTER" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="jellyfish.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +></TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="xmlrpcmsg.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="CHAPTER" +><H1 +><A +NAME="APIDOCS" +></A +>Chapter 5. Class documentation</H1 +><DIV +CLASS="TOC" +><DL +><DT +><B +>Table of Contents</B +></DT +><DT +><A +HREF="apidocs.html#XMLRPC-CLIENT" +>xmlrpc_client</A +></DT +><DT +><A +HREF="xmlrpcmsg.html" +>xmlrpcmsg</A +></DT +><DT +><A +HREF="xmlrpcresp.html" +>xmlrpcresp</A +></DT +><DT +><A +HREF="xmlrpcval.html" +>xmlrpcval</A +></DT +><DT +><A +HREF="xmlrpc-server.html" +>xmlrpc_server</A +></DT +></DL +></DIV +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="XMLRPC-CLIENT" +></A +>xmlrpc_client</H1 +><P +>This is the basic class used to represent a client of an + XML-RPC server.</P +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="AEN174" +></A +>Creation</H2 +><P +>The constructor has the following syntax:</P +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN177" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$client=new xmlrpc_client</CODE +>($server_path, $server_hostname, $server_port);</CODE +></P +><P +></P +></DIV +><P +>Here's an example client set up to query Userland's XML-RPC + server at <SPAN +CLASS="emphasis" +><I +CLASS="EMPHASIS" +>betty.userland.com</I +></SPAN +>:</P +><PRE +CLASS="PROGRAMLISTING" +>$client=new xmlrpc_client("/RPC2", "betty.userland.com", 80);</PRE +><P +>The <TT +CLASS="PARAMETER" +><I +>server_port</I +></TT +> parameter is + optional, and if omitted will default to 80 when using + HTTP and 443 when using HTTPS (see the "send" method below.)</P +></DIV +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="AEN191" +></A +>Methods</H2 +><P +>This class supports the following methods.</P +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="XMLRPC-CLIENT-SEND" +></A +>send</H3 +><P +>This method takes the form:</P +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN197" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$response=$client->send</CODE +>($xmlrpc_message, $timeout, $server_method);</CODE +></P +><P +></P +></DIV +><P +>Where <TT +CLASS="PARAMETER" +><I +>$xmlrpc_message</I +></TT +> is an + instance of <TT +CLASS="CLASSNAME" +>xmlrpcmsg</TT +> (see <A +HREF="xmlrpcmsg.html" +>xmlrpcmsg</A +>), and + <TT +CLASS="PARAMETER" +><I +>$response</I +></TT +> is an + instance of <TT +CLASS="CLASSNAME" +>xmlrpcresp</TT +> (see <A +HREF="xmlrpcresp.html" +>xmlrpcresp</A +>).</P +><P +>The <TT +CLASS="PARAMETER" +><I +>$timeout</I +></TT +> is optional, and + will be set to <TT +CLASS="LITERAL" +>0</TT +> (wait forever) if + omitted. This timeout value is passed to + <TT +CLASS="FUNCTION" +>fsockopen()</TT +>.</P +><P +>The <TT +CLASS="PARAMETER" +><I +>server_method</I +></TT +> parameter is + optional, and if omitted will default to 'http'. The only + other valid value is 'https', which will use an SSL HTTP + connection to connect to the remote server. Note that your + PHP must have the "curl" extensions compiled in in order to + use this feature. Note that when using SSL you should + normally set your port number to 443, unless the SSL server + you are contacting runs at any other port.</P +><DIV +CLASS="WARNING" +><P +></P +><TABLE +CLASS="WARNING" +BORDER="1" +WIDTH="100%" +><TR +><TD +ALIGN="CENTER" +><B +>Warning</B +></TD +></TR +><TR +><TD +ALIGN="LEFT" +><P +>PHP 4.0.2 or greater is required for SSL + functionality. + PHP 4.0.6 has a bug which prevents SSL + working.</P +></TD +></TR +></TABLE +></DIV +><P +>If the value of <TT +CLASS="PARAMETER" +><I +>$response</I +></TT +> is + <TT +CLASS="LITERAL" +>0</TT +> rather than an + <TT +CLASS="CLASSNAME" +>xmlrpcresp</TT +> object, then this + signifies an I/O error has occured. You can find out what + the I/O error was from the values + <TT +CLASS="FUNCTION" +>$client->errno</TT +> and + <TT +CLASS="FUNCTION" +>$client->errstring</TT +>. + </P +><P +>In addition to low-level errors, the XML-RPC server you + were querying may return an error in the + <TT +CLASS="CLASSNAME" +>xmlrpcresp</TT +> object. See <A +HREF="xmlrpcresp.html" +>xmlrpcresp</A +> for details of + how to handle these errors. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN230" +></A +>setCredentials</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN232" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$client->setCredentials</CODE +>($username, $password);</CODE +></P +><P +></P +></DIV +><P +>This method sets the username and password for authorizing the + client to a server. With the default (HTTP) transport, this + information is used for HTTP Basic authorization. + + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN240" +></A +>setCertificate</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN242" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$client->setCertificate</CODE +>($certificate, $passphrase);</CODE +></P +><P +></P +></DIV +><P +>This method sets the optional certificate and passphrase + used in SSL-enabled communication with a remote server + (when the <TT +CLASS="PARAMETER" +><I +>server_method</I +></TT +> is set to + 'https' in the client's construction). + </P +><P +>The <TT +CLASS="PARAMETER" +><I +>certificate</I +></TT +> parameter must + be the filename of a PEM formatted certificate. The + <TT +CLASS="PARAMETER" +><I +>passphrase</I +></TT +> parameter must contain + the password required to use the certificate.</P +><P +>This requires the "curl" extensions to be compiled + into your installation of PHP.</P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN255" +></A +>setSSLVerifyPeer</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN257" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$client->setSSLVerifyPeer</CODE +>($i);</CODE +></P +><P +></P +></DIV +><P +>This method defines whether connections made to XMLRPC + backends via HTTPS should verify the remote host's SSL + certificate, and cause the connection to fail if the cert + verification fails. $i should be a boolean value. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN263" +></A +>setSSLVerifyHost</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN265" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$client->setSSLVerifyHost</CODE +>($i);</CODE +></P +><P +></P +></DIV +><P +>This method defines whether connections made to XMLRPC + backends via HTTPS should verify the remote host's SSL + certificate's common name (CN). By default, only the existence + of a CN is checked. $i should be an integer value; 0 to not + check the CN at all, 1 to merely check for its existence, and + 2 to check that the CN on the certificate matches the hostname + that is being connected to. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN271" +></A +>setDebug</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN273" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$client->setDebug</CODE +>($debugOn);</CODE +></P +><P +></P +></DIV +><P +><TT +CLASS="PARAMETER" +><I +>$debugOn</I +></TT +> is either + <TT +CLASS="LITERAL" +>0</TT +> or <TT +CLASS="LITERAL" +>1</TT +> depending on + whether you require the client to print debugging + information to the browser. The default is not to output + this information.</P +><P +> The debugging information includes the raw data returned + from the XML-RPC server it was querying, and the PHP value + the client attempts to create to represent the value + returned by the server. This option can be very useful when + debugging servers as it allows you to see exactly what the + server returns. + </P +></DIV +></DIV +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="jellyfish.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="xmlrpcmsg.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>The Jellyfish Book</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +> </TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>xmlrpcmsg</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/arrayuse.html b/modules/xmlrpc/doc/arrayuse.html new file mode 100644 index 00000000..e8011ed4 --- /dev/null +++ b/modules/xmlrpc/doc/arrayuse.html @@ -0,0 +1,253 @@ +<HTML +><HEAD +><TITLE +>Easy use with PHP arrays</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="UP" +TITLE="Helper functions" +HREF="helpers.html"><LINK +REL="PREVIOUS" +TITLE="Helper functions" +HREF="helpers.html"><LINK +REL="NEXT" +TITLE="Debugging aids" +HREF="debugging.html"></HEAD +><BODY +CLASS="SECT1" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="helpers.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +>Chapter 6. Helper functions</TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="debugging.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="ARRAYUSE" +></A +>Easy use with PHP arrays</H1 +><P +>Dan Libby was kind enough to contribute two helper functions + that make it easier to translate to and from PHP arrays. This + makes it easier to deal with complex structures. At the moment + support is limited to <SPAN +CLASS="TYPE" +>int</SPAN +>, <SPAN +CLASS="TYPE" +>double</SPAN +>, + <SPAN +CLASS="TYPE" +>string</SPAN +>, <SPAN +CLASS="TYPE" +>array</SPAN +> and <SPAN +CLASS="TYPE" +>struct</SPAN +> + datatypes; note also that all PHP arrays are encoded as structs + due to PHP not being able to tell the difference between a hash + and a linear array.</P +><P +>These functions reside in <TT +CLASS="FILENAME" +>xmlrpc.inc</TT +>.</P +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="XMLRPCDECODE" +></A +>xmlrpc_decode</H2 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN791" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$arr=xmlrpc_decode</CODE +>($xmlrpc_val);</CODE +></P +><P +></P +></DIV +><P +> Returns a PHP array stuffed with the values found in the + <SPAN +CLASS="TYPE" +>xmlrpcval</SPAN +> <TT +CLASS="PARAMETER" +><I +>$xmlrpc_val</I +></TT +>, + translated into native PHP types. + </P +></DIV +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="XMLRPCENCODE" +></A +>xmlrpc_encode</H2 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN801" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$xmlrpc_val=xmlrpc_encode</CODE +>($phpval);</CODE +></P +><P +></P +></DIV +><P +> Returns an <SPAN +CLASS="TYPE" +>xmlrpcval</SPAN +> populated with the PHP + values in <TT +CLASS="PARAMETER" +><I +>$phpval</I +></TT +>. Works recursively on + arrays and structs. Note that there's no support for non-base + types like base-64 values or date-times. + </P +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="helpers.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="debugging.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>Helper functions</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="helpers.html" +ACCESSKEY="U" +>Up</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>Debugging aids</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/bugs.html b/modules/xmlrpc/doc/bugs.html new file mode 100644 index 00000000..be925b65 --- /dev/null +++ b/modules/xmlrpc/doc/bugs.html @@ -0,0 +1,159 @@ +<HTML +><HEAD +><TITLE +>Bugs</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="PREVIOUS" +TITLE="Files in the distribution" +HREF="manifest.html"><LINK +REL="NEXT" +TITLE="Support" +HREF="support.html"></HEAD +><BODY +CLASS="CHAPTER" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="manifest.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +></TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="support.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="CHAPTER" +><H1 +><A +NAME="BUGS" +></A +>Chapter 3. Bugs</H1 +><P +>This is a bare framework. The "nice" bits haven't been put in + yet. Specifically, no HTTP response checking is performed, and no type + validation or coercion has been put in. PHP being a loosely-typed + language, this is going to have to be done explicitly.</P +><P +>dateTime.iso8601 is supported opaquely. It can't be done + natively as the XML-RPC specification explictly forbids passing of + timezone specifiers in ISO8601 format dates. You can, however, use + the <A +HREF="helpers.html#ISO8601ENCODE" +>iso8601_encode()</A +> and <A +HREF="helpers.html#ISO8601DECODE" +>iso8601_decode()</A +> functions to do the encoding and decoding for you.</P +><P +>If alternative character set encoding is sent in HTTP header + than it will be ignored for the moment. We speak only UTF-8...</P +><P +>If more than 32k of HTTP headers are encountered (like, why?) then the + response parsing code will break.</P +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="manifest.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="support.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>Files in the distribution</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +> </TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>Support</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/custom.dsl b/modules/xmlrpc/doc/custom.dsl new file mode 100644 index 00000000..24d4b4bf --- /dev/null +++ b/modules/xmlrpc/doc/custom.dsl @@ -0,0 +1,25 @@ +<!DOCTYPE style-sheet PUBLIC "-//James Clark//DTD DSSSL Style Sheet//EN" [ +<!ENTITY dbstyle SYSTEM "/usr/lib/sgml/stylesheet/dsssl/docbook/nwalsh/html/docbook.dsl" CDATA DSSSL> +]> + +<style-sheet> +<style-specification use="docbook"> +<style-specification-body> + +(define %link-mailto-url% + "edd@usefulinc.com") + +(define %html-ext% + ".html") + +(define %use-id-as-filename% + #t) + +(define %root-filename% + "index") + + +</style-specification-body> +</style-specification> +<external-specification id="docbook" document="dbstyle"> +</style-sheet> diff --git a/modules/xmlrpc/doc/debugging.html b/modules/xmlrpc/doc/debugging.html new file mode 100644 index 00000000..7a0e567b --- /dev/null +++ b/modules/xmlrpc/doc/debugging.html @@ -0,0 +1,188 @@ +<HTML +><HEAD +><TITLE +>Debugging aids</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="UP" +TITLE="Helper functions" +HREF="helpers.html"><LINK +REL="PREVIOUS" +TITLE="Easy use with PHP arrays" +HREF="arrayuse.html"><LINK +REL="NEXT" +TITLE="Reserved methods" +HREF="reserved.html"></HEAD +><BODY +CLASS="SECT1" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="arrayuse.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +>Chapter 6. Helper functions</TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="reserved.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="DEBUGGING" +></A +>Debugging aids</H1 +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="AEN811" +></A +>xmlrpc_debugmsg</H2 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN813" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>xmlrpc_debugmsg</CODE +>($debugstring);</CODE +></P +><P +></P +></DIV +><P +>Sends the contents of <TT +CLASS="PARAMETER" +><I +>$debugstring</I +></TT +> + in XML comments in the server return payload. If a PHP client + has debugging turned on, the user will be able to see server + debug information.</P +><P +>Use this function in your methods so you can pass back + diagnostic information. It is only available from + <TT +CLASS="FILENAME" +>xmlrpcs.inc</TT +>.</P +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="arrayuse.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="reserved.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>Easy use with PHP arrays</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="helpers.html" +ACCESSKEY="U" +>Up</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>Reserved methods</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/examples.html b/modules/xmlrpc/doc/examples.html new file mode 100644 index 00000000..e8d44415 --- /dev/null +++ b/modules/xmlrpc/doc/examples.html @@ -0,0 +1,159 @@ +<HTML +><HEAD +><TITLE +>Examples</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="PREVIOUS" +TITLE="system.methodHelp" +HREF="sysmethhelp.html"></HEAD +><BODY +CLASS="CHAPTER" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="sysmethhelp.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +></TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +> </TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="CHAPTER" +><H1 +><A +NAME="EXAMPLES" +></A +>Chapter 8. Examples</H1 +><P +>The best examples are to be found in the sample files + included with the distribution. Some are included here.</P +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="STATENAME" +></A +>XML-RPC client: state name query</H1 +><P +>Code to get the corresponding + state name from a number (1-50) from Dave Winer's server</P +><PRE +CLASS="PROGRAMLISTING" +> $f=new xmlrpcmsg('examples.getStateName', + array(new xmlrpcval($HTTP_POST_VARS["stateno"], "int"))); + $c=new xmlrpc_client("/RPC2", "betty.userland.com", 80); + $r=$c->send($f); + $v=$r->value(); + if (!$r->faultCode()) { + print "State number ". $HTTP_POST_VARS["stateno"] . " is " . + $v->scalarval() . "<BR>"; + print "<HR>I got this value back<BR><PRE>" . + htmlentities($r->serialize()). "</PRE><HR>\n"; + } else { + print "Fault: "; + print "Code: " . $r->faultCode() . + " Reason '" .$r->faultString()."'<BR>"; + } + </PRE +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="sysmethhelp.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +> </TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>system.methodHelp</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +> </TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +> </TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/helpers.html b/modules/xmlrpc/doc/helpers.html new file mode 100644 index 00000000..4f0c1319 --- /dev/null +++ b/modules/xmlrpc/doc/helpers.html @@ -0,0 +1,318 @@ +<HTML +><HEAD +><TITLE +>Helper functions</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="PREVIOUS" +TITLE="xmlrpc_server" +HREF="xmlrpc-server.html"><LINK +REL="NEXT" +TITLE="Easy use with PHP arrays" +HREF="arrayuse.html"></HEAD +><BODY +CLASS="CHAPTER" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="xmlrpc-server.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +></TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="arrayuse.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="CHAPTER" +><H1 +><A +NAME="HELPERS" +></A +>Chapter 6. Helper functions</H1 +><DIV +CLASS="TOC" +><DL +><DT +><B +>Table of Contents</B +></DT +><DT +><A +HREF="helpers.html#AEN739" +>Date functions</A +></DT +><DT +><A +HREF="arrayuse.html" +>Easy use with PHP arrays</A +></DT +><DT +><A +HREF="debugging.html" +>Debugging aids</A +></DT +></DL +></DIV +><P +>XML-RPC for PHP contains some helper functions which you can + use to make processing of XML-RPC requests easier.</P +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="AEN739" +></A +>Date functions</H1 +><P +>The XML-RPC specification has this to say on dates:</P +><A +NAME="AEN742" +></A +><BLOCKQUOTE +CLASS="BLOCKQUOTE" +><P +>Don't assume a timezone. It should be specified by the server in its + documentation what assumptions it makes about timezones. </P +></BLOCKQUOTE +><P +>Unfortunately, this means that date processing isn't + straightforward. Although XML-RPC uses ISO 8601 format dates, it + doesn't use the timezone specifier.</P +><P +>We strongly recommend that in every case where you pass + dates in XML-RPC calls, you use UTC (GMT) as your timezone. Most computer + languages include routines for handling GMT times natively, and + you won't have to translate between timezones.</P +><P +>For more information about dates, see <A +HREF="http://www.uic.edu/year2000/datefmt.html" +TARGET="_top" +>ISO 8601: The Right Format for Dates</A +>, which has a handy link to a PDF of the ISO 8601 specification. Note that XML-RPC uses exactly one of the available representations: CCYYMMDDTHH:MM:SS.</P +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="ISO8601ENCODE" +></A +>iso8601_encode</H2 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN750" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$isoString=iso8601_encode</CODE +>($time_t, $utc=0);</CODE +></P +><P +></P +></DIV +><P +>Returns an ISO 8601 formatted date generated from the + UNIX timestamp <TT +CLASS="PARAMETER" +><I +>$time_t</I +></TT +>, as returned by + the PHP function <TT +CLASS="FUNCTION" +>time()</TT +>. </P +><P +>The argument <TT +CLASS="PARAMETER" +><I +>$utc</I +></TT +> can be omitted, + in which case it defaults to <TT +CLASS="LITERAL" +>0</TT +>. If it is + set to <TT +CLASS="LITERAL" +>1</TT +>, then the function corrects the + time passed in for UTC. Example: if you're in the GMT-6:00 + timezone and set <TT +CLASS="PARAMETER" +><I +>$utc</I +></TT +>, you will receive + a date representation six hours ahead of your local + time.</P +><P +>The included demo program <TT +CLASS="FILENAME" +>vardemo.php</TT +> + includes a demonstration of this function.</P +></DIV +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="ISO8601DECODE" +></A +>iso8601_decode</H2 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN768" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$time_t=iso8601_decode</CODE +>($isoString, $utc=0);</CODE +></P +><P +></P +></DIV +><P +>Returns a UNIX timestamp from an ISO 8601 encoded time and + date string passed in. If <TT +CLASS="PARAMETER" +><I +>$utc</I +></TT +> is + <TT +CLASS="LITERAL" +>1</TT +> then <TT +CLASS="PARAMETER" +><I +>$isoString</I +></TT +> is + assumed to be in the UTC timezone, and thus the + <TT +CLASS="PARAMETER" +><I +>$time_t</I +></TT +> result is also UTC: otherwise, + the timezone is assumed to be your local timezone and you receive a local timestamp.</P +></DIV +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="xmlrpc-server.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="arrayuse.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>xmlrpc_server</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +> </TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>Easy use with PHP arrays</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/index.html b/modules/xmlrpc/doc/index.html new file mode 100644 index 00000000..6e91b313 --- /dev/null +++ b/modules/xmlrpc/doc/index.html @@ -0,0 +1,453 @@ +<HTML +><HEAD +><TITLE +>XML-RPC for PHP</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="NEXT" +TITLE="Introduction" +HREF="introduction.html"></HEAD +><BODY +CLASS="BOOK" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="BOOK" +><A +NAME="AEN1" +></A +><DIV +CLASS="TITLEPAGE" +><H1 +CLASS="TITLE" +><A +NAME="AEN2" +></A +>XML-RPC for PHP</H1 +><H2 +CLASS="SUBTITLE" +>version 1.1</H2 +><H3 +CLASS="AUTHOR" +><A +NAME="AEN7" +></A +>Edd Dumbill</H3 +><DIV +CLASS="AFFILIATION" +><SPAN +CLASS="ORGNAME" +><A +HREF="http://usefulinc.com/" +TARGET="_top" +>Useful + Information Company</A +><BR></SPAN +><DIV +CLASS="ADDRESS" +><P +CLASS="ADDRESS" +> <TT +CLASS="EMAIL" +><<A +HREF="mailto:edd@usefulinc.com" +>edd@usefulinc.com</A +>></TT +><br> + </P +></DIV +></DIV +><P +CLASS="COPYRIGHT" +>Copyright © 1999,2000,2001 Edd Dumbill, Useful Information Company</P +><DIV +CLASS="LEGALNOTICE" +><A +NAME="AEN18" +></A +><P +></P +><P +> All rights reserved. + </P +><P +> Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + </P +><P +> <P +></P +><UL +><LI +><P +> Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + </P +></LI +><LI +><P +> Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + </P +></LI +><LI +><P +> Neither the name of the "XML-RPC for PHP" nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + </P +></LI +></UL +> + </P +><P +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE.</P +><P +></P +></DIV +><HR></DIV +><DIV +CLASS="TOC" +><DL +><DT +><B +>Table of Contents</B +></DT +><DT +>1. <A +HREF="introduction.html" +>Introduction</A +></DT +><DD +><DL +><DT +><A +HREF="introduction.html#AEN43" +>Acknowledgements</A +></DT +></DL +></DD +><DT +>2. <A +HREF="manifest.html" +>Files in the distribution</A +></DT +><DT +>3. <A +HREF="bugs.html" +>Bugs</A +></DT +><DT +>4. <A +HREF="support.html" +>Support</A +></DT +><DD +><DL +><DT +><A +HREF="support.html#AEN146" +>Online Support</A +></DT +><DT +><A +HREF="jellyfish.html" +>The Jellyfish Book</A +></DT +></DL +></DD +><DT +>5. <A +HREF="apidocs.html" +>Class documentation</A +></DT +><DD +><DL +><DT +><A +HREF="apidocs.html#XMLRPC-CLIENT" +>xmlrpc_client</A +></DT +><DD +><DL +><DT +><A +HREF="apidocs.html#AEN174" +>Creation</A +></DT +><DT +><A +HREF="apidocs.html#AEN191" +>Methods</A +></DT +></DL +></DD +><DT +><A +HREF="xmlrpcmsg.html" +>xmlrpcmsg</A +></DT +><DD +><DL +><DT +><A +HREF="xmlrpcmsg.html#AEN289" +>Creation</A +></DT +><DT +><A +HREF="xmlrpcmsg.html#AEN309" +>Methods</A +></DT +></DL +></DD +><DT +><A +HREF="xmlrpcresp.html" +>xmlrpcresp</A +></DT +><DD +><DL +><DT +><A +HREF="xmlrpcresp.html#AEN386" +>Creation</A +></DT +><DT +><A +HREF="xmlrpcresp.html#AEN408" +>Methods</A +></DT +></DL +></DD +><DT +><A +HREF="xmlrpcval.html" +>xmlrpcval</A +></DT +><DD +><DL +><DT +><A +HREF="xmlrpcval.html#AEN452" +>Notes on types</A +></DT +><DT +><A +HREF="xmlrpcval.html#XMLRPCVAL-CREATION" +>Creation</A +></DT +><DT +><A +HREF="xmlrpcval.html#XMLRPCVAL-METHODS" +>Methods</A +></DT +></DL +></DD +><DT +><A +HREF="xmlrpc-server.html" +>xmlrpc_server</A +></DT +><DD +><DL +><DT +><A +HREF="xmlrpc-server.html#AEN658" +>The dispatch map</A +></DT +><DT +><A +HREF="xmlrpc-server.html#SIGNATURES" +>Method signatures</A +></DT +><DT +><A +HREF="xmlrpc-server.html#AEN686" +>Delaying the server response</A +></DT +><DT +><A +HREF="xmlrpc-server.html#AEN692" +>Fault reporting</A +></DT +></DL +></DD +></DL +></DD +><DT +>6. <A +HREF="helpers.html" +>Helper functions</A +></DT +><DD +><DL +><DT +><A +HREF="helpers.html#AEN739" +>Date functions</A +></DT +><DD +><DL +><DT +><A +HREF="helpers.html#ISO8601ENCODE" +>iso8601_encode</A +></DT +><DT +><A +HREF="helpers.html#ISO8601DECODE" +>iso8601_decode</A +></DT +></DL +></DD +><DT +><A +HREF="arrayuse.html" +>Easy use with PHP arrays</A +></DT +><DD +><DL +><DT +><A +HREF="arrayuse.html#XMLRPCDECODE" +>xmlrpc_decode</A +></DT +><DT +><A +HREF="arrayuse.html#XMLRPCENCODE" +>xmlrpc_encode</A +></DT +></DL +></DD +><DT +><A +HREF="debugging.html" +>Debugging aids</A +></DT +><DD +><DL +><DT +><A +HREF="debugging.html#AEN811" +>xmlrpc_debugmsg</A +></DT +></DL +></DD +></DL +></DD +><DT +>7. <A +HREF="reserved.html" +>Reserved methods</A +></DT +><DD +><DL +><DT +><A +HREF="reserved.html#AEN827" +>system.listMethods</A +></DT +><DT +><A +HREF="sysmethodsig.html" +>system.methodSignature</A +></DT +><DT +><A +HREF="sysmethhelp.html" +>system.methodHelp</A +></DT +></DL +></DD +><DT +>8. <A +HREF="examples.html" +>Examples</A +></DT +><DD +><DL +><DT +><A +HREF="examples.html#STATENAME" +>XML-RPC client: state name query</A +></DT +></DL +></DD +></DL +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +> </TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +> </TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="introduction.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +> </TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +> </TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>Introduction</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/introduction.html b/modules/xmlrpc/doc/introduction.html new file mode 100644 index 00000000..09b314e4 --- /dev/null +++ b/modules/xmlrpc/doc/introduction.html @@ -0,0 +1,280 @@ +<HTML +><HEAD +><TITLE +>Introduction</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="PREVIOUS" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="NEXT" +TITLE="Files in the distribution" +HREF="manifest.html"></HEAD +><BODY +CLASS="CHAPTER" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="index.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +></TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="manifest.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="CHAPTER" +><H1 +><A +NAME="INTRODUCTION" +></A +>Chapter 1. Introduction</H1 +><P +>XML-RPC is a format devised by <A +HREF="http://www.userland.com/" +TARGET="_top" +>Userland Software</A +> for + achieving remote procedure call via XML. XML-RPC has its own web + site, <A +HREF="http://www.xmlrpc.com/" +TARGET="_top" +>www.XmlRpc.com</A +></P +><P +>The most common implementations of XML-RPC available at the + moment use HTTP as the transport. A list of implementations for + other languages such as Perl and Python can be found on the + <A +HREF="http://www.xmlrpc.com/" +TARGET="_top" +>www.xmlrpc.com</A +>.</P +><P +>This collection of PHP classes provides a framework for + writing XML-RPC clients and servers in PHP.</P +><DIV +CLASS="WARNING" +><P +></P +><TABLE +CLASS="WARNING" +BORDER="1" +WIDTH="100%" +><TR +><TD +ALIGN="CENTER" +><B +>Warning</B +></TD +></TR +><TR +><TD +ALIGN="LEFT" +><P +>The <SPAN +CLASS="emphasis" +><I +CLASS="EMPHASIS" +>server code</I +></SPAN +> works only with versions of PHP3 + >= 3.0.12. The code is also known to work with PHP4. + </P +><P +>If you wish to use SSL to communicate with remote servers, + you need the "curl" extension compiled into your PHP + installation, this is available in PHP 4.0.2 and greater, + although 4.0.6 has a bug preventing SSL working.</P +></TD +></TR +></TABLE +></DIV +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="AEN43" +></A +>Acknowledgements</H1 +><P +>Jim Winstead <TT +CLASS="EMAIL" +><<A +HREF="mailto:jimw@php.net" +>jimw@php.net</A +>></TT +></P +><P +>Peter Kocks <TT +CLASS="EMAIL" +><<A +HREF="mailto:peter.kocks@baygate.com" +>peter.kocks@baygate.com</A +>></TT +></P +><P +>Nicolay Mausz <TT +CLASS="EMAIL" +><<A +HREF="mailto:mausz@flying-dog.com" +>mausz@flying-dog.com</A +>></TT +></P +><P +>Ben Margolin + <TT +CLASS="EMAIL" +><<A +HREF="mailto:ben@wendy.auctionwatch.com" +>ben@wendy.auctionwatch.com</A +>></TT +></P +><P +>Dan Libby <TT +CLASS="EMAIL" +><<A +HREF="mailto:dan@libby.com" +>dan@libby.com</A +>></TT +></P +><P +>Gaetano Giunta <TT +CLASS="EMAIL" +><<A +HREF="mailto:g.giunta@libero.it" +>g.giunta@libero.it</A +>></TT +></P +><P +>Idan Sofer <TT +CLASS="EMAIL" +><<A +HREF="mailto:i_sofer@yahoo.com" +>i_sofer@yahoo.com</A +>></TT +></P +><P +>Giancarlo Pinerolo <TT +CLASS="EMAIL" +><<A +HREF="mailto:ping@alt.it" +>ping@alt.it</A +>></TT +></P +><P +>Justin Miller <TT +CLASS="EMAIL" +><<A +HREF="mailto:justin@voxel.net" +>justin@voxel.net</A +>></TT +></P +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="manifest.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>XML-RPC for PHP</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +> </TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>Files in the distribution</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/jellyfish.html b/modules/xmlrpc/doc/jellyfish.html new file mode 100644 index 00000000..a4154100 --- /dev/null +++ b/modules/xmlrpc/doc/jellyfish.html @@ -0,0 +1,180 @@ +<HTML +><HEAD +><TITLE +>The Jellyfish Book</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="UP" +TITLE="Support" +HREF="support.html"><LINK +REL="PREVIOUS" +TITLE="Support" +HREF="support.html"><LINK +REL="NEXT" +TITLE="Class documentation" +HREF="apidocs.html"></HEAD +><BODY +CLASS="SECT1" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="support.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +>Chapter 4. Support</TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="apidocs.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="JELLYFISH" +></A +>The Jellyfish Book</H1 +><P +> + <P +><IMG +SRC="http://www.oreilly.com/catalog/covers/progxmlrpc.s.gif" +ALIGN="RIGHT" +WIDTH="145" +HEIGHT="190"></P +> + Together with Simon St.Laurent and Joe Johnston, I wrote a + book on XML-RPC for O'Reilly and Associates on XML-RPC. It + features a rather fetching jellyfish on the cover. + </P +><P +>Complete details of the book are + <A +HREF="http://www.oreilly.com/catalog/progxmlrpc/" +TARGET="_top" +>available + from O'Reilly's web site.</A +> + </P +><P +>I'm responsible for the chapter on PHP, which includes a + worked example of creating a forum server, and hooking it up + the O'Reilly's <A +HREF="http://meerkat.oreillynet.com/" +TARGET="_top" +>Meerkat</A +> service + in order to allow commenting on news stories from around the + Web.</P +><P +>If you've benefitted from the effort I've put into writing + this software, then please consider buying the book!</P +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="support.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="apidocs.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>Support</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="support.html" +ACCESSKEY="U" +>Up</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>Class documentation</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/manifest.html b/modules/xmlrpc/doc/manifest.html new file mode 100644 index 00000000..1586e41c --- /dev/null +++ b/modules/xmlrpc/doc/manifest.html @@ -0,0 +1,310 @@ +<HTML +><HEAD +><TITLE +>Files in the distribution</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="PREVIOUS" +TITLE="Introduction" +HREF="introduction.html"><LINK +REL="NEXT" +TITLE="Bugs" +HREF="bugs.html"></HEAD +><BODY +CLASS="CHAPTER" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="introduction.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +></TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="bugs.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="CHAPTER" +><H1 +><A +NAME="MANIFEST" +></A +>Chapter 2. Files in the distribution</H1 +><DIV +CLASS="GLOSSLIST" +><DL +><DT +><B +>xmlrpc.inc</B +></DT +><DD +><P +>the XML-RPC classes. <TT +CLASS="FUNCTION" +>include()</TT +> this in + your PHP files to use the classes.</P +></DD +><DT +><B +>xmlrpcs.inc</B +></DT +><DD +><P +>the XML-RPC server class. <TT +CLASS="FUNCTION" +>include()</TT +> + this in addition to xmlrpc.inc to get server + functionality</P +></DD +><DT +><B +>bettydemo.php</B +></DT +><DD +><P +>demo which retrieves a state name from a number using Dave + Winer's XML-RPC server at betty.userland.com</P +></DD +><DT +><B +>server.php</B +></DT +><DD +><P +>a sample server hosting three functions: a US State lookup + tool, a struct sorter and an echo function.</P +></DD +><DT +><B +>client.php, agesort.php, echotest.php</B +></DT +><DD +><P +>client code to exercise the various functions in + server.php</P +></DD +><DT +><B +>base64test.php, stringtest.php</B +></DT +><DD +><P +> Tests to verify that encoding and decoding of base 64 and + entities is functioning correctly. + </P +></DD +><DT +><B +>vardemo.php</B +></DT +><DD +><P +>examples of how to construct xmlrpcval types</P +></DD +><DT +><B +>demo1.txt, demo2.txt, demo3.txt</B +></DT +><DD +><P +>XML-RPC responses captured in a file for testing purposes (you + can use these to test the + <TT +CLASS="FUNCTION" +>xmlrpcmsg->parseResponse()</TT +> + method).</P +></DD +><DT +><B +>httptest.php</B +></DT +><DD +><P +>Testing that HTTP response detection works OK.</P +></DD +><DT +><B +>test.pl, test.py</B +></DT +><DD +><P +>Perl and Python programs to exercise server.php to test + that some of the methods work. + Make sure you point these at your server, not mine!</P +></DD +><DT +><B +>workspace.testPhpServer.fttb</B +></DT +><DD +><P +>Frontier scripts to exercise the demo server. Thanks to Dave + Winer for permission to include these. See <A +HREF="http://www.xmlrpc.com/discuss/msgReader$853" +TARGET="_top" +>Dave's announcement of these.</A +></P +></DD +><DT +><B +>phpunit.php</B +></DT +><DD +><P +>Fred Yankowski's unit test framework for PHP.</P +></DD +><DT +><B +>testsuite.php</B +></DT +><DD +><P +>Start of a unit test suite for this software + package. If you do development on this software, please + consider submitting tests for this suite.</P +></DD +><DT +><B +>which.php</B +></DT +><DD +><P +>A demo of the + <TT +CLASS="FUNCTION" +>interopEchoTests.whichToolkit</TT +> + method.</P +></DD +><DT +><B +>discuss.php, comment.php</B +></DT +><DD +><P +>Software used in the PHP chapter of <A +HREF="jellyfish.html" +>The Jellyfish Book</A +> to + provide a comment server and allow the attachment of + comments to stories from Meerkat's data store.</P +></DD +><DT +><B +>rsakey.pem</B +></DT +><DD +><P +>A test certificate for the SSL support. It has the + passphrase "test."</P +></DD +></DL +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="introduction.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="bugs.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>Introduction</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +> </TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>Bugs</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/reserved.html b/modules/xmlrpc/doc/reserved.html new file mode 100644 index 00000000..d5525d6a --- /dev/null +++ b/modules/xmlrpc/doc/reserved.html @@ -0,0 +1,191 @@ +<HTML +><HEAD +><TITLE +>Reserved methods</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="PREVIOUS" +TITLE="Debugging aids" +HREF="debugging.html"><LINK +REL="NEXT" +TITLE="system.methodSignature" +HREF="sysmethodsig.html"></HEAD +><BODY +CLASS="CHAPTER" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="debugging.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +></TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="sysmethodsig.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="CHAPTER" +><H1 +><A +NAME="RESERVED" +></A +>Chapter 7. Reserved methods</H1 +><DIV +CLASS="TOC" +><DL +><DT +><B +>Table of Contents</B +></DT +><DT +><A +HREF="reserved.html#AEN827" +>system.listMethods</A +></DT +><DT +><A +HREF="sysmethodsig.html" +>system.methodSignature</A +></DT +><DT +><A +HREF="sysmethhelp.html" +>system.methodHelp</A +></DT +></DL +></DIV +><P +>In order to extend the functionality offered by XML-RPC + servers without impacting on the protocol, I've included + experimental support in this release for reserved methods.</P +><P +>All methods starting with <TT +CLASS="FUNCTION" +>system.</TT +> are + considered reserved by the server. PHP for XML-RPC itself provides + three special methods, detailed in this chapter.</P +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="AEN827" +></A +>system.listMethods</H1 +><P +>This method may be used to enumerate the methods implemented + by the XML-RPC server.</P +><P +>The <TT +CLASS="FUNCTION" +>system.listMethods</TT +> method requires + no parameters. It returns an array of strings, each of which is + the name of a method implemented by the server.</P +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="debugging.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="sysmethodsig.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>Debugging aids</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +> </TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>system.methodSignature</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/support.html b/modules/xmlrpc/doc/support.html new file mode 100644 index 00000000..6c60c9cb --- /dev/null +++ b/modules/xmlrpc/doc/support.html @@ -0,0 +1,209 @@ +<HTML +><HEAD +><TITLE +>Support</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="PREVIOUS" +TITLE="Bugs" +HREF="bugs.html"><LINK +REL="NEXT" +TITLE="The Jellyfish Book" +HREF="jellyfish.html"></HEAD +><BODY +CLASS="CHAPTER" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="bugs.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +></TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="jellyfish.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="CHAPTER" +><H1 +><A +NAME="SUPPORT" +></A +>Chapter 4. Support</H1 +><DIV +CLASS="TOC" +><DL +><DT +><B +>Table of Contents</B +></DT +><DT +><A +HREF="support.html#AEN146" +>Online Support</A +></DT +><DT +><A +HREF="jellyfish.html" +>The Jellyfish Book</A +></DT +></DL +></DIV +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="AEN146" +></A +>Online Support</H1 +><P +>XML-RPC for PHP is offered "as-is" without any warranty or + commitment to support. However, informal advice and help is + available via the XML-RPC for PHP mailing list and XML-RPC.com. + </P +><P +></P +><UL +><LI +><P +>The <SPAN +CLASS="emphasis" +><I +CLASS="EMPHASIS" +>PHP XML-RPC interest mailing list</I +></SPAN +> + is run by the author. More details <A +HREF="http://www.usefulinc.com/xmlrpc/list.html" +TARGET="_top" +>can be found + here</A +>.</P +></LI +><LI +><P +>For more general XML-RPC questions, there is a + Yahoo! Groups <A +HREF="http://groups.yahoo.com/group/xml-rpc/" +TARGET="_top" +>XML-RPC mailing list</A +>.</P +></LI +><LI +><P +>The <A +HREF="http://www.xmlrpc.com/discuss" +TARGET="_top" +>XML-RPC.com</A +> + discussion group is a useful place to get help with using + XML-RPC. This group is also gatewayed into the Yahoo! Groups mailing list.</P +></LI +></UL +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="bugs.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="jellyfish.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>Bugs</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +> </TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>The Jellyfish Book</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/sysmethhelp.html b/modules/xmlrpc/doc/sysmethhelp.html new file mode 100644 index 00000000..151d8d16 --- /dev/null +++ b/modules/xmlrpc/doc/sysmethhelp.html @@ -0,0 +1,155 @@ +<HTML +><HEAD +><TITLE +>system.methodHelp</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="UP" +TITLE="Reserved methods" +HREF="reserved.html"><LINK +REL="PREVIOUS" +TITLE="system.methodSignature" +HREF="sysmethodsig.html"><LINK +REL="NEXT" +TITLE="Examples" +HREF="examples.html"></HEAD +><BODY +CLASS="SECT1" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="sysmethodsig.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +>Chapter 7. Reserved methods</TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="examples.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="SYSMETHHELP" +></A +>system.methodHelp</H1 +><P +>This method takes one parameter, the name of a method + implemented by the XML-RPC server.</P +><P +> It returns a documentation string describing the use of that + method. If no such string is available, an empty string is returned. + </P +><P +> The documentation string may contain HTML markup. + </P +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="sysmethodsig.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="examples.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>system.methodSignature</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="reserved.html" +ACCESSKEY="U" +>Up</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>Examples</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/sysmethodsig.html b/modules/xmlrpc/doc/sysmethodsig.html new file mode 100644 index 00000000..bb0bb7ca --- /dev/null +++ b/modules/xmlrpc/doc/sysmethodsig.html @@ -0,0 +1,191 @@ +<HTML +><HEAD +><TITLE +>system.methodSignature</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="UP" +TITLE="Reserved methods" +HREF="reserved.html"><LINK +REL="PREVIOUS" +TITLE="Reserved methods" +HREF="reserved.html"><LINK +REL="NEXT" +TITLE="system.methodHelp" +HREF="sysmethhelp.html"></HEAD +><BODY +CLASS="SECT1" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="reserved.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +>Chapter 7. Reserved methods</TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="sysmethhelp.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="SYSMETHODSIG" +></A +>system.methodSignature</H1 +><P +>This method takes one parameter, the name of a method + implemented by the XML-RPC server.</P +><P +>It returns an array of possible signatures for this + method. A signature is an array of types. The first of these + types is the return type of the method, the rest are parameters.</P +><P +>Multiple signatures (ie. overloading) are permitted: this is + the reason that an array of signatures are returned by this + method.</P +><P +>Signatures themselves are restricted to the top level + parameters expected by a method. For instance if a method + expects one array of structs as a parameter, and it returns a + string, its signature is simply "string, array". If it expects + three integers, its signature is "string, int, int, + int".</P +><P +> If no signature is defined for the method, a none-array value is + returned. Therefore this is the way to test for a non-signature, + if <TT +CLASS="PARAMETER" +><I +>$resp</I +></TT +> below is the response object + from a method call to <TT +CLASS="FUNCTION" +>system.methodSignature</TT +>: + </P +><PRE +CLASS="PROGRAMLISTING" +>$v=$resp->value(); +if ($v->kindOf()!="array") { + // then the method did not have a signature defined +} + </PRE +><P +> See the <TT +CLASS="FILENAME" +>introspect.php</TT +> demo included in + this distribution for an example of using this method. + </P +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="reserved.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="sysmethhelp.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>Reserved methods</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="reserved.html" +ACCESSKEY="U" +>Up</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>system.methodHelp</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/xmlrpc-server.html b/modules/xmlrpc/doc/xmlrpc-server.html new file mode 100644 index 00000000..da18e0f7 --- /dev/null +++ b/modules/xmlrpc/doc/xmlrpc-server.html @@ -0,0 +1,487 @@ +<HTML +><HEAD +><TITLE +>xmlrpc_server</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="UP" +TITLE="Class documentation" +HREF="apidocs.html"><LINK +REL="PREVIOUS" +TITLE="xmlrpcval" +HREF="xmlrpcval.html"><LINK +REL="NEXT" +TITLE="Helper functions" +HREF="helpers.html"></HEAD +><BODY +CLASS="SECT1" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="xmlrpcval.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +>Chapter 5. Class documentation</TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="helpers.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="XMLRPC-SERVER" +></A +>xmlrpc_server</H1 +><P +>The current implementation of this class has been + kept as simple as possible. The constructor for the server + basically does all the work. Here's a minimal example:</P +><PRE +CLASS="PROGRAMLISTING" +> function foo ($params) { + ... + } + + $s=new xmlrpc_server( array("examples.myFunc" => + array("function" => "foo"))); + </PRE +><P +> This performs everything you need to do with a server. The single + argument is an associative array from method names to function + names. The request is parsed and despatched to the relevant function, + which is reponsible for returning a + <TT +CLASS="CLASSNAME" +>xmlrpcresp</TT +> + object, which gets + serialized back to the caller. See server.php in this distribution for + examples of how to do this. + </P +><P +>Here is a more detailed look at what the handler function + <TT +CLASS="FUNCTION" +>foo</TT +> may do:</P +><PRE +CLASS="PROGRAMLISTING" +> function foo ($params) { + global $xmlrpcerruser; // import user errcode value + + // $params is an Array of xmlrpcval objects + + if ($err) { + // this is an error condition + return new xmlrpcresp(0, $xmlrpcerruser+1, // user error 1 + "There's a problem, Captain"); + } else { + // this is a successful value being returned + return new xmlrpcresp(new xmlrpcval("All's fine!", "string")); + } + } + </PRE +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="AEN658" +></A +>The dispatch map</H2 +><P +>The first argument to the + <TT +CLASS="FUNCTION" +>xmlrpc_server</TT +> constructor is an array, + called the <SPAN +CLASS="emphasis" +><I +CLASS="EMPHASIS" +>dispatch map</I +></SPAN +>. In this array is the + information the server needs to service the XML-RPC methods + you define.</P +><P +> The dispatch map takes the form of an associative array of + associative arrays: the outer array has one entry for each + method, the key being the method name. The corresponding value + is another associative array, which can have the following members: + </P +><P +></P +><UL +><LI +><P +><TT +CLASS="FUNCTION" +>function</TT +> - this entry is + mandatory. It must be a name of a function in the + global scope which services the XML-RPC method.</P +></LI +><LI +><P +><TT +CLASS="FUNCTION" +>signature</TT +> - this entry is an + array containg the possible signatures (see <A +HREF="xmlrpc-server.html#SIGNATURES" +>Signatures</A +>) for the method. If + this entry is present then the server will check that the + correct number and type of parameters have been sent for + this method before dispatching it. + </P +></LI +><LI +><P +> <TT +CLASS="FUNCTION" +>docstring</TT +> - this entry is a string + containing documentation for the method. The + documentation may contain HTML markup. + </P +></LI +></UL +><P +>Look at the <TT +CLASS="FILENAME" +>server.php</TT +> example in the + distribution to see what a dispatch map looks like.</P +></DIV +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="SIGNATURES" +></A +>Method signatures</H2 +><P +>A signature is a description of a method's return type and + its parameter types. A method may have more than one + signature.</P +><P +>Within a server's dispatch map, each method has an array + of possible signatures. Each signature is an array of + types. The first entry is the return type. For instance, the + method <PRE +CLASS="PROGRAMLISTING" +>string examples.getStateName(int)</PRE +> has the signature +<PRE +CLASS="PROGRAMLISTING" +>array($xmlrpcString, $xmlrpcInt)</PRE +> and, assuming that it the only possible signature for + the method, might be used like this in server creation: +<PRE +CLASS="PROGRAMLISTING" +>$findstate_sig=array(array($xmlrpcString, $xmlrpcInt)); + +$findstate_doc='When passed an integer between 1 and 51 returns the +name of a US state, where the integer is the index of that state name +in an alphabetic order.'; + +$s=new xmlrpc_server( array( "examples.getStateName" => + array("function" => "findstate", + "signature" => $findstate_sig, + "docstring" => $findstate_doc)));</PRE +> + + </P +><P +>For convenience the strings representing the XML-RPC types + have been encoded as global variables:<PRE +CLASS="PROGRAMLISTING" +>$xmlrpcI4="i4"; +$xmlrpcInt="int"; +$xmlrpcBoolean="boolean"; +$xmlrpcDouble="double"; +$xmlrpcString="string"; +$xmlrpcDateTime="dateTime.iso8601"; +$xmlrpcBase64="base64"; +$xmlrpcArray="array"; +$xmlrpcStruct="struct";</PRE +></P +></DIV +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="AEN686" +></A +>Delaying the server response</H2 +><P +>You may want to construct the server, but for some reason + not fulfill the request immediately (security verification, for + instance). If you pass the constructor a second argument of + <TT +CLASS="LITERAL" +>0</TT +> this will have the desired effect. You + can then use the <TT +CLASS="FUNCTION" +>service()</TT +> method of the + server class to service the request. For example:</P +><PRE +CLASS="PROGRAMLISTING" +>$s=new xmlrpc_server($myDispMap, 0); + +// ... some code that does other stuff here + +$s->service();</PRE +></DIV +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="AEN692" +></A +>Fault reporting</H2 +><P +>Fault codes for your servers should start at the + value indicated by + the global <TT +CLASS="LITERAL" +>$xmlrpcerruser</TT +> + 1.</P +><P +>Standard errors returned by the server include:</P +><P +></P +><DIV +CLASS="VARIABLELIST" +><DL +><DT +><TT +CLASS="LITERAL" +>1</TT +> <SPAN +CLASS="phrase" +><SPAN +CLASS="PHRASE" +>Unknown method</SPAN +></SPAN +></DT +><DD +><P +>Returned if the server was asked to dispatch a + method it didn't know about</P +></DD +><DT +><TT +CLASS="LITERAL" +>2</TT +> <SPAN +CLASS="phrase" +><SPAN +CLASS="PHRASE" +>Invalid return payload</SPAN +></SPAN +></DT +><DD +><P +>This error is actually generated by the client, not + server, code, but signifies that a server returned + something it couldn't understand.</P +></DD +><DT +><TT +CLASS="LITERAL" +>3</TT +> <SPAN +CLASS="phrase" +><SPAN +CLASS="PHRASE" +>Incorrect parameters</SPAN +></SPAN +></DT +><DD +><P +>This error is generated when the server has signature(s) + defined for a method, and the parameters passed by the + client do not match any of signatures.</P +></DD +><DT +><TT +CLASS="LITERAL" +>4</TT +> <SPAN +CLASS="phrase" +><SPAN +CLASS="PHRASE" +>Can't introspect: method unknown</SPAN +></SPAN +></DT +><DD +><P +>This error is generated by the builtin + <TT +CLASS="FUNCTION" +>system.*</TT +> methods when any kind of + introspection is attempted on a method undefined by the + server.</P +></DD +><DT +><TT +CLASS="LITERAL" +>5</TT +> <SPAN +CLASS="phrase" +><SPAN +CLASS="PHRASE" +>Didn't receive 200 OK from remote server</SPAN +></SPAN +></DT +><DD +><P +>This error is generated by the client when a remote server + doesn't return HTTP/1.1 200 OK in response to a + request. A more detailed error report is added onto the + end of the phrase above.</P +></DD +><DT +><TT +CLASS="LITERAL" +>100-</TT +> <SPAN +CLASS="phrase" +><SPAN +CLASS="PHRASE" +>XML parse errors</SPAN +></SPAN +></DT +><DD +><P +>Returns 100 plus the XML parser error code for the + fault that occurred. The + <TT +CLASS="FUNCTION" +>faultString</TT +> returned explains where + the parse error was in the incoming XML stream.</P +></DD +></DL +></DIV +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="xmlrpcval.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="helpers.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>xmlrpcval</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="apidocs.html" +ACCESSKEY="U" +>Up</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>Helper functions</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/xmlrpc_php.sgml b/modules/xmlrpc/doc/xmlrpc_php.sgml new file mode 100644 index 00000000..4f9bc7b2 --- /dev/null +++ b/modules/xmlrpc/doc/xmlrpc_php.sgml @@ -0,0 +1,1487 @@ +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook V3//EN" []> +<book> + <bookinfo> + <title>XML-RPC for PHP</title> + <subtitle>version 1.1</subtitle> + <date>December 17, 2002</date> + <authorgroup> + <author> + <firstname>Edd</firstname> + <surname>Dumbill</surname> + <affiliation> + <orgname><ulink url="http://usefulinc.com/">Useful + Information Company</ulink></orgname> + <address> + <email>edd@usefulinc.com</email> + </address> + </affiliation> + </author> + </authorgroup> + <copyright> + <year>1999,2000,2001</year> + <holder>Edd Dumbill, Useful Information Company</holder> + </copyright> + <legalnotice> + <para> + All rights reserved. + </para> + <para> + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + </para> + + <para> + <itemizedlist> + <listitem> + <para> + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + </para> + </listitem> + + <listitem><para> + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + </para></listitem> + + <listitem><para> + Neither the name of the "XML-RPC for PHP" nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + </para></listitem> + </itemizedlist> + </para> + <para> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGE.</para> + + </legalnotice> + </bookinfo> + <toc> + </toc> + <chapter id="introduction"> + + <title>Introduction</title> + + <para>XML-RPC is a format devised by <ulink + url="http://www.userland.com/">Userland Software</ulink> for + achieving remote procedure call via XML. XML-RPC has its own web + site, <ulink + url="http://www.xmlrpc.com/">www.XmlRpc.com</ulink></para> + <para>The most common implementations of XML-RPC available at the + moment use HTTP as the transport. A list of implementations for + other languages such as Perl and Python can be found on the + <ulink + url="http://www.xmlrpc.com/">www.xmlrpc.com</ulink>.</para> + <para>This collection of PHP classes provides a framework for + writing XML-RPC clients and servers in PHP.</para> + <warning> + <para>The <emphasis>server code</emphasis> works only with versions of PHP3 + >= 3.0.12. The code is also known to work with PHP4. + </para> + <para>If you wish to use SSL to communicate with remote servers, + you need the "curl" extension compiled into your PHP + installation, this is available in PHP 4.0.2 and greater, + although 4.0.6 has a bug preventing SSL working.</para> + </warning> + + <sect1> + <title>Acknowledgements</title> + <para>Jim Winstead <email>jimw@php.net</email></para> + <para>Peter Kocks <email>peter.kocks@baygate.com</email></para> + <para>Nicolay Mausz <email>mausz@flying-dog.com</email></para> + <para>Ben Margolin + <email>ben@wendy.auctionwatch.com</email></para> + <para>Dan Libby <email>dan@libby.com</email></para> + <para>Gaetano Giunta <email>g.giunta@libero.it</email></para> + <para>Idan Sofer <email>i_sofer@yahoo.com</email></para> + <para>Giancarlo Pinerolo <email>ping@alt.it</email></para> + <para>Justin Miller <email>justin@voxel.net</email></para> + </sect1> + </chapter> + + <chapter id="manifest"> + <title>Files in the distribution</title> + + <glosslist> + <glossentry> + <glossterm>xmlrpc.inc</glossterm> + <glossdef> + <para>the XML-RPC classes. <function>include()</function> this in + your PHP files to use the classes.</para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm>xmlrpcs.inc</glossterm> + <glossdef> + <para>the XML-RPC server class. <function>include()</function> + this in addition to xmlrpc.inc to get server + functionality</para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm>bettydemo.php</glossterm> + <glossdef> + <para>demo which retrieves a state name from a number using Dave + Winer's XML-RPC server at betty.userland.com</para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm>server.php</glossterm> + <glossdef> + <para>a sample server hosting three functions: a US State lookup + tool, a struct sorter and an echo function.</para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm>client.php, agesort.php, echotest.php</glossterm> + <glossdef> + <para>client code to exercise the various functions in + server.php</para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm>base64test.php, stringtest.php</glossterm> + <glossdef> + <para> + Tests to verify that encoding and decoding of base 64 and + entities is functioning correctly. + </para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm>vardemo.php</glossterm> + <glossdef> + <para>examples of how to construct xmlrpcval types</para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm>demo1.txt, demo2.txt, demo3.txt</glossterm> + <glossdef> + <para>XML-RPC responses captured in a file for testing purposes (you + can use these to test the + <function>xmlrpcmsg->parseResponse()</function> + method).</para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm>httptest.php</glossterm> + <glossdef> + <para>Testing that HTTP response detection works OK.</para> + </glossdef> + </glossentry> + <glossentry> + <glossterm>test.pl, test.py</glossterm> + <glossdef> + <para>Perl and Python programs to exercise server.php to test + that some of the methods work. + Make sure you point these at your server, not mine!</para> + </glossdef> + </glossentry> + <glossentry> + <glossterm>workspace.testPhpServer.fttb</glossterm> + <glossdef> + <para>Frontier scripts to exercise the demo server. Thanks to Dave + Winer for permission to include these. See <ulink + url="http://www.xmlrpc.com/discuss/msgReader$853">Dave's announcement of these.</ulink></para> + + </glossdef> + </glossentry> + + <glossentry> + <glossterm>phpunit.php</glossterm> + <glossdef> + <para>Fred Yankowski's unit test framework for PHP.</para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm>testsuite.php</glossterm> + <glossdef> + <para>Start of a unit test suite for this software + package. If you do development on this software, please + consider submitting tests for this suite.</para> + </glossdef> + </glossentry> + <glossentry> + <glossterm>which.php</glossterm> + <glossdef> + <para>A demo of the + <function>interopEchoTests.whichToolkit</function> + method.</para> + </glossdef> + </glossentry> + <glossentry> + <glossterm>discuss.php, comment.php</glossterm> + <glossdef> + <para>Software used in the PHP chapter of <xref linkend="jellyfish"> to + provide a comment server and allow the attachment of + comments to stories from Meerkat's data store.</para> + </glossdef> + </glossentry> + + <glossentry> + <glossterm>rsakey.pem</glossterm> + <glossdef> + <para>A test certificate for the SSL support. It has the + passphrase "test."</para> + </glossdef> + </glossentry> + </glosslist> + </chapter> + <chapter id="bugs"> + <title>Bugs</title> + <para>This is a bare framework. The "nice" bits haven't been put in + yet. Specifically, no HTTP response checking is performed, and no type + validation or coercion has been put in. PHP being a loosely-typed + language, this is going to have to be done explicitly.</para> + <para>dateTime.iso8601 is supported opaquely. It can't be done + natively as the XML-RPC specification explictly forbids passing of + timezone specifiers in ISO8601 format dates. You can, however, use + the <xref linkend="iso8601encode"> and <xref + linkend="iso8601decode"> functions to do the encoding and decoding for you.</para> + <para>If alternative character set encoding is sent in HTTP header + than it will be ignored for the moment. We speak only UTF-8...</para> + <para>If more than 32k of HTTP headers are encountered (like, why?) then the + response parsing code will break.</para> + + </chapter> + <chapter id="support"> + <title>Support</title> + <sect1> + <title>Online Support</title> + + <para>XML-RPC for PHP is offered "as-is" without any warranty or + commitment to support. However, informal advice and help is + available via the XML-RPC for PHP mailing list and XML-RPC.com. + </para> + <itemizedlist> + <listitem> + <para>The <emphasis>PHP XML-RPC interest mailing list</emphasis> + is run by the author. More details <ulink + url="http://www.usefulinc.com/xmlrpc/list.html">can be found + here</ulink>.</para> + </listitem> + <listitem><para>For more general XML-RPC questions, there is a + Yahoo! Groups <ulink + url="http://groups.yahoo.com/group/xml-rpc/">XML-RPC mailing list</ulink>.</para> + <listitem> + <para>The <ulink + url="http://www.xmlrpc.com/discuss">XML-RPC.com</ulink> + discussion group is a useful place to get help with using + XML-RPC. This group is also gatewayed into the Yahoo! Groups mailing list.</para> + </listitem> + </itemizedlist> + </sect1> + <sect1 id="jellyfish" xreflabel="The + Jellyfish Book"> + <title>The Jellyfish Book</title> + <para> + <graphic + fileref="http://www.oreilly.com/catalog/covers/progxmlrpc.s.gif" + format="gif" width="145" depth="190" align="right"></graphic> + Together with Simon St.Laurent and Joe Johnston, I wrote a + book on XML-RPC for O'Reilly and Associates on XML-RPC. It + features a rather fetching jellyfish on the cover. + </para> + <para>Complete details of the book are + <ulink + url="http://www.oreilly.com/catalog/progxmlrpc/">available + from O'Reilly's web site.</ulink> + </para> + <para>I'm responsible for the chapter on PHP, which includes a + worked example of creating a forum server, and hooking it up + the O'Reilly's <ulink + url="http://meerkat.oreillynet.com/">Meerkat</ulink> service + in order to allow commenting on news stories from around the + Web.</para> + <para>If you've benefitted from the effort I've put into writing + this software, then please consider buying the book!</para> + </sect1> + </chapter> + <chapter id="apidocs"> + <title>Class documentation</title> + <sect1 id="xmlrpc-client" xreflabel="xmlrpc_client"> + <title>xmlrpc_client</title> + <para>This is the basic class used to represent a client of an + XML-RPC server.</para> + <sect2> + <title>Creation</title> + <para>The constructor has the following syntax:</para> + <funcsynopsis> + <funcprototype> + <funcdef>$client=new xmlrpc_client</funcdef> + <paramdef><parameter>$server_path</parameter></paramdef> + <paramdef><parameter>$server_hostname</parameter></paramdef> + <paramdef><parameter>$server_port</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>Here's an example client set up to query Userland's XML-RPC + server at <emphasis>betty.userland.com</emphasis>:</para> + <programlisting> +$client=new xmlrpc_client("/RPC2", "betty.userland.com", 80);</programlisting> + <para>The <parameter>server_port</parameter> parameter is + optional, and if omitted will default to 80 when using + HTTP and 443 when using HTTPS (see the "send" method below.)</para> + + </sect2> + <sect2> + <title>Methods</title> + <para>This class supports the following methods.</para> + <sect3 id="xmlrpc-client-send" xreflabel="xmlrpc_client->send"> + <title>send</title> + <para>This method takes the form:</para> + <funcsynopsis> + <funcprototype> + <funcdef>$response=$client->send</funcdef> + <paramdef><parameter>$xmlrpc_message</parameter></paramdef> + <paramdef><parameter>$timeout</parameter></paramdef> + <paramdef><parameter>$server_method</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <para>Where <parameter>$xmlrpc_message</parameter> is an + instance of <classname>xmlrpcmsg</classname> (see <xref + linkend="xmlrpcmsg">), and + <parameter>$response</parameter> is an + instance of <classname>xmlrpcresp</classname> (see <xref + linkend="xmlrpcresp">).</para> + + <para>The <parameter>$timeout</parameter> is optional, and + will be set to <literal>0</literal> (wait forever) if + omitted. This timeout value is passed to + <function>fsockopen()</function>.</para> + + <para>The <parameter>server_method</parameter> parameter is + optional, and if omitted will default to 'http'. The only + other valid value is 'https', which will use an SSL HTTP + connection to connect to the remote server. Note that your + PHP must have the "curl" extensions compiled in in order to + use this feature. Note that when using SSL you should + normally set your port number to 443, unless the SSL server + you are contacting runs at any other port.</para> + <warning> + <para>PHP 4.0.2 or greater is required for SSL + functionality. + PHP 4.0.6 has a bug which prevents SSL + working.</para> + </warning> + <para>If the value of <parameter>$response</parameter> is + <literal>0</literal> rather than an + <classname>xmlrpcresp</classname> object, then this + signifies an I/O error has occured. You can find out what + the I/O error was from the values + <function>$client->errno</function> and + <function>$client->errstring</function>. + </para> + <para>In addition to low-level errors, the XML-RPC server you + were querying may return an error in the + <classname>xmlrpcresp</classname> object. See <xref + linkend="xmlrpcresp"> for details of + how to handle these errors. + </para> + </sect3> + + <sect3> + <title>setCredentials</title> + <funcsynopsis> + <funcprototype> + <funcdef>$client->setCredentials</funcdef> + <paramdef><parameter>$username</parameter></paramdef> + <paramdef><parameter>$password</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>This method sets the username and password for authorizing the + client to a server. With the default (HTTP) transport, this + information is used for HTTP Basic authorization. + + </para> + </sect3> + <sect3> + <title>setCertificate</title> + <funcsynopsis> + <funcprototype> + <funcdef>$client->setCertificate</funcdef> + <paramdef><parameter>$certificate</parameter></paramdef> + <paramdef><parameter>$passphrase</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>This method sets the optional certificate and passphrase + used in SSL-enabled communication with a remote server + (when the <parameter>server_method</parameter> is set to + 'https' in the client's construction). + </para> + <para>The <parameter>certificate</parameter> parameter must + be the filename of a PEM formatted certificate. The + <parameter>passphrase</parameter> parameter must contain + the password required to use the certificate.</para> + <para>This requires the "curl" extensions to be compiled + into your installation of PHP.</para> + </sect3> + <sect3> + <title>setSSLVerifyPeer</title> + <funcsynopsis> + <funcprototype> + <funcdef>$client->setSSLVerifyPeer</funcdef> + <paramdef><parameter>$i</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>This method defines whether connections made to XMLRPC + backends via HTTPS should verify the remote host's SSL + certificate, and cause the connection to fail if the cert + verification fails. $i should be a boolean value. + </para> + </sect3> + <sect3> + <title>setSSLVerifyHost</title> + <funcsynopsis> + <funcprototype> + <funcdef>$client->setSSLVerifyHost</funcdef> + <paramdef><parameter>$i</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>This method defines whether connections made to XMLRPC + backends via HTTPS should verify the remote host's SSL + certificate's common name (CN). By default, only the existence + of a CN is checked. $i should be an integer value; 0 to not + check the CN at all, 1 to merely check for its existence, and + 2 to check that the CN on the certificate matches the hostname + that is being connected to. + </para> + </sect3> + + + <sect3> + <title>setDebug</title> + <funcsynopsis> + <funcprototype> + <funcdef>$client->setDebug</funcdef> + <paramdef><parameter>$debugOn</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para><parameter>$debugOn</parameter> is either + <literal>0</literal> or <literal>1</literal> depending on + whether you require the client to print debugging + information to the browser. The default is not to output + this information.</para> + <para> + The debugging information includes the raw data returned + from the XML-RPC server it was querying, and the PHP value + the client attempts to create to represent the value + returned by the server. This option can be very useful when + debugging servers as it allows you to see exactly what the + server returns. + </para> + </sect3> + </sect2> + </sect1> + <sect1 id="xmlrpcmsg" xreflabel="xmlrpcmsg"> + <title>xmlrpcmsg</title> + <para>This class provides a representation for a request to an + XML-RPC server. A client sends an + <classname>xmlrpcmsg</classname> to a server, and receives back + an <classname>xmlrpcresp</classname> (see <xref + linkend="xmlrpc-client-send">).</para> + <sect2> + <title>Creation</title> + <para>The constructor takes the following form:</para> + <funcsynopsis> + <!-- one of (funcprototype funcdef) --> + <funcprototype> + <funcdef>$msg=new xmlrpcmsg</funcdef> + <paramdef><parameter>$methodName</parameter></paramdef> + <paramdef><parameter>$parameterArray</parameter></paramdef> + </funcprototype> + </funcsynopsis> + + <para>Where <parameter>$methodName</parameter> is a string + indicating the name of the method you wish to invoke, and + <parameter>$parameterArray</parameter> is a simple + <classname>Array</classname> of + <classname>xmlrpcval</classname> objects. Here's an example + message to the <emphasis>US state name</emphasis> server: + </para> + <programlisting> +$msg=new xmlrpcmsg("examples.getStateName", + array(new xmlrpcval(23, "int"))); + </programlisting> + <para> + This example requests the name of state number 23. For more + information on <classname>xmlrpcval</classname> objects, see + <xref linkend="xmlrpcval">. + </para> + </sect2> + <sect2> + <title>Methods</title> + <sect3> + <title>serialize</title> + <funcsynopsis> + <funcprototype> + <funcdef>$outString=$msg->serialize</funcdef> + <paramdef></paramdef> + </funcprototype> + </funcsynopsis> + <para>Returns the an XML string representing the XML-RPC + message.</para> + </sect3> + <sect3> + <title>addParam</title> + <funcsynopsis> + <funcprototype> + <funcdef>$msg->addParam</funcdef> + <paramdef><parameter>$xmlrpcVal</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>Adds the <classname>xmlrpcval</classname> + <parameter>$xmlrpcVal</parameter> to the parameter list for + this method call.</para> + </sect3> + <sect3> + <title>getParam</title> + <funcsynopsis> + <funcprototype> + <funcdef>$xmlrpcVal=$msg->getParam</funcdef> + <paramdef><parameter>$n</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>Gets the <parameter>$n</parameter>th parameter in the + message. Use this method in server implementations. Returns + the <literal>undef</literal> value if no such parameter + exists.</para> + </sect3> + <sect3> + <title>getNumParams</title> + <funcsynopsis> + <funcprototype> + <funcdef>$n=$msg->getNumParams</funcdef> + <paramdef></paramdef> + </funcprototype> + </funcsynopsis> + <para> + Returns the number of parameters attached to this message. + </para> + </sect3> + <sect3> + <title>method</title> + <funcsynopsis> + <funcprototype> + <funcdef>$methName=$msg->method</funcdef> + <paramdef></paramdef> + </funcprototype> + <funcprototype> + <funcdef>$msg->method</funcdef> + <paramdef><parameter>$methName</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>Gets or sets the method contained in the XML-RPC message.</para> + </sect3> + <sect3> + <title>parseResponse</title> + <funcsynopsis> + + <funcprototype> + <funcdef>$response=$msg->parseResponse</funcdef> + <paramdef><parameter>$xmlString</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>Given an incoming XML-RPC server response contained in + the string + <parameter>$xmlString</parameter>, this method constructs + an <classname>xmlrpcresp</classname> response object and + returns it, setting error codes as appropriate (see <xref + linkend="xmlrpc-client-send">). + </para> + <para> + This method processes any HTTP/MIME headers it finds. + </para> + </sect3> + <sect3> + <title>parseResponseFile</title> + <funcsynopsis> + <funcprototype> + <funcdef>$response=$msg->parseResponseFile</funcdef> + <paramdef><parameter>$fileHandle</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>Given an incoming XML-RPC server response on the file handle + <parameter>$fileHandle</parameter>, this method reads the + data and passes it to <function>parseResponse</function> + </para> + <para> + This method is useful to construct responses from + pre-prepared files (see files <literal>demo1.txt, demo2.txt, + demo3.txt</literal> in this distribution). It processes + any HTTP headers it finds. + </para> + </sect3> + </sect2> + </sect1> + <sect1 id="xmlrpcresp" xreflabel="xmlrpcresp"> + <title>xmlrpcresp</title> + <para>This class is used to contain responses to XML-RPC + requests. A server method handler will construct an + <classname>xmlrpcresp</classname> and pass it as a return value. + This same value will be returned by the result of an invocation of + the <function>send</function> method of the + <classname>xmlrpc_client</classname> class.</para> + <sect2> + <title>Creation</title> + <funcsynopsis> + <!-- one of (funcprototype funcdef) --> + <funcprototype> + <funcdef>$resp=new xmlrpcresp</funcdef> + <!-- one of (paramdef varargs void) --> + <paramdef><parameter>$xmlrpcval</parameter></paramdef> + </funcprototype> + <funcprototype> + <funcdef>$resp=new xmlrpcresp</funcdef> + <paramdef><parameter>0</parameter></paramdef> + <paramdef><parameter + >$errcode</parameter></paramdef> + <paramdef><parameter + >$errstring</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>The first instance is used when execution has happened + without difficulty: <parameter>$xmlrpcval</parameter> is an + <classname>xmlrpcval</classname> value with the result of the + method execution contained in it.</para> + <para> + The second type of constructor is used in case of + failure. <parameter>$errcode</parameter> and + <parameter>$errstring</parameter> are used to provide + indication of what has gone wrong. See <xref + linkend="xmlrpc-server"> for more information on passing + error codes. + </para> + </sect2> + <sect2> + <title>Methods</title> + <sect3> + <title>faultCode</title> + <funcsynopsis> + <funcprototype> + <funcdef>$fn=$resp->faultCode</funcdef> + <paramdef></paramdef> + </funcprototype> + </funcsynopsis> + <para>Returns the integer fault code return from the XML-RPC + response <parameter>$resp</parameter>. + A zero value indicates success, any other value + indicates a failure response.</para> + </sect3> + <sect3> + <title>faultString</title> + <funcsynopsis> + <funcprototype> + <funcdef>$fs=$resp->faultString</funcdef> + <paramdef></paramdef> + </funcprototype> + </funcsynopsis> + <para> + Returns the human readable explanation of the fault + indicated by <function>$resp->faultCode</function>. + </para> + </sect3> + <sect3> + <title>value</title> + <funcsynopsis> + <funcprototype> + <funcdef>$xmlrpcVal=$resp->value</funcdef> + <paramdef></paramdef> + </funcprototype> + </funcsynopsis> + <para> + Returns an <classname>xmlrpcval</classname> object + containing the return value sent by the server. If the + response's <function>faultCode</function> is non-zero then + the value returned by this method should not be used (it may + not even be an object). + </para> + </sect3> + <sect3> + <title>serialize</title><para></para> + <funcsynopsis> + <funcprototype> + <funcdef>$outString=$resp->serialize</funcdef> + <paramdef></paramdef> + </funcprototype> + </funcsynopsis> + <para>Returns an XML string representation of the response.</para> + </sect3> + </sect2> + </sect1> + <sect1 id="xmlrpcval" xreflabel="xmlrpcval"> + <title>xmlrpcval</title> + <para>This is where a lot of the hard work gets done. This class + enables the creation and encapsulation of values for XML-RPC. + </para> + <para> + Ensure you've read the XML-RPC spec at <ulink + url="http://www.xmlrpc.com/stories/storyReader$7">http://www.xmlrpc.com/stories/storyReader$7</ulink> + before reading on as it will make things clearer. + </para> + <para>The <classname>xmlrpcval</classname> class can store + arbitrarily complicated values using the following types: + <literal>i4 int boolean string double dateTime.iso8601 base64 + array struct</literal>. You should refer to the <ulink + url="http://www.xmlrpc.com/stories/storyReader$7">spec</ulink> + for more information on what each of these types mean. + </para> + <sect2> + <title>Notes on types</title> + <sect3> + <title>int</title> + <para>The type <classname>i4</classname> is accepted as a + synonym for <classname>int</classname>. The value parsing + code will always convert <classname>i4</classname> to + <classname>int</classname>: <classname>int</classname> + is regarded by this implementation as the canonical name for + this type.</para> + </sect3> + <sect3> + <title>base64</title> + <para>Base 64 encoding is performed transparently to the + caller when using this type. Therefore you ought to + consider it as a "binary" data type, for use when you want + to pass none 7-bit clean data. Decoding is also + transparent. + </para> + </sect3> + <sect3> + <title>boolean</title> + <para>The values <literal>true</literal> and + <literal>1</literal> map to <literal>true</literal>. All + other values (including the empty string) + are converted to <literal>false</literal>. + </para> + </sect3> + <sect3> + <title>string</title> + <para> + The characters <literal>< > "</literal> and + <literal>&</literal> are converted to their entity + equivalents <literal>&lt; &gt; + &quot;</literal> and <literal>&amp;</literal> + for transport through XML-RPC. The current XML-RPC spec + recommends only encoding <literal>< &</literal> but + this implementation goes further, for reasons explained by + <ulink url="http://www.w3.org/TR/REC-xml#syntax">the XML 1.0 + recommendation</ulink>. + </para><para>TODO: <literal> &apos;</literal> entity not + yet supported</para> + </sect3> + </sect2> + <sect2 id="xmlrpcval-creation" xreflabel="xmlrpcval constructors"> + <title>Creation</title> + <para>The constructor is the normal way to create an + <classname>xmlrpcval</classname>. The constructor can take + these forms: + </para> + <funcsynopsis> + <!-- one of (funcprototype funcdef) --> + <funcprototype> + <funcdef>$myVal=new xmlrpcval</funcdef> + <!-- one of (paramdef varargs void) --> + <paramdef></paramdef> + </funcprototype> + <funcprototype> + <funcdef>$myVal=new xmlrpcval</funcdef> + <!-- one of (paramdef varargs void) --> + <paramdef><parameter>$stringVal</parameter></paramdef> + </funcprototype> + <funcprototype> + <funcdef>$myVal=new xmlrpcval</funcdef> + <!-- one of (paramdef varargs void) --> + <paramdef><parameter>$scalarVal</parameter></paramdef> + <paramdef><parameter>"int" | "boolean" | "string" | "double" | "dateTime.iso8601" | "base64"</parameter></paramdef> + </funcprototype> + <funcprototype> + <funcdef>$myVal=new xmlrpcval</funcdef> + <!-- one of (paramdef varargs void) --> + <paramdef><parameter>$arrayVal</parameter></paramdef> + <paramdef><parameter>"array" | "struct"</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>The first constructor creates an empty value, which must + be altered using the methods <function>addScalar</function>, + <function>addArray</function> or + <function>addStruct</function> before it can be used. + </para> + <para> + The second constructor creates a simple string value. + </para> + <para> + The third constructor is used to create a scalar value. The + second parameter must be a name of an XML-RPC type. Examples: + </para> + <programlisting> + $myInt=new xmlrpcvalue(1267, "int"); + $myString=new xmlrpcvalue("Hello, World!", "string"); + $myBool=new xmlrpcvalue(1, "boolean"); + </programlisting> + + <para> + The fourth constructor form can be used to compose complex + XML-RPC values. The first argument is either a simple array in + the case of an XML-RPC <classname>array</classname> or + an associative array in the case of a + <classname>struct</classname>. The elements of the array + <emphasis>must be <classname>xmlrpcval</classname> objects + themselves</emphasis>. + Examples:</para> + <programlisting> + $myArray=new xmlrpcval(array( + new xmlrpcval("Tom"), new xmlrpcval("Dick"), + new xmlrpcval("Harry")), "array"); + + $myStruct=new xmlrpcval(array( + "name" => new xmlrpcval("Tom"), + "age" => new xmlrpcval(34, "int"), + "geek" => new xmlrpcval(1, "boolean")), "struct"); + </programlisting> + <para>See the file <literal>vardemo.php</literal> in this + distribution for more examples.</para> + </sect2> + <sect2 id="xmlrpcval-methods" xreflabel="xmlrpcval methods"> + <title>Methods</title> + <sect3> + <title>addScalar</title> + <funcsynopsis> + <!-- one of (funcprototype funcdef) --> + <funcprototype> + <funcdef>$ok=$val->addScalar</funcdef> + <!-- one of (paramdef varargs void) --> + <paramdef><parameter>$stringVal</parameter></paramdef> + </funcprototype> + <funcprototype> + <funcdef>$ok=$val->addScalar</funcdef> + <!-- one of (paramdef varargs void) --> + <paramdef><parameter>$scalarVal</parameter></paramdef> + <paramdef><parameter>"int" | "boolean" | "string" | "double" | "dateTime.iso8601" | "base64"</parameter></paramdef> + </funcprototype> + + </funcsynopsis> + <para> + If <parameter>$val</parameter> is an empty + <classname>xmlrpcval</classname> this method makes it a + scalar value, and sets that value. If + <parameter>$val</parameter> is already a scalar value, then + no more scalars can be added and <literal>0</literal> is + returned. If all went OK, <literal>1</literal> is returned. + </para> + <para>There is a special case if <parameter>$val</parameter> + is an <classname>array</classname>: the scalar value passed + is appended to the array.</para> + </sect3> + <sect3> + <title>addArray</title> + <funcsynopsis> + <funcprototype> + <funcdef>$ok=$val->addArray</funcdef> + <paramdef><parameter>$arrayVal</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>Turns an empty <classname>xmlrpcval</classname> into an + <classname>array</classname> with contents as specified by + <parameter>$arrayVal</parameter>. See the fourth + constructor form for more information.</para> + </sect3> + <sect3> + <title>addStruct</title> + <funcsynopsis> + <funcprototype> + <funcdef>$ok=$val->addArray</funcdef> + <paramdef><parameter>$assocArrayVal</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>Turns an empty <classname>xmlrpcval</classname> into a + <classname>struct</classname> with contents as specified by + <parameter>$assocArrayVal</parameter>. See the fourth + constructor form for more information.</para> + </sect3> + <sect3> + <title>kindOf</title> + <funcsynopsis> + <funcprototype> + <funcdef>$kind=$val->kindOf</funcdef> + <paramdef></paramdef> + </funcprototype> + </funcsynopsis> + <para> + Returns a string containing "struct", "array" or "scalar" + describing the base type of the value. If it returns + "undef" it means that the value hasn't been initialised. + </para> + </sect3> + <sect3> + <title>serialize</title> + <funcsynopsis> + <funcprototype> + <funcdef>$outString=$val->serialize</funcdef> + <paramdef></paramdef> + </funcprototype> + </funcsynopsis> + <para> + Returns a string containing the XML-RPC representation of + this value. + </para> + </sect3> + <sect3> + <title>scalarval</title> + <funcsynopsis> + <funcprototype> + <funcdef>$scalarVal=$val->scalarval</funcdef> + <paramdef></paramdef> + </funcprototype> + </funcsynopsis> + <para> + If <function>$val->kindOf()=="scalar"</function>, this + method returns the actual PHP-language value of the scalar + (base 64 decoding is automatically handled here). + </para> + </sect3> + <sect3> + <title>scalartyp</title> + <funcsynopsis> + <funcprototype> + <funcdef>$typeName=$val->scalartyp</funcdef> + <paramdef></paramdef> + </funcprototype> + </funcsynopsis> + <para> + If <function>$val->kindOf()=="scalar"</function>, this + method returns a string denoting the type of the scalar. + As mentioned before, + <literal>i4</literal> is always coerced to <literal>int</literal>. + </para> + </sect3> + <sect3> + <title>arraymem</title> + <funcsynopsis> + <funcprototype> + <funcdef>$xmlrpcVal=$val->arraymem</funcdef> + <paramdef><parameter>$n</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para> + Returns the <parameter>$n</parameter>th element in the array + represented by the value <parameter>$val</parameter>. The + value returned is an <classname>xmlrpcval</classname> object. + </para> + </sect3> + <sect3> + <title>arraysize</title> + <funcsynopsis> + <funcprototype> + <funcdef>$len=$val->arraysize</funcdef> + <paramdef></paramdef> + </funcprototype> + </funcsynopsis> + <para>If <parameter>$val</parameter> is an + <classname>array</classname>, returns the number of elements + in that array. + </para> + </sect3> + <sect3> + <title>structmem</title> + <funcsynopsis> + <funcprototype> + <funcdef>$xmlrpcVal=$val->structmem</funcdef> + <paramdef><parameter>$memberName</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para> + Returns the element called + <parameter>$memberName</parameter> from the struct + represented by the value <parameter>$val</parameter>. The + value returned is an <classname>xmlrpcval</classname> object. + </para> + </sect3> + <sect3> + <title>structeach</title> + <funcsynopsis> + <funcprototype> + <funcdef>list($key,$value)=$val->structeach</funcdef> + <paramdef></paramdef> + </funcprototype> + </funcsynopsis> + <para> + Returns the next (key,value) pair from the struct, when + <parameter>$val</parameter> is a struct. See also + <xref linkend="structreset">. + </para> + </sect3> + + <sect3 id="structreset" xreflabel="structreset()"> + <title>structreset</title> + <funcsynopsis> + <funcprototype> + <funcdef>$val->structreset</funcdef> + <paramdef></paramdef> + </funcprototype> + </funcsynopsis> + <para> + Resets the internal pointer for + <function>structeach()</function> to the beginning of the + struct, where <parameter>$val</parameter> is a struct. + </para> + </sect3> + + </sect2> + </sect1> + <sect1 id="xmlrpc-server" xreflabel="xmlrpc_server"> + <title>xmlrpc_server</title> + <para>The current implementation of this class has been + kept as simple as possible. The constructor for the server + basically does all the work. Here's a minimal example:</para> + <programlisting> + function foo ($params) { + ... + } + + $s=new xmlrpc_server( array("examples.myFunc" => + array("function" => "foo"))); + </programlisting> + <para> + This performs everything you need to do with a server. The single + argument is an associative array from method names to function + names. The request is parsed and despatched to the relevant function, + which is reponsible for returning a + <classname>xmlrpcresp</classname> + object, which gets + serialized back to the caller. See server.php in this distribution for + examples of how to do this. + </para> + <para>Here is a more detailed look at what the handler function + <function>foo</function> may do:</para> + <programlisting> + + function foo ($params) { + global $xmlrpcerruser; // import user errcode value + + // $params is an Array of xmlrpcval objects + + if ($err) { + // this is an error condition + return new xmlrpcresp(0, $xmlrpcerruser+1, // user error 1 + "There's a problem, Captain"); + } else { + // this is a successful value being returned + return new xmlrpcresp(new xmlrpcval("All's fine!", "string")); + } + } + </programlisting> + <sect2> + <title>The dispatch map</title> + <para>The first argument to the + <function>xmlrpc_server</function> constructor is an array, + called the <emphasis>dispatch map</emphasis>. In this array is the + information the server needs to service the XML-RPC methods + you define.</para> + <para> + The dispatch map takes the form of an associative array of + associative arrays: the outer array has one entry for each + method, the key being the method name. The corresponding value + is another associative array, which can have the following members: + </para> + <itemizedlist> + <listitem> + <para><function>function</function> - this entry is + mandatory. It must be a name of a function in the + global scope which services the XML-RPC method.</para> + </listitem> + <listitem> + <para><function>signature</function> - this entry is an + array containg the possible signatures (see <xref + linkend="signatures">) for the method. If + this entry is present then the server will check that the + correct number and type of parameters have been sent for + this method before dispatching it. + </para> + </listitem> + <listitem> + <para> + <function>docstring</function> - this entry is a string + containing documentation for the method. The + documentation may contain HTML markup. + </para> + </listitem> + </itemizedlist> + <para>Look at the <filename>server.php</filename> example in the + distribution to see what a dispatch map looks like.</para> + </sect2> + <sect2 id="signatures" xreflabel="Signatures"><title>Method signatures</title> + <para>A signature is a description of a method's return type and + its parameter types. A method may have more than one + signature.</para> + <para>Within a server's dispatch map, each method has an array + of possible signatures. Each signature is an array of + types. The first entry is the return type. For instance, the + method <programlisting> +string examples.getStateName(int) +</programlisting> has the signature +<programlisting> +array($xmlrpcString, $xmlrpcInt) +</programlisting> and, assuming that it the only possible signature for + the method, might be used like this in server creation: +<programlisting> +$findstate_sig=array(array($xmlrpcString, $xmlrpcInt)); + +$findstate_doc='When passed an integer between 1 and 51 returns the +name of a US state, where the integer is the index of that state name +in an alphabetic order.'; + +$s=new xmlrpc_server( array( "examples.getStateName" => + array("function" => "findstate", + "signature" => $findstate_sig, + "docstring" => $findstate_doc))); +</programlisting> + + </para> + <para>For convenience the strings representing the XML-RPC types + have been encoded as global variables:<programlisting> +$xmlrpcI4="i4"; +$xmlrpcInt="int"; +$xmlrpcBoolean="boolean"; +$xmlrpcDouble="double"; +$xmlrpcString="string"; +$xmlrpcDateTime="dateTime.iso8601"; +$xmlrpcBase64="base64"; +$xmlrpcArray="array"; +$xmlrpcStruct="struct"; +</programlisting></para> + </sect2> + <sect2> + <title>Delaying the server response</title> + <para>You may want to construct the server, but for some reason + not fulfill the request immediately (security verification, for + instance). If you pass the constructor a second argument of + <literal>0</literal> this will have the desired effect. You + can then use the <function>service()</function> method of the + server class to service the request. For example:</para> + <programlisting> +$s=new xmlrpc_server($myDispMap, 0); + +// ... some code that does other stuff here + +$s->service(); +</programlisting> + </sect2> + <sect2> + <title>Fault reporting</title> + <para>Fault codes for your servers should start at the + value indicated by + the global <literal>$xmlrpcerruser</literal> + 1.</para> + <para>Standard errors returned by the server include:</para> + <variablelist> + <varlistentry> + <term><literal>1</literal> <phrase>Unknown method</phrase></term> + <listitem> + <para>Returned if the server was asked to dispatch a + method it didn't know about</para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>2</literal> <phrase>Invalid return payload</phrase></term> + <listitem> + <para>This error is actually generated by the client, not + server, code, but signifies that a server returned + something it couldn't understand.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>3</literal> <phrase>Incorrect parameters</phrase></term> + <listitem> + <para>This error is generated when the server has signature(s) + defined for a method, and the parameters passed by the + client do not match any of signatures.</para> + </listitem> + </varlistentry> + <varlistentry> + <term><literal>4</literal> <phrase>Can't introspect: method unknown</phrase></term> + <listitem> + <para>This error is generated by the builtin + <function>system.*</function> methods when any kind of + introspection is attempted on a method undefined by the + server.</para> + </listitem> + </varlistentry> +<varlistentry> + <term><literal>5</literal> <phrase>Didn't receive 200 OK from remote server</phrase></term> + <listitem> + <para>This error is generated by the client when a remote server + doesn't return HTTP/1.1 200 OK in response to a + request. A more detailed error report is added onto the + end of the phrase above.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>100-</literal> <phrase>XML parse errors</phrase></term> + <listitem> + <para>Returns 100 plus the XML parser error code for the + fault that occurred. The + <function>faultString</function> returned explains where + the parse error was in the incoming XML stream.</para> + </listitem> + </varlistentry> + </variablelist> + </sect2> + </sect1> + </chapter> + + <chapter id="helpers"> + <title>Helper functions</title> + <para>XML-RPC for PHP contains some helper functions which you can + use to make processing of XML-RPC requests easier.</para> + <sect1> + <title>Date functions</title> + <para>The XML-RPC specification has this to say on dates:</para> + <blockquote> + <para>Don't assume a timezone. It should be specified by the server in its + documentation what assumptions it makes about timezones. </para> + </blockquote> + <para>Unfortunately, this means that date processing isn't + straightforward. Although XML-RPC uses ISO 8601 format dates, it + doesn't use the timezone specifier.</para> + <para>We strongly recommend that in every case where you pass + dates in XML-RPC calls, you use UTC (GMT) as your timezone. Most computer + languages include routines for handling GMT times natively, and + you won't have to translate between timezones.</para> + <para>For more information about dates, see <ulink + url="http://www.uic.edu/year2000/datefmt.html">ISO 8601: The Right Format for Dates</ulink>, which has a handy link to a PDF of the ISO 8601 specification. Note that XML-RPC uses exactly one of the available representations: CCYYMMDDTHH:MM:SS.</para> + + <sect2 id="iso8601encode" xreflabel="iso8601_encode()"> + <title>iso8601_encode</title> + <funcsynopsis> + <funcprototype> + <funcdef>$isoString=iso8601_encode</funcdef> + <paramdef><parameter>$time_t</parameter><parameter>$utc=0</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>Returns an ISO 8601 formatted date generated from the + UNIX timestamp <parameter>$time_t</parameter>, as returned by + the PHP function <function>time()</function>. </para> + <para>The argument <parameter>$utc</parameter> can be omitted, + in which case it defaults to <literal>0</literal>. If it is + set to <literal>1</literal>, then the function corrects the + time passed in for UTC. Example: if you're in the GMT-6:00 + timezone and set <parameter>$utc</parameter>, you will receive + a date representation six hours ahead of your local + time.</para> + <para>The included demo program <filename>vardemo.php</filename> + includes a demonstration of this function.</para> + </sect2> + <sect2 id="iso8601decode" xreflabel="iso8601_decode()"> + <title>iso8601_decode</title> + <funcsynopsis> + <funcprototype> + <funcdef>$time_t=iso8601_decode</funcdef> + <paramdef><parameter>$isoString</parameter><parameter>$utc=0</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>Returns a UNIX timestamp from an ISO 8601 encoded time and + date string passed in. If <parameter>$utc</parameter> is + <literal>1</literal> then <parameter>$isoString</parameter> is + assumed to be in the UTC timezone, and thus the + <parameter>$time_t</parameter> result is also UTC: otherwise, + the timezone is assumed to be your local timezone and you receive a local timestamp.</para> + </sect2> + </sect1> + + <sect1 id="arrayuse"> + <title>Easy use with PHP arrays</title> + <para>Dan Libby was kind enough to contribute two helper functions + that make it easier to translate to and from PHP arrays. This + makes it easier to deal with complex structures. At the moment + support is limited to <type>int</type>, <type>double</type>, + <type>string</type>, <type>array</type> and <type>struct</type> + datatypes; note also that all PHP arrays are encoded as structs + due to PHP not being able to tell the difference between a hash + and a linear array.</para> + <para>These functions reside in <filename>xmlrpc.inc</filename>.</para> + <sect2 id="xmlrpcdecode"> + <title>xmlrpc_decode</title> + <funcsynopsis> + <funcprototype> + <funcdef>$arr=xmlrpc_decode</funcdef> + <paramdef><parameter>$xmlrpc_val</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para> + Returns a PHP array stuffed with the values found in the + <type>xmlrpcval</type> <parameter>$xmlrpc_val</parameter>, + translated into native PHP types. + </para> + </sect2> + <sect2 id="xmlrpcencode"> + <title>xmlrpc_encode</title> + <funcsynopsis> + <funcprototype> + <funcdef>$xmlrpc_val=xmlrpc_encode</funcdef> + <paramdef><parameter>$phpval</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para> + Returns an <type>xmlrpcval</type> populated with the PHP + values in <parameter>$phpval</parameter>. Works recursively on + arrays and structs. Note that there's no support for non-base + types like base-64 values or date-times. + </para> + </sect2> + </sect1> + + <sect1 id="debugging"> + <title>Debugging aids</title> + <sect2> + <title>xmlrpc_debugmsg</title> + <funcsynopsis> + <funcprototype> + <funcdef>xmlrpc_debugmsg</funcdef> + <paramdef><parameter>$debugstring</parameter></paramdef> + </funcprototype> + </funcsynopsis> + <para>Sends the contents of <parameter>$debugstring</parameter> + in XML comments in the server return payload. If a PHP client + has debugging turned on, the user will be able to see server + debug information.</para> + <para>Use this function in your methods so you can pass back + diagnostic information. It is only available from + <filename>xmlrpcs.inc</filename>.</para> + </sect2> + </sect1> + + </chapter> + + <chapter id="reserved" xreflabel="Reserved methods"> + <title>Reserved methods</title> + <para>In order to extend the functionality offered by XML-RPC + servers without impacting on the protocol, I've included + experimental support in this release for reserved methods.</para> + <para>All methods starting with <function>system.</function> are + considered reserved by the server. PHP for XML-RPC itself provides + three special methods, detailed in this chapter.</para> + <sect1> + <title>system.listMethods</title> + <para>This method may be used to enumerate the methods implemented + by the XML-RPC server.</para> + <para>The <function>system.listMethods</function> method requires + no parameters. It returns an array of strings, each of which is + the name of a method implemented by the server.</para> + </sect1> + <sect1 id="sysmethodsig"> + <title>system.methodSignature</title> + <para>This method takes one parameter, the name of a method + implemented by the XML-RPC server.</para> + <para>It returns an array of possible signatures for this + method. A signature is an array of types. The first of these + types is the return type of the method, the rest are parameters.</para> + <para>Multiple signatures (ie. overloading) are permitted: this is + the reason that an array of signatures are returned by this + method.</para> + <para>Signatures themselves are restricted to the top level + parameters expected by a method. For instance if a method + expects one array of structs as a parameter, and it returns a + string, its signature is simply "string, array". If it expects + three integers, its signature is "string, int, int, + int".</para> + <para> + If no signature is defined for the method, a none-array value is + returned. Therefore this is the way to test for a non-signature, + if <parameter>$resp</parameter> below is the response object + from a method call to <function>system.methodSignature</function>: + </para> + + <programlisting> +$v=$resp->value(); +if ($v->kindOf()!="array") { + // then the method did not have a signature defined +} + </programlisting> + <para> + See the <filename>introspect.php</filename> demo included in + this distribution for an example of using this method. + </para> + </sect1> + <sect1 id="sysmethhelp"> + <title>system.methodHelp</title> + <para>This method takes one parameter, the name of a method + implemented by the XML-RPC server.</para> + <para> + It returns a documentation string describing the use of that + method. If no such string is available, an empty string is returned. + </para> + <para> + The documentation string may contain HTML markup. + </para> + </sect1> + </chapter> + + + <chapter id="examples" xreflabel="Examples"> + <title>Examples</title> + <para>The best examples are to be found in the sample files + included with the distribution. Some are included here.</para> + <sect1 id="statename"> + <title>XML-RPC client: state name query</title> + <para>Code to get the corresponding + state name from a number (1-50) from Dave Winer's server</para> + <programlisting> + $f=new xmlrpcmsg('examples.getStateName', + array(new xmlrpcval($HTTP_POST_VARS["stateno"], "int"))); + $c=new xmlrpc_client("/RPC2", "betty.userland.com", 80); + $r=$c->send($f); + $v=$r->value(); + if (!$r->faultCode()) { + print "State number ". $HTTP_POST_VARS["stateno"] . " is " . + $v->scalarval() . "<BR>"; + print "<HR>I got this value back<BR><PRE>" . + htmlentities($r->serialize()). "</PRE><HR>\n"; + } else { + print "Fault: "; + print "Code: " . $r->faultCode() . + " Reason '" .$r->faultString()."'<BR>"; + } + </programlisting> + </sect1> + </chapter> +</book> +<!-- Keep this comment at the end of the file +Local variables: +mode: sgml +sgml-omittag:nil +sgml-shorttag:t +sgml-minimize-attributes:nil +sgml-always-quote-attributes:t +sgml-indent-step:2 +sgml-indent-data:t +sgml-parent-document:nil +sgml-exposed-tags:nil +sgml-local-catalogs:nil +sgml-local-ecat-files:nil +sgml-namecase-general:t +sgml-general-insert-case:lower +End: +--> diff --git a/modules/xmlrpc/doc/xmlrpcmsg.html b/modules/xmlrpc/doc/xmlrpcmsg.html new file mode 100644 index 00000000..958ff3a2 --- /dev/null +++ b/modules/xmlrpc/doc/xmlrpcmsg.html @@ -0,0 +1,507 @@ +<HTML +><HEAD +><TITLE +>xmlrpcmsg</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="UP" +TITLE="Class documentation" +HREF="apidocs.html"><LINK +REL="PREVIOUS" +TITLE="Class documentation" +HREF="apidocs.html"><LINK +REL="NEXT" +TITLE="xmlrpcresp" +HREF="xmlrpcresp.html"></HEAD +><BODY +CLASS="SECT1" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="apidocs.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +>Chapter 5. Class documentation</TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="xmlrpcresp.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="XMLRPCMSG" +></A +>xmlrpcmsg</H1 +><P +>This class provides a representation for a request to an + XML-RPC server. A client sends an + <TT +CLASS="CLASSNAME" +>xmlrpcmsg</TT +> to a server, and receives back + an <TT +CLASS="CLASSNAME" +>xmlrpcresp</TT +> (see <A +HREF="apidocs.html#XMLRPC-CLIENT-SEND" +>xmlrpc_client->send</A +>).</P +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="AEN289" +></A +>Creation</H2 +><P +>The constructor takes the following form:</P +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN292" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$msg=new xmlrpcmsg</CODE +>($methodName, $parameterArray);</CODE +></P +><P +></P +></DIV +><P +>Where <TT +CLASS="PARAMETER" +><I +>$methodName</I +></TT +> is a string + indicating the name of the method you wish to invoke, and + <TT +CLASS="PARAMETER" +><I +>$parameterArray</I +></TT +> is a simple + <TT +CLASS="CLASSNAME" +>Array</TT +> of + <TT +CLASS="CLASSNAME" +>xmlrpcval</TT +> objects. Here's an example + message to the <SPAN +CLASS="emphasis" +><I +CLASS="EMPHASIS" +>US state name</I +></SPAN +> server: + </P +><PRE +CLASS="PROGRAMLISTING" +>$msg=new xmlrpcmsg("examples.getStateName", + array(new xmlrpcval(23, "int"))); + </PRE +><P +> This example requests the name of state number 23. For more + information on <TT +CLASS="CLASSNAME" +>xmlrpcval</TT +> objects, see + <A +HREF="xmlrpcval.html" +>xmlrpcval</A +>. + </P +></DIV +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="AEN309" +></A +>Methods</H2 +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN311" +></A +>serialize</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN313" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$outString=$msg->serialize</CODE +>();</CODE +></P +><P +></P +></DIV +><P +>Returns the an XML string representing the XML-RPC + message.</P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN318" +></A +>addParam</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN320" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$msg->addParam</CODE +>($xmlrpcVal);</CODE +></P +><P +></P +></DIV +><P +>Adds the <TT +CLASS="CLASSNAME" +>xmlrpcval</TT +> + <TT +CLASS="PARAMETER" +><I +>$xmlrpcVal</I +></TT +> to the parameter list for + this method call.</P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN328" +></A +>getParam</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN330" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$xmlrpcVal=$msg->getParam</CODE +>($n);</CODE +></P +><P +></P +></DIV +><P +>Gets the <TT +CLASS="PARAMETER" +><I +>$n</I +></TT +>th parameter in the + message. Use this method in server implementations. Returns + the <TT +CLASS="LITERAL" +>undef</TT +> value if no such parameter + exists.</P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN338" +></A +>getNumParams</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN340" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$n=$msg->getNumParams</CODE +>();</CODE +></P +><P +></P +></DIV +><P +> Returns the number of parameters attached to this message. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN345" +></A +>method</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN347" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$methName=$msg->method</CODE +>();</CODE +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$msg->method</CODE +>($methName);</CODE +></P +><P +></P +></DIV +><P +>Gets or sets the method contained in the XML-RPC message.</P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN356" +></A +>parseResponse</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN358" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$response=$msg->parseResponse</CODE +>($xmlString);</CODE +></P +><P +></P +></DIV +><P +>Given an incoming XML-RPC server response contained in + the string + <TT +CLASS="PARAMETER" +><I +>$xmlString</I +></TT +>, this method constructs + an <TT +CLASS="CLASSNAME" +>xmlrpcresp</TT +> response object and + returns it, setting error codes as appropriate (see <A +HREF="apidocs.html#XMLRPC-CLIENT-SEND" +>xmlrpc_client->send</A +>). + </P +><P +> This method processes any HTTP/MIME headers it finds. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN368" +></A +>parseResponseFile</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN370" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$response=$msg->parseResponseFile</CODE +>($fileHandle);</CODE +></P +><P +></P +></DIV +><P +>Given an incoming XML-RPC server response on the file handle + <TT +CLASS="PARAMETER" +><I +>$fileHandle</I +></TT +>, this method reads the + data and passes it to <TT +CLASS="FUNCTION" +>parseResponse</TT +> + </P +><P +> This method is useful to construct responses from + pre-prepared files (see files <TT +CLASS="LITERAL" +>demo1.txt, demo2.txt, + demo3.txt</TT +> in this distribution). It processes + any HTTP headers it finds. + </P +></DIV +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="apidocs.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="xmlrpcresp.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>Class documentation</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="apidocs.html" +ACCESSKEY="U" +>Up</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>xmlrpcresp</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/xmlrpcresp.html b/modules/xmlrpc/doc/xmlrpcresp.html new file mode 100644 index 00000000..d48216e3 --- /dev/null +++ b/modules/xmlrpc/doc/xmlrpcresp.html @@ -0,0 +1,374 @@ +<HTML +><HEAD +><TITLE +>xmlrpcresp</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="UP" +TITLE="Class documentation" +HREF="apidocs.html"><LINK +REL="PREVIOUS" +TITLE="xmlrpcmsg" +HREF="xmlrpcmsg.html"><LINK +REL="NEXT" +TITLE="xmlrpcval" +HREF="xmlrpcval.html"></HEAD +><BODY +CLASS="SECT1" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="xmlrpcmsg.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +>Chapter 5. Class documentation</TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="xmlrpcval.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="XMLRPCRESP" +></A +>xmlrpcresp</H1 +><P +>This class is used to contain responses to XML-RPC + requests. A server method handler will construct an + <TT +CLASS="CLASSNAME" +>xmlrpcresp</TT +> and pass it as a return value. + This same value will be returned by the result of an invocation of + the <TT +CLASS="FUNCTION" +>send</TT +> method of the + <TT +CLASS="CLASSNAME" +>xmlrpc_client</TT +> class.</P +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="AEN386" +></A +>Creation</H2 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN388" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$resp=new xmlrpcresp</CODE +>($xmlrpcval);</CODE +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$resp=new xmlrpcresp</CODE +>(0, $errcode, $errstring);</CODE +></P +><P +></P +></DIV +><P +>The first instance is used when execution has happened + without difficulty: <TT +CLASS="PARAMETER" +><I +>$xmlrpcval</I +></TT +> is an + <TT +CLASS="CLASSNAME" +>xmlrpcval</TT +> value with the result of the + method execution contained in it.</P +><P +> The second type of constructor is used in case of + failure. <TT +CLASS="PARAMETER" +><I +>$errcode</I +></TT +> and + <TT +CLASS="PARAMETER" +><I +>$errstring</I +></TT +> are used to provide + indication of what has gone wrong. See <A +HREF="xmlrpc-server.html" +>xmlrpc_server</A +> for more information on passing + error codes. + </P +></DIV +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="AEN408" +></A +>Methods</H2 +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN410" +></A +>faultCode</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN412" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$fn=$resp->faultCode</CODE +>();</CODE +></P +><P +></P +></DIV +><P +>Returns the integer fault code return from the XML-RPC + response <TT +CLASS="PARAMETER" +><I +>$resp</I +></TT +>. + A zero value indicates success, any other value + indicates a failure response.</P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN418" +></A +>faultString</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN420" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$fs=$resp->faultString</CODE +>();</CODE +></P +><P +></P +></DIV +><P +> Returns the human readable explanation of the fault + indicated by <TT +CLASS="FUNCTION" +>$resp->faultCode</TT +>. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN426" +></A +>value</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN428" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$xmlrpcVal=$resp->value</CODE +>();</CODE +></P +><P +></P +></DIV +><P +> Returns an <TT +CLASS="CLASSNAME" +>xmlrpcval</TT +> object + containing the return value sent by the server. If the + response's <TT +CLASS="FUNCTION" +>faultCode</TT +> is non-zero then + the value returned by this method should not be used (it may + not even be an object). + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN435" +></A +>serialize</H3 +><P +></P +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN438" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$outString=$resp->serialize</CODE +>();</CODE +></P +><P +></P +></DIV +><P +>Returns an XML string representation of the response.</P +></DIV +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="xmlrpcmsg.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="xmlrpcval.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>xmlrpcmsg</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="apidocs.html" +ACCESSKEY="U" +>Up</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>xmlrpcval</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/doc/xmlrpcval.html b/modules/xmlrpc/doc/xmlrpcval.html new file mode 100644 index 00000000..68489699 --- /dev/null +++ b/modules/xmlrpc/doc/xmlrpcval.html @@ -0,0 +1,933 @@ +<HTML +><HEAD +><TITLE +>xmlrpcval</TITLE +><META +NAME="GENERATOR" +CONTENT="Modular DocBook HTML Stylesheet Version 1.77+"><LINK +REV="MADE" +HREF="edd@usefulinc.com"><LINK +REL="HOME" +TITLE="XML-RPC for PHP" +HREF="index.html"><LINK +REL="UP" +TITLE="Class documentation" +HREF="apidocs.html"><LINK +REL="PREVIOUS" +TITLE="xmlrpcresp" +HREF="xmlrpcresp.html"><LINK +REL="NEXT" +TITLE="xmlrpc_server" +HREF="xmlrpc-server.html"></HEAD +><BODY +CLASS="SECT1" +BGCOLOR="#FFFFFF" +TEXT="#000000" +LINK="#0000FF" +VLINK="#840084" +ALINK="#0000FF" +><DIV +CLASS="NAVHEADER" +><TABLE +SUMMARY="Header navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TH +COLSPAN="3" +ALIGN="center" +>XML-RPC for PHP: version 1.1</TH +></TR +><TR +><TD +WIDTH="10%" +ALIGN="left" +VALIGN="bottom" +><A +HREF="xmlrpcresp.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="80%" +ALIGN="center" +VALIGN="bottom" +>Chapter 5. Class documentation</TD +><TD +WIDTH="10%" +ALIGN="right" +VALIGN="bottom" +><A +HREF="xmlrpc-server.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +></TABLE +><HR +ALIGN="LEFT" +WIDTH="100%"></DIV +><DIV +CLASS="SECT1" +><H1 +CLASS="SECT1" +><A +NAME="XMLRPCVAL" +></A +>xmlrpcval</H1 +><P +>This is where a lot of the hard work gets done. This class + enables the creation and encapsulation of values for XML-RPC. + </P +><P +> Ensure you've read the XML-RPC spec at <A +HREF="http://www.xmlrpc.com/stories/storyReader$7" +TARGET="_top" +>http://www.xmlrpc.com/stories/storyReader$7</A +> + before reading on as it will make things clearer. + </P +><P +>The <TT +CLASS="CLASSNAME" +>xmlrpcval</TT +> class can store + arbitrarily complicated values using the following types: + <TT +CLASS="LITERAL" +>i4 int boolean string double dateTime.iso8601 base64 + array struct</TT +>. You should refer to the <A +HREF="http://www.xmlrpc.com/stories/storyReader$7" +TARGET="_top" +>spec</A +> + for more information on what each of these types mean. + </P +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="AEN452" +></A +>Notes on types</H2 +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN454" +></A +>int</H3 +><P +>The type <TT +CLASS="CLASSNAME" +>i4</TT +> is accepted as a + synonym for <TT +CLASS="CLASSNAME" +>int</TT +>. The value parsing + code will always convert <TT +CLASS="CLASSNAME" +>i4</TT +> to + <TT +CLASS="CLASSNAME" +>int</TT +>: <TT +CLASS="CLASSNAME" +>int</TT +> + is regarded by this implementation as the canonical name for + this type.</P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN462" +></A +>base64</H3 +><P +>Base 64 encoding is performed transparently to the + caller when using this type. Therefore you ought to + consider it as a "binary" data type, for use when you want + to pass none 7-bit clean data. Decoding is also + transparent. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN465" +></A +>boolean</H3 +><P +>The values <TT +CLASS="LITERAL" +>true</TT +> and + <TT +CLASS="LITERAL" +>1</TT +> map to <TT +CLASS="LITERAL" +>true</TT +>. All + other values (including the empty string) + are converted to <TT +CLASS="LITERAL" +>false</TT +>. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN472" +></A +>string</H3 +><P +> The characters <TT +CLASS="LITERAL" +>< > "</TT +> and + <TT +CLASS="LITERAL" +>&</TT +> are converted to their entity + equivalents <TT +CLASS="LITERAL" +>&lt; &gt; + &quot;</TT +> and <TT +CLASS="LITERAL" +>&amp;</TT +> + for transport through XML-RPC. The current XML-RPC spec + recommends only encoding <TT +CLASS="LITERAL" +>< &</TT +> but + this implementation goes further, for reasons explained by + <A +HREF="http://www.w3.org/TR/REC-xml#syntax" +TARGET="_top" +>the XML 1.0 + recommendation</A +>. + </P +><P +>TODO: <TT +CLASS="LITERAL" +> &apos;</TT +> entity not + yet supported</P +></DIV +></DIV +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="XMLRPCVAL-CREATION" +></A +>Creation</H2 +><P +>The constructor is the normal way to create an + <TT +CLASS="CLASSNAME" +>xmlrpcval</TT +>. The constructor can take + these forms: + </P +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN487" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$myVal=new xmlrpcval</CODE +>();</CODE +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$myVal=new xmlrpcval</CODE +>($stringVal);</CODE +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$myVal=new xmlrpcval</CODE +>($scalarVal, "int" | "boolean" | "string" | "double" | "dateTime.iso8601" | "base64");</CODE +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$myVal=new xmlrpcval</CODE +>($arrayVal, "array" | "struct");</CODE +></P +><P +></P +></DIV +><P +>The first constructor creates an empty value, which must + be altered using the methods <TT +CLASS="FUNCTION" +>addScalar</TT +>, + <TT +CLASS="FUNCTION" +>addArray</TT +> or + <TT +CLASS="FUNCTION" +>addStruct</TT +> before it can be used. + </P +><P +> The second constructor creates a simple string value. + </P +><P +> The third constructor is used to create a scalar value. The + second parameter must be a name of an XML-RPC type. Examples: + </P +><PRE +CLASS="PROGRAMLISTING" +> $myInt=new xmlrpcvalue(1267, "int"); + $myString=new xmlrpcvalue("Hello, World!", "string"); + $myBool=new xmlrpcvalue(1, "boolean"); + </PRE +><P +> The fourth constructor form can be used to compose complex + XML-RPC values. The first argument is either a simple array in + the case of an XML-RPC <TT +CLASS="CLASSNAME" +>array</TT +> or + an associative array in the case of a + <TT +CLASS="CLASSNAME" +>struct</TT +>. The elements of the array + <SPAN +CLASS="emphasis" +><I +CLASS="EMPHASIS" +>must be <TT +CLASS="CLASSNAME" +>xmlrpcval</TT +> objects + themselves</I +></SPAN +>. + Examples:</P +><PRE +CLASS="PROGRAMLISTING" +> $myArray=new xmlrpcval(array( + new xmlrpcval("Tom"), new xmlrpcval("Dick"), + new xmlrpcval("Harry")), "array"); + + $myStruct=new xmlrpcval(array( + "name" => new xmlrpcval("Tom"), + "age" => new xmlrpcval(34, "int"), + "geek" => new xmlrpcval(1, "boolean")), "struct"); + </PRE +><P +>See the file <TT +CLASS="LITERAL" +>vardemo.php</TT +> in this + distribution for more examples.</P +></DIV +><DIV +CLASS="SECT2" +><H2 +CLASS="SECT2" +><A +NAME="XMLRPCVAL-METHODS" +></A +>Methods</H2 +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN524" +></A +>addScalar</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN526" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$ok=$val->addScalar</CODE +>($stringVal);</CODE +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$ok=$val->addScalar</CODE +>($scalarVal, "int" | "boolean" | "string" | "double" | "dateTime.iso8601" | "base64");</CODE +></P +><P +></P +></DIV +><P +> If <TT +CLASS="PARAMETER" +><I +>$val</I +></TT +> is an empty + <TT +CLASS="CLASSNAME" +>xmlrpcval</TT +> this method makes it a + scalar value, and sets that value. If + <TT +CLASS="PARAMETER" +><I +>$val</I +></TT +> is already a scalar value, then + no more scalars can be added and <TT +CLASS="LITERAL" +>0</TT +> is + returned. If all went OK, <TT +CLASS="LITERAL" +>1</TT +> is returned. + </P +><P +>There is a special case if <TT +CLASS="PARAMETER" +><I +>$val</I +></TT +> + is an <TT +CLASS="CLASSNAME" +>array</TT +>: the scalar value passed + is appended to the array.</P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN546" +></A +>addArray</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN548" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$ok=$val->addArray</CODE +>($arrayVal);</CODE +></P +><P +></P +></DIV +><P +>Turns an empty <TT +CLASS="CLASSNAME" +>xmlrpcval</TT +> into an + <TT +CLASS="CLASSNAME" +>array</TT +> with contents as specified by + <TT +CLASS="PARAMETER" +><I +>$arrayVal</I +></TT +>. See the fourth + constructor form for more information.</P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN557" +></A +>addStruct</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN559" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$ok=$val->addArray</CODE +>($assocArrayVal);</CODE +></P +><P +></P +></DIV +><P +>Turns an empty <TT +CLASS="CLASSNAME" +>xmlrpcval</TT +> into a + <TT +CLASS="CLASSNAME" +>struct</TT +> with contents as specified by + <TT +CLASS="PARAMETER" +><I +>$assocArrayVal</I +></TT +>. See the fourth + constructor form for more information.</P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN568" +></A +>kindOf</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN570" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$kind=$val->kindOf</CODE +>();</CODE +></P +><P +></P +></DIV +><P +> Returns a string containing "struct", "array" or "scalar" + describing the base type of the value. If it returns + "undef" it means that the value hasn't been initialised. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN575" +></A +>serialize</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN577" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$outString=$val->serialize</CODE +>();</CODE +></P +><P +></P +></DIV +><P +> Returns a string containing the XML-RPC representation of + this value. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN582" +></A +>scalarval</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN584" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$scalarVal=$val->scalarval</CODE +>();</CODE +></P +><P +></P +></DIV +><P +> If <TT +CLASS="FUNCTION" +>$val->kindOf()=="scalar"</TT +>, this + method returns the actual PHP-language value of the scalar + (base 64 decoding is automatically handled here). + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN590" +></A +>scalartyp</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN592" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$typeName=$val->scalartyp</CODE +>();</CODE +></P +><P +></P +></DIV +><P +> If <TT +CLASS="FUNCTION" +>$val->kindOf()=="scalar"</TT +>, this + method returns a string denoting the type of the scalar. + As mentioned before, + <TT +CLASS="LITERAL" +>i4</TT +> is always coerced to <TT +CLASS="LITERAL" +>int</TT +>. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN600" +></A +>arraymem</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN602" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$xmlrpcVal=$val->arraymem</CODE +>($n);</CODE +></P +><P +></P +></DIV +><P +> Returns the <TT +CLASS="PARAMETER" +><I +>$n</I +></TT +>th element in the array + represented by the value <TT +CLASS="PARAMETER" +><I +>$val</I +></TT +>. The + value returned is an <TT +CLASS="CLASSNAME" +>xmlrpcval</TT +> object. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN611" +></A +>arraysize</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN613" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$len=$val->arraysize</CODE +>();</CODE +></P +><P +></P +></DIV +><P +>If <TT +CLASS="PARAMETER" +><I +>$val</I +></TT +> is an + <TT +CLASS="CLASSNAME" +>array</TT +>, returns the number of elements + in that array. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN620" +></A +>structmem</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN622" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$xmlrpcVal=$val->structmem</CODE +>($memberName);</CODE +></P +><P +></P +></DIV +><P +> Returns the element called + <TT +CLASS="PARAMETER" +><I +>$memberName</I +></TT +> from the struct + represented by the value <TT +CLASS="PARAMETER" +><I +>$val</I +></TT +>. The + value returned is an <TT +CLASS="CLASSNAME" +>xmlrpcval</TT +> object. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="AEN631" +></A +>structeach</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN633" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>list($key,$value)=$val->structeach</CODE +>();</CODE +></P +><P +></P +></DIV +><P +> Returns the next (key,value) pair from the struct, when + <TT +CLASS="PARAMETER" +><I +>$val</I +></TT +> is a struct. See also + <A +HREF="xmlrpcval.html#STRUCTRESET" +>structreset()</A +>. + </P +></DIV +><DIV +CLASS="SECT3" +><H3 +CLASS="SECT3" +><A +NAME="STRUCTRESET" +></A +>structreset</H3 +><DIV +CLASS="FUNCSYNOPSIS" +><A +NAME="AEN642" +></A +><P +></P +><P +><CODE +><CODE +CLASS="FUNCDEF" +>$val->structreset</CODE +>();</CODE +></P +><P +></P +></DIV +><P +> Resets the internal pointer for + <TT +CLASS="FUNCTION" +>structeach()</TT +> to the beginning of the + struct, where <TT +CLASS="PARAMETER" +><I +>$val</I +></TT +> is a struct. + </P +></DIV +></DIV +></DIV +><DIV +CLASS="NAVFOOTER" +><HR +ALIGN="LEFT" +WIDTH="100%"><TABLE +SUMMARY="Footer navigation table" +WIDTH="100%" +BORDER="0" +CELLPADDING="0" +CELLSPACING="0" +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +><A +HREF="xmlrpcresp.html" +ACCESSKEY="P" +>Prev</A +></TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="index.html" +ACCESSKEY="H" +>Home</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +><A +HREF="xmlrpc-server.html" +ACCESSKEY="N" +>Next</A +></TD +></TR +><TR +><TD +WIDTH="33%" +ALIGN="left" +VALIGN="top" +>xmlrpcresp</TD +><TD +WIDTH="34%" +ALIGN="center" +VALIGN="top" +><A +HREF="apidocs.html" +ACCESSKEY="U" +>Up</A +></TD +><TD +WIDTH="33%" +ALIGN="right" +VALIGN="top" +>xmlrpc_server</TD +></TR +></TABLE +></DIV +></BODY +></HTML +>
\ No newline at end of file diff --git a/modules/xmlrpc/xmlrpc.inc b/modules/xmlrpc/xmlrpc.inc new file mode 100755 index 00000000..5b4c0150 --- /dev/null +++ b/modules/xmlrpc/xmlrpc.inc @@ -0,0 +1,1476 @@ +<?php // -*-c++-*- +// by Edd Dumbill (C) 1999-2002 +// <edd@usefulinc.com> +// $Id: xmlrpc.inc,v 1.20 2003/01/10 22:01:56 dilinger Exp $ + + +// Copyright (c) 1999,2000,2002 Edd Dumbill. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of the "XML-RPC for PHP" nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + + if (!function_exists('xml_parser_create')) + { + // Win 32 fix. From: 'Leo West' <lwest@imaginet.fr> + if($WINDIR) + { + dl('php3_xml.dll'); + } + else + { + dl('xml.so'); + } + } + + $xmlrpcI4='i4'; + $xmlrpcInt='int'; + $xmlrpcBoolean='boolean'; + $xmlrpcDouble='double'; + $xmlrpcString='string'; + $xmlrpcDateTime='dateTime.iso8601'; + $xmlrpcBase64='base64'; + $xmlrpcArray='array'; + $xmlrpcStruct='struct'; + + $xmlrpcTypes=array( + $xmlrpcI4 => 1, + $xmlrpcInt => 1, + $xmlrpcBoolean => 1, + $xmlrpcString => 1, + $xmlrpcDouble => 1, + $xmlrpcDateTime => 1, + $xmlrpcBase64 => 1, + $xmlrpcArray => 2, + $xmlrpcStruct => 3 + ); + + $xmlEntities=array( + 'amp' => '&', + 'quot' => '"', + 'lt' => '<', + 'gt' => '>', + 'apos' => "'" + ); + + $xmlrpcerr['unknown_method']=1; + $xmlrpcstr['unknown_method']='Unknown method'; + $xmlrpcerr['invalid_return']=2; + $xmlrpcstr['invalid_return']='Invalid return payload: enabling debugging to examine incoming payload'; + $xmlrpcerr['incorrect_params']=3; + $xmlrpcstr['incorrect_params']='Incorrect parameters passed to method'; + $xmlrpcerr['introspect_unknown']=4; + $xmlrpcstr['introspect_unknown']="Can't introspect: method unknown"; + $xmlrpcerr['http_error']=5; + $xmlrpcstr['http_error']="Didn't receive 200 OK from remote server."; + $xmlrpcerr['no_data']=6; + $xmlrpcstr['no_data']='No data received from server.'; + $xmlrpcerr['no_ssl']=7; + $xmlrpcstr['no_ssl']='No SSL support compiled in.'; + $xmlrpcerr['curl_fail']=8; + $xmlrpcstr['curl_fail']='CURL error'; + + + $xmlrpcerr['multicall_notstruct'] = 9; + $xmlrpcstr['multicall_notstruct'] = 'system.multicall expected struct'; + $xmlrpcerr['multicall_nomethod'] = 10; + $xmlrpcstr['multicall_nomethod'] = 'missing methodName'; + $xmlrpcerr['multicall_notstring'] = 11; + $xmlrpcstr['multicall_notstring'] = 'methodName is not a string'; + $xmlrpcerr['multicall_recursion'] = 12; + $xmlrpcstr['multicall_recursion'] = 'recursive system.multicall forbidden'; + $xmlrpcerr['multicall_noparams'] = 13; + $xmlrpcstr['multicall_noparams'] = 'missing params'; + $xmlrpcerr['multicall_notarray'] = 14; + $xmlrpcstr['multicall_notarray'] = 'params is not an array'; + + $xmlrpc_defencoding='UTF-8'; + + $xmlrpcName='XML-RPC for PHP'; + $xmlrpcVersion='1.0.99'; + + // let user errors start at 800 + $xmlrpcerruser=800; + // let XML parse errors start at 100 + $xmlrpcerrxml=100; + + // formulate backslashes for escaping regexp + $xmlrpc_backslash=chr(92).chr(92); + + // used to store state during parsing + // quick explanation of components: + // st - used to build up a string for evaluation + // ac - used to accumulate values + // qt - used to decide if quotes are needed for evaluation + // cm - used to denote struct or array (comma needed) + // isf - used to indicate a fault + // lv - used to indicate "looking for a value": implements + // the logic to allow values with no types to be strings + // params - used to store parameters in method calls + // method - used to store method name + + $_xh=array(); + + if (!function_exists('xmlrpc_entity_decode')) { + + function xmlrpc_entity_decode($string) + { + $top=split('&', $string); + $op=''; + $i=0; + while($i<sizeof($top)) + { + if (ereg("^([#a-zA-Z0-9]+);", $top[$i], $regs)) + { + $op.=ereg_replace("^[#a-zA-Z0-9]+;", + xmlrpc_lookup_entity($regs[1]), + $top[$i]); + } + else + { + if ($i==0) + { + $op=$top[$i]; + } + else + { + $op.='&' . $top[$i]; + } + } + $i++; + } + return $op; + } + + } // if xmlrpc_entity_decode doesn't exist + + function xmlrpc_lookup_entity($ent) + { + global $xmlEntities; + + if (isset($xmlEntities[strtolower($ent)])) + { + return $xmlEntities[strtolower($ent)]; + } + if (ereg("^#([0-9]+)$", $ent, $regs)) + { + return chr($regs[1]); + } + return '?'; + } + + function xmlrpc_se($parser, $name, $attrs) + { + global $_xh, $xmlrpcDateTime, $xmlrpcString; + + switch($name) + { + case 'STRUCT': + case 'ARRAY': + $_xh[$parser]['st'].='array('; + $_xh[$parser]['cm']++; + // this last line turns quoting off + // this means if we get an empty array we'll + // simply get a bit of whitespace in the eval + $_xh[$parser]['qt']=0; + break; + case 'NAME': + $_xh[$parser]['st'].="'"; $_xh[$parser]['ac']=''; + break; + case 'FAULT': + $_xh[$parser]['isf']=1; + break; + case 'PARAM': + $_xh[$parser]['st']=''; + break; + case 'VALUE': + $_xh[$parser]['st'].='new xmlrpcval('; + $_xh[$parser]['vt']=$xmlrpcString; + $_xh[$parser]['ac']=''; + $_xh[$parser]['qt']=0; + $_xh[$parser]['lv']=1; + // look for a value: if this is still 1 by the + // time we reach the first data segment then the type is string + // by implication and we need to add in a quote + break; + case 'I4': + case 'INT': + case 'STRING': + case 'BOOLEAN': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + $_xh[$parser]['ac']=''; // reset the accumulator + + if ($name=='DATETIME.ISO8601' || $name=='STRING') + { + $_xh[$parser]['qt']=1; + if ($name=='DATETIME.ISO8601') + { + $_xh[$parser]['vt']=$xmlrpcDateTime; + } + } + elseif ($name=='BASE64') + { + $_xh[$parser]['qt']=2; + } + else + { + // No quoting is required here -- but + // at the end of the element we must check + // for data format errors. + $_xh[$parser]['qt']=0; + } + break; + case 'MEMBER': + $_xh[$parser]['ac']=''; + break; + default: + break; + } + + if ($name!='VALUE') + { + $_xh[$parser]['lv']=0; + } + } + + function xmlrpc_ee($parser, $name) + { + global $_xh,$xmlrpcTypes,$xmlrpcString; + + switch($name) + { + case 'STRUCT': + case 'ARRAY': + if ($_xh[$parser]['cm'] && substr($_xh[$parser]['st'], -1) ==',') + { + $_xh[$parser]['st']=substr($_xh[$parser]['st'],0,-1); + } + $_xh[$parser]['st'].=')'; + $_xh[$parser]['vt']=strtolower($name); + $_xh[$parser]['cm']--; + break; + case 'NAME': + $_xh[$parser]['st'].= $_xh[$parser]['ac'] . "' => "; + break; + case 'BOOLEAN': + // special case here: we translate boolean 1 or 0 into PHP + // constants true or false + if ($_xh[$parser]['ac']=='1') + { + $_xh[$parser]['ac']='true'; + } + else + { + $_xh[$parser]['ac']='false'; + $_xh[$parser]['vt']=strtolower($name); + // Drop through intentionally. + } + case 'I4': + case 'INT': + case 'STRING': + case 'DOUBLE': + case 'DATETIME.ISO8601': + case 'BASE64': + if ($_xh[$parser]['qt']==1) + { + // we use double quotes rather than single so backslashification works OK + $_xh[$parser]['st'].='"'. $_xh[$parser]['ac'] . '"'; + } + elseif ($_xh[$parser]['qt']==2) + { + $_xh[$parser]['st'].="base64_decode('". $_xh[$parser]['ac'] . "')"; + } + elseif ($name=='BOOLEAN') + { + $_xh[$parser]['st'].=$_xh[$parser]['ac']; + } + else + { + // we have an I4, INT or a DOUBLE + // we must check that only 0123456789-.<space> are characters here + if (!ereg("^\-?[0123456789 \t\.]+$", $_xh[$parser]['ac'])) + { + // TODO: find a better way of throwing an error + // than this! + error_log('XML-RPC: non numeric value received in INT or DOUBLE'); + $_xh[$parser]['st'].='ERROR_NON_NUMERIC_FOUND'; + } + else + { + // it's ok, add it on + $_xh[$parser]['st'].=$_xh[$parser]['ac']; + } + } + $_xh[$parser]['ac']=''; $_xh[$parser]['qt']=0; + $_xh[$parser]['lv']=3; // indicate we've found a value + break; + case 'VALUE': + // deal with a string value + if (strlen($_xh[$parser]['ac'])>0 && + $_xh[$parser]['vt']==$xmlrpcString) + { + $_xh[$parser]['st'].='"'. $_xh[$parser]['ac'] . '"'; + } + // This if() detects if no scalar was inside <VALUE></VALUE> + // and pads an empty ''. + if($_xh[$parser]['st'][strlen($_xh[$parser]['st'])-1] == '(') + { + $_xh[$parser]['st'].= '""'; + } + $_xh[$parser]['st'].=", '" . $_xh[$parser]['vt'] . "')"; + if ($_xh[$parser]['cm']) + { + $_xh[$parser]['st'].=','; + } + break; + case 'MEMBER': + $_xh[$parser]['ac']=''; $_xh[$parser]['qt']=0; + break; + case 'DATA': + $_xh[$parser]['ac']=''; $_xh[$parser]['qt']=0; + break; + case 'PARAM': + $_xh[$parser]['params'][]=$_xh[$parser]['st']; + break; + case 'METHODNAME': + $_xh[$parser]['method']=ereg_replace("^[\n\r\t ]+", '', $_xh[$parser]['ac']); + break; + case 'BOOLEAN': + // special case here: we translate boolean 1 or 0 into PHP + // constants true or false + if ($_xh[$parser]['ac']=='1') + { + $_xh[$parser]['ac']='true'; + } + else + { + $_xh[$parser]['ac']='false'; + $_xh[$parser]['vt']=strtolower($name); + } + break; + default: + break; + } + // if it's a valid type name, set the type + if (isset($xmlrpcTypes[strtolower($name)])) + { + $_xh[$parser]['vt']=strtolower($name); + } + } + + function xmlrpc_cd($parser, $data) + { + global $_xh, $xmlrpc_backslash; + + //if (ereg("^[\n\r \t]+$", $data)) return; + // print "adding [${data}]\n"; + + if ($_xh[$parser]['lv']!=3) + { + // "lookforvalue==3" means that we've found an entire value + // and should discard any further character data + if ($_xh[$parser]['lv']==1) + { + // if we've found text and we're just in a <value> then + // turn quoting on, as this will be a string + $_xh[$parser]['qt']=1; + // and say we've found a value + $_xh[$parser]['lv']=2; + } + if(!@isset($_xh[$parser]['ac'])) + { + $_xh[$parser]['ac'] = ''; + } + $_xh[$parser]['ac'].=str_replace('$', '\$', str_replace('"', '\"', str_replace(chr(92),$xmlrpc_backslash, $data))); + } + } + + function xmlrpc_dh($parser, $data) + { + global $_xh; + if (substr($data, 0, 1) == '&' && substr($data, -1, 1) == ';') + { + if ($_xh[$parser]['lv']==1) + { + $_xh[$parser]['qt']=1; + $_xh[$parser]['lv']=2; + } + $_xh[$parser]['ac'].=str_replace('$', '\$', str_replace('"', '\"', str_replace(chr(92),$xmlrpc_backslash, $data))); + } + } + + class xmlrpc_client + { + var $path; + var $server; + var $port; + var $errno; + var $errstring; + var $debug=0; + var $username=''; + var $password=''; + var $cert=''; + var $certpass=''; + var $verifypeer=1; + var $verifyhost=1; + var $no_multicall=false; + + function xmlrpc_client($path, $server, $port=0) + { + $this->port=$port; $this->server=$server; $this->path=$path; + } + + function setDebug($in) + { + if ($in) + { + $this->debug=1; + } + else + { + $this->debug=0; + } + } + + function setCredentials($u, $p) + { + $this->username=$u; + $this->password=$p; + } + + function setCertificate($cert, $certpass) + { + $this->cert = $cert; + $this->certpass = $certpass; + } + + function setSSLVerifyPeer($i) + { + $this->verifypeer = $i; + } + + function setSSLVerifyHost($i) + { + $this->verifyhost = $i; + } + + function send($msg, $timeout=0, $method='http') + { + if (is_array($msg)) + { + // $msg is an array of xmlrpcmsg's + return $this->multicall($msg, $timeout, $method); + } + + // where msg is an xmlrpcmsg + $msg->debug=$this->debug; + + if ($method == 'https') + { + return $this->sendPayloadHTTPS($msg, + $this->server, + $this->port, $timeout, + $this->username, $this->password, + $this->cert, + $this->certpass); + } + else + { + return $this->sendPayloadHTTP10($msg, $this->server, $this->port, + $timeout, $this->username, + $this->password); + } + } + + function sendPayloadHTTP10($msg, $server, $port, $timeout=0,$username='', $password='') + { + global $xmlrpcerr, $xmlrpcstr; + if ($port==0) + { + $port=80; + } + if($timeout>0) + { + $fp=fsockopen($server, $port,$this->errno, $this->errstr, $timeout); + } + else + { + $fp=fsockopen($server, $port,$this->errno, $this->errstr); + } + if (!$fp) + { + $this->errstr='Connect error'; + $r=new xmlrpcresp(0, $xmlrpcerr['http_error'],$xmlrpcstr['http_error']); + return $r; + } + // Only create the payload if it was not created previously + if(empty($msg->payload)) + { + $msg->createPayload(); + } + + // thanks to Grant Rauscher <grant7@firstworld.net> + // for this + $credentials=''; + if ($username!='') + { + $credentials='Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n"; + } + + $op= "POST " . $this->path. " HTTP/1.0\r\nUser-Agent: PHP XMLRPC 1.0\r\n" . + "Host: ". $this->server . "\r\n" . + $credentials . + "Content-Type: text/xml\r\nContent-Length: " . + strlen($msg->payload) . "\r\n\r\n" . + $msg->payload; + + if (!fputs($fp, $op, strlen($op))) + { + $this->errstr='Write error'; + $r=new xmlrpcresp(0, $xmlrpcerr['http_error'], $xmlrpcstr['http_error']); + return $r; + } + $resp=$msg->parseResponseFile($fp); + fclose($fp); + return $resp; + } + + // contributed by Justin Miller <justin@voxel.net> + // requires curl to be built into PHP + function sendPayloadHTTPS($msg, $server, $port, $timeout=0,$username='', $password='', $cert='',$certpass='') + { + global $xmlrpcerr, $xmlrpcstr; + if ($port == 0) + { + $port = 443; + } + + // Only create the payload if it was not created previously + if(empty($msg->payload)) + { + $msg->createPayload(); + } + + if (!function_exists('curl_init')) + { + $this->errstr='SSL unavailable on this install'; + $r=new xmlrpcresp(0, $xmlrpcerr['no_ssl'], $xmlrpcstr['no_ssl']); + return $r; + } + + $curl = curl_init('https://' . $server . ':' . $port . $this->path); + + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + // results into variable + if ($this->debug) + { + curl_setopt($curl, CURLOPT_VERBOSE, 1); + } + curl_setopt($curl, CURLOPT_USERAGENT, 'PHP XMLRPC 1.0'); + // required for XMLRPC + curl_setopt($curl, CURLOPT_POST, 1); + // post the data + curl_setopt($curl, CURLOPT_POSTFIELDS, $msg->payload); + // the data + curl_setopt($curl, CURLOPT_HEADER, 1); + // return the header too + curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: text/xml')); + // whether to verify remote host's cert + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer); + // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used + curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost); + // required for XMLRPC + if ($timeout) + { + curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1); + } + // timeout is borked + if ($username && $password) + { + curl_setopt($curl, CURLOPT_USERPWD,"$username:$password"); + } + // set auth stuff + if ($cert) + { + curl_setopt($curl, CURLOPT_SSLCERT, $cert); + } + // set cert file + if ($certpass) + { + curl_setopt($curl, CURLOPT_SSLCERTPASSWD,$certpass); + } + // set cert password + + $result = curl_exec($curl); + + if (!$result) + { + $this->errstr='no response'; + $resp=new xmlrpcresp(0, $xmlrpcerr['curl_fail'], $xmlrpcstr['curl_fail']. ': '. curl_error($curl)); + } + else + { + $resp = $msg->parseResponse($result); + } + curl_close($curl); + return $resp; + } + + function multicall($msgs, $timeout=0, $method='http') + { + $results = false; + + if (! $this->no_multicall) + { + $results = $this->_try_multicall($msgs, $timeout, $method); + /* TODO - this is not php3-friendly */ + // if($results !== false) + if($results != false) + { + // Either the system.multicall succeeded, or the send + // failed (e.g. due to HTTP timeout). In either case, + // we're done for now. + return $results; + } + else + { + // system.multicall unsupported by server, + // don't try it next time... + $this->no_multicall = true; + } + } + + // system.multicall is unupported by server: + // Emulate multicall via multiple requests + $results = array(); + //foreach($msgs as $msg) + @reset($msgs); + while(list(,$msg) = @each($msgs)) + { + $results[] = $this->send($msg, $timeout, $method); + } + return $results; + } + + // Attempt to boxcar $msgs via system.multicall. + function _try_multicall($msgs, $timeout, $method) + { + // Construct multicall message + $calls = array(); + //foreach($msgs as $msg) + @reset($msgs); + while(list(,$msg) = @each($msgs)) + { + $call['methodName'] = new xmlrpcval($msg->method(),'string'); + $numParams = $msg->getNumParams(); + $params = array(); + for ($i = 0; $i < $numParams; $i++) + { + $params[$i] = $msg->getParam($i); + } + $call['params'] = new xmlrpcval($params, 'array'); + $calls[] = new xmlrpcval($call, 'struct'); + } + $multicall = new xmlrpcmsg('system.multicall'); + $multicall->addParam(new xmlrpcval($calls, 'array')); + + // Attempt RPC call + $result = $this->send($multicall, $timeout, $method); + if (!is_object($result)) + return ($result || 0); // transport failed + + if ($result->faultCode() != 0) + return false; // system.multicall failed + + // Unpack responses. + $rets = $result->value(); + if ($rets->kindOf() != 'array') + return false; // bad return type from system.multicall + $numRets = $rets->arraysize(); + if ($numRets != count($msgs)) + return false; // wrong number of return values. + + $response = array(); + for ($i = 0; $i < $numRets; $i++) + { + $val = $rets->arraymem($i); + switch ($val->kindOf()) + { + case 'array': + if ($val->arraysize() != 1) + return false; // Bad value + // Normal return value + $response[$i] = new xmlrpcresp($val->arraymem(0)); + break; + case 'struct': + $code = $val->structmem('faultCode'); + if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') + return false; + $str = $val->structmem('faultString'); + if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') + return false; + $response[$i] = new xmlrpcresp(0, $code->scalarval(), $str->scalarval()); + break; + default: + return false; + } + } + return $response; + } + } // end class xmlrpc_client + + class xmlrpcresp + { + var $val = 0; + var $errno = 0; + var $errstr = ''; + var $hdrs = array(); + + function xmlrpcresp($val, $fcode = 0, $fstr = '') + { + if ($fcode != 0) + { + // error + $this->errno = $fcode; + $this->errstr = htmlspecialchars($fstr); // XXX: encoding probably shouldn't be done here; fix later. + } + else if (!is_object($val)) + { + // programmer error + error_log("Invalid type '" . gettype($val) . "' (value: $val) passed to xmlrpcresp. Defaulting to empty value."); + $this->val = new xmlrpcval(); + } + else + { + // success + $this->val = $val; + } + } + + function faultCode() + { + return $this->errno; + } + + function faultString() + { + return $this->errstr; + } + + function value() + { + return $this->val; + } + + function serialize() + { + $result = "<methodResponse>\n"; + if ($this->errno) + { + $result .= '<fault> +<value> +<struct> +<member> +<name>faultCode</name> +<value><int>' . $this->errno . '</int></value> +</member> +<member> +<name>faultString</name> +<value><string>' . $this->errstr . '</string></value> +</member> +</struct> +</value> +</fault>'; + } + else + { + $result .= "<params>\n<param>\n" . + $this->val->serialize() . + "</param>\n</params>"; + } + $result .= "\n</methodResponse>"; + return $result; + } + } + + class xmlrpcmsg + { + var $payload; + var $methodname; + var $params=array(); + var $debug=0; + + function xmlrpcmsg($meth, $pars=0) + { + $this->methodname=$meth; + if (is_array($pars) && sizeof($pars)>0) + { + for($i=0; $i<sizeof($pars); $i++) + { + $this->addParam($pars[$i]); + } + } + } + + function xml_header() + { + return "<?xml version=\"1.0\"?>\n<methodCall>\n"; + } + + function xml_footer() + { + return "</methodCall>\n"; + } + + function createPayload() + { + $this->payload=$this->xml_header(); + $this->payload.='<methodName>' . $this->methodname . "</methodName>\n"; + // if (sizeof($this->params)) { + $this->payload.="<params>\n"; + for($i=0; $i<sizeof($this->params); $i++) + { + $p=$this->params[$i]; + $this->payload.="<param>\n" . $p->serialize() . + "</param>\n"; + } + $this->payload.="</params>\n"; + // } + $this->payload.=$this->xml_footer(); + $this->payload=str_replace("\n", "\r\n", $this->payload); + } + + function method($meth='') + { + if ($meth!='') + { + $this->methodname=$meth; + } + return $this->methodname; + } + + function serialize() + { + $this->createPayload(); + return $this->payload; + } + + function addParam($par) { $this->params[]=$par; } + function getParam($i) { return $this->params[$i]; } + function getNumParams() { return sizeof($this->params); } + + function parseResponseFile($fp) + { + $ipd=''; + while($data=fread($fp, 32768)) + { + $ipd.=$data; + } + return $this->parseResponse($ipd); + } + + function parseResponse($data='') + { + global $_xh,$xmlrpcerr,$xmlrpcstr; + global $xmlrpc_defencoding; + + $parser = xml_parser_create($xmlrpc_defencoding); + + $_xh[$parser]=array(); + + $_xh[$parser]['st']=''; + $_xh[$parser]['cm']=0; + $_xh[$parser]['isf']=0; + $_xh[$parser]['ac']=''; + $_xh[$parser]['qt']=''; + $_xh[$parser]['headers'] = array(); + + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); + xml_set_character_data_handler($parser, 'xmlrpc_cd'); + xml_set_default_handler($parser, 'xmlrpc_dh'); + $xmlrpc_value=new xmlrpcval; + + $hdrfnd = 0; + if($this->debug) + { + //by maHo, replaced htmlspecialchars with htmlentities + print "<PRE>---GOT---\n" . htmlentities($data) . "\n---END---\n</PRE>"; + } + + if($data == '') + { + error_log('No response received from server.'); + $r = new xmlrpcresp(0, $xmlrpcerr['no_data'], $xmlrpcstr['no_data']); + xml_parser_free($parser); + return $r; + } + // see if we got an HTTP 200 OK, else bomb + // but only do this if we're using the HTTP protocol. + if(ereg("^HTTP",$data) && !ereg("^HTTP/[0-9\.]+ 200 ", $data)) + { + $errstr= substr($data, 0, strpos($data, "\n")-1); + error_log('HTTP error, got response: ' .$errstr); + $r=new xmlrpcresp(0, $xmlrpcerr['http_error'], $xmlrpcstr['http_error']. ' (' . $errstr . ')'); + xml_parser_free($parser); + return $r; + } + + // separate HTTP headers from data + if (ereg("^HTTP", $data)) + { + $ar = split("\r\n", $data); + while (($line = array_shift($ar))) + { + if (strlen($line) < 1) + { + break; + } + $_xh[$parser]['headers'][] = $line; + } + $data = join("\r\n", $ar); + } + + if ($this->debug && count($_xh[$parser]['headers'])) + { + print "<PRE>"; + foreach ($_xh[$parser]['headers'] as $header) + { + print "HEADER: $header\n"; + } + print "</PRE>\n"; + } + + if (!xml_parse($parser, $data, sizeof($data))) + { + // thanks to Peter Kocks <peter.kocks@baygate.com> + if((xml_get_current_line_number($parser)) == 1) + { + $errstr = 'XML error at line 1, check URL'; + } + else + { + $errstr = sprintf('XML error: %s at line %d', + xml_error_string(xml_get_error_code($parser)), + xml_get_current_line_number($parser)); + error_log($errstr); + $r=new xmlrpcresp(0, $xmlrpcerr['invalid_return'], $xmlrpcstr['invalid_return']); + xml_parser_free($parser); + echo $errstr; + return $r; + } + } + xml_parser_free($parser); + if ($this->debug) + { + print "<PRE>---EVALING---[" . + strlen($_xh[$parser]['st']) . " chars]---\n" . + htmlspecialchars($_xh[$parser]['st']) . ";\n---END---</PRE>"; + } + if (strlen($_xh[$parser]['st'])==0) + { + // then something odd has happened + // and it's time to generate a client side error + // indicating something odd went on + $r=new xmlrpcresp(0, $xmlrpcerr['invalid_return'], + $xmlrpcstr['invalid_return']); + } + else + { + eval('$v=' . $_xh[$parser]['st'] . '; $allOK=1;'); + if ($_xh[$parser]['isf']) + { + $errno_v = $v->structmem('faultCode'); + $errstr_v = $v->structmem('faultString'); + $errno = $errno_v->scalarval(); + + if ($errno == 0) + { + // FAULT returned, errno needs to reflect that + $errno = -1; + } + + $r = new xmlrpcresp($v, $errno, $errstr_v->scalarval()); + } + else + { + $r=new xmlrpcresp($v); + } + } + + $r->hdrs = $_xh[$parser]['headers']; + return $r; + } + } + + class xmlrpcval + { + var $me=array(); + var $mytype=0; + + function xmlrpcval($val=-1, $type='') + { + global $xmlrpcTypes; + $this->me=array(); + $this->mytype=0; + if ($val!=-1 || $type!='') + { + if ($type=='') + { + $type='string'; + } + if ($xmlrpcTypes[$type]==1) + { + $this->addScalar($val,$type); + } + elseif ($xmlrpcTypes[$type]==2) + { + $this->addArray($val); + } + elseif ($xmlrpcTypes[$type]==3) + { + $this->addStruct($val); + } + } + } + + function addScalar($val, $type='string') + { + global $xmlrpcTypes, $xmlrpcBoolean; + + if ($this->mytype==1) + { + echo '<B>xmlrpcval</B>: scalar can have only one value<BR>'; + return 0; + } + $typeof=$xmlrpcTypes[$type]; + if ($typeof!=1) + { + echo '<B>xmlrpcval</B>: not a scalar type (${typeof})<BR>'; + return 0; + } + + if ($type==$xmlrpcBoolean) + { + if (strcasecmp($val,'true')==0 || $val==1 || ($val==true && strcasecmp($val,'false'))) + { + $val=1; + } + else + { + $val=0; + } + } + + if ($this->mytype==2) + { + // we're adding to an array here + $ar=$this->me['array']; + $ar[]=new xmlrpcval($val, $type); + $this->me['array']=$ar; + } + else + { + // a scalar, so set the value and remember we're scalar + $this->me[$type]=$val; + $this->mytype=$typeof; + } + return 1; + } + + function addArray($vals) + { + global $xmlrpcTypes; + if ($this->mytype!=0) + { + echo '<B>xmlrpcval</B>: already initialized as a [' . $this->kindOf() . ']<BR>'; + return 0; + } + + $this->mytype=$xmlrpcTypes['array']; + $this->me['array']=$vals; + return 1; + } + + function addStruct($vals) + { + global $xmlrpcTypes; + if ($this->mytype!=0) + { + echo '<B>xmlrpcval</B>: already initialized as a [' . $this->kindOf() . ']<BR>'; + return 0; + } + $this->mytype=$xmlrpcTypes['struct']; + $this->me['struct']=$vals; + return 1; + } + + function dump($ar) + { + reset($ar); + while ( list( $key, $val ) = each( $ar ) ) + { + echo "$key => $val<br>"; + if ($key == 'array') + { + while ( list( $key2, $val2 ) = each( $val ) ) + { + echo "-- $key2 => $val2<br>"; + } + } + } + } + + function kindOf() + { + switch($this->mytype) + { + case 3: + return 'struct'; + break; + case 2: + return 'array'; + break; + case 1: + return 'scalar'; + break; + default: + return 'undef'; + } + } + + function serializedata($typ, $val) + { + $rs=''; + global $xmlrpcTypes, $xmlrpcBase64, $xmlrpcString, + $xmlrpcBoolean; + switch($xmlrpcTypes[$typ]) + { + case 3: + // struct + $rs.="<struct>\n"; + reset($val); + while(list($key2, $val2)=each($val)) + { + $rs.="<member><name>${key2}</name>\n"; + $rs.=$this->serializeval($val2); + $rs.="</member>\n"; + } + $rs.='</struct>'; + break; + case 2: + // array + $rs.="<array>\n<data>\n"; + for($i=0; $i<sizeof($val); $i++) + { + $rs.=$this->serializeval($val[$i]); + } + $rs.="</data>\n</array>"; + break; + case 1: + switch ($typ) + { + case $xmlrpcBase64: + $rs.="<${typ}>" . base64_encode($val) . "</${typ}>"; + break; + case $xmlrpcBoolean: + $rs.="<${typ}>" . ($val ? '1' : '0') . "</${typ}>"; + break; + case $xmlrpcString: + $rs.="<${typ}>" . htmlspecialchars($val). "</${typ}>"; + break; + default: + $rs.="<${typ}>${val}</${typ}>"; + } + break; + default: + break; + } + return $rs; + } + + function serialize() + { + return $this->serializeval($this); + } + + function serializeval($o) + { + global $xmlrpcTypes; + $rs=''; + $ar=$o->me; + reset($ar); + list($typ, $val) = each($ar); + $rs.='<value>'; + $rs.=$this->serializedata($typ, $val); + $rs.="</value>\n"; + return $rs; + } + + function structmem($m) + { + $nv=$this->me['struct'][$m]; + return $nv; + } + + function structreset() + { + reset($this->me['struct']); + } + + function structeach() + { + return each($this->me['struct']); + } + + function getval() + { + // UNSTABLE + global $xmlrpcBoolean, $xmlrpcBase64; + reset($this->me); + list($a,$b)=each($this->me); + // contributed by I Sofer, 2001-03-24 + // add support for nested arrays to scalarval + // i've created a new method here, so as to + // preserve back compatibility + + if (is_array($b)) + { + @reset($b); + while(list($id,$cont) = @each($b)) + { + $b[$id] = $cont->scalarval(); + } + } + + // add support for structures directly encoding php objects + if (is_object($b)) + { + $t = get_object_vars($b); + @reset($t); + while(list($id,$cont) = @each($t)) + { + $t[$id] = $cont->scalarval(); + } + @reset($t); + while(list($id,$cont) = @each($t)) + { + eval('$b->'.$id.' = $cont;'); + } + } + // end contrib + return $b; + } + + function scalarval() + { + global $xmlrpcBoolean, $xmlrpcBase64; + reset($this->me); + list($a,$b)=each($this->me); + return $b; + } + + function scalartyp() + { + global $xmlrpcI4, $xmlrpcInt; + reset($this->me); + list($a,$b)=each($this->me); + if ($a==$xmlrpcI4) + { + $a=$xmlrpcInt; + } + return $a; + } + + function arraymem($m) + { + $nv=$this->me['array'][$m]; + return $nv; + } + + function arraysize() + { + reset($this->me); + list($a,$b)=each($this->me); + return sizeof($b); + } + } + + // date helpers + function iso8601_encode($timet, $utc=0) + { + // return an ISO8601 encoded string + // really, timezones ought to be supported + // but the XML-RPC spec says: + // + // "Don't assume a timezone. It should be specified by the server in its + // documentation what assumptions it makes about timezones." + // + // these routines always assume localtime unless + // $utc is set to 1, in which case UTC is assumed + // and an adjustment for locale is made when encoding + if (!$utc) + { + $t=strftime("%Y%m%dT%H:%M:%S", $timet); + } + else + { + if (function_exists('gmstrftime')) + { + // gmstrftime doesn't exist in some versions + // of PHP + $t=gmstrftime("%Y%m%dT%H:%M:%S", $timet); + } + else + { + $t=strftime("%Y%m%dT%H:%M:%S", $timet-date('Z')); + } + } + return $t; + } + + function iso8601_decode($idate, $utc=0) + { + // return a timet in the localtime, or UTC + $t=0; + if (ereg("([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})", $idate, $regs)) + { + if ($utc) + { + $t=gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); + } + else + { + $t=mktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]); + } + } + return $t; + } + + /**************************************************************** + * xmlrpc_decode takes a message in PHP xmlrpc object format and * + * tranlates it into native PHP types. * + * * + * author: Dan Libby (dan@libby.com) * + ****************************************************************/ + function old_xmlrpc_decode($xmlrpc_val) + { + $kind = $xmlrpc_val->kindOf(); + + if($kind == 'scalar') + { + return $xmlrpc_val->scalarval(); + } + elseif($kind == 'array') + { + $size = $xmlrpc_val->arraysize(); + $arr = array(); + + for($i = 0; $i < $size; $i++) + { + $arr[]=old_xmlrpc_decode($xmlrpc_val->arraymem($i)); + } + return $arr; + } + elseif($kind == 'struct') + { + $xmlrpc_val->structreset(); + $arr = array(); + + while(list($key,$value)=$xmlrpc_val->structeach()) + { + $arr[$key] = old_xmlrpc_decode($value); + } + return $arr; + } + } + + /**************************************************************** + * xmlrpc_encode takes native php types and encodes them into * + * xmlrpc PHP object format. * + * BUG: All sequential arrays are turned into structs. I don't * + * know of a good way to determine if an array is sequential * + * only. * + * * + * feature creep -- could support more types via optional type * + * argument. * + * * + * author: Dan Libby (dan@libby.com) * + ****************************************************************/ + function old_xmlrpc_encode($php_val) + { + global $xmlrpcInt; + global $xmlrpcDouble; + global $xmlrpcString; + global $xmlrpcArray; + global $xmlrpcStruct; + global $xmlrpcBoolean; + + $type = gettype($php_val); + $xmlrpc_val = new xmlrpcval; + + switch($type) + { + case 'array': + case 'object': + $arr = array(); + while (list($k,$v) = each($php_val)) + { + $arr[$k] = old_xmlrpc_encode($v); + } + $xmlrpc_val->addStruct($arr); + break; + case 'integer': + $xmlrpc_val->addScalar($php_val, $xmlrpcInt); + break; + case 'double': + $xmlrpc_val->addScalar($php_val, $xmlrpcDouble); + break; + case 'string': + $xmlrpc_val->addScalar($php_val, $xmlrpcString); + break; + // <G_Giunta_2001-02-29> + // Add support for encoding/decoding of booleans, since they are supported in PHP + case 'boolean': + $xmlrpc_val->addScalar($php_val, $xmlrpcBoolean); + break; + // </G_Giunta_2001-02-29> + case 'unknown type': + default: + // giancarlo pinerolo <ping@alt.it> + // it has to return + // an empty object in case (which is already + // at this point), not a boolean. + break; + } + return $xmlrpc_val; + } +?> diff --git a/modules/xmlrpc/xmlrpcs.inc b/modules/xmlrpc/xmlrpcs.inc new file mode 100755 index 00000000..51411712 --- /dev/null +++ b/modules/xmlrpc/xmlrpcs.inc @@ -0,0 +1,466 @@ +<?php +// by Edd Dumbill (C) 1999-2002 +// <edd@usefulinc.com> +// $Id: xmlrpcs.inc,v 1.7 2002/12/19 12:40:01 milosch Exp $ + +// Copyright (c) 1999,2000,2002 Edd Dumbill. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of the "XML-RPC for PHP" nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + + // XML RPC Server class + // requires: xmlrpc.inc + + // listMethods: either a string, or nothing + $_xmlrpcs_listMethods_sig=array(array($xmlrpcArray, $xmlrpcString), array($xmlrpcArray)); + $_xmlrpcs_listMethods_doc='This method lists all the methods that the XML-RPC server knows how to dispatch'; + function _xmlrpcs_listMethods($server, $m) + { + global $xmlrpcerr, $xmlrpcstr, $_xmlrpcs_dmap; + $v=new xmlrpcval(); + $dmap=$server->dmap; + $outAr=array(); + for(reset($dmap); list($key, $val)=each($dmap); ) + { + $outAr[]=new xmlrpcval($key, 'string'); + } + $dmap=$_xmlrpcs_dmap; + for(reset($dmap); list($key, $val)=each($dmap); ) + { + $outAr[]=new xmlrpcval($key, 'string'); + } + $v->addArray($outAr); + return new xmlrpcresp($v); + } + + $_xmlrpcs_methodSignature_sig=array(array($xmlrpcArray, $xmlrpcString)); + $_xmlrpcs_methodSignature_doc='Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature)'; + function _xmlrpcs_methodSignature($server, $m) + { + global $xmlrpcerr, $xmlrpcstr, $_xmlrpcs_dmap; + + $methName=$m->getParam(0); + $methName=$methName->scalarval(); + if (ereg("^system\.", $methName)) + { + $dmap=$_xmlrpcs_dmap; $sysCall=1; + } + else + { + $dmap=$server->dmap; $sysCall=0; + } + // print "<!-- ${methName} -->\n"; + if (isset($dmap[$methName])) + { + if ($dmap[$methName]['signature']) + { + $sigs=array(); + $thesigs=$dmap[$methName]['signature']; + for($i=0; $i<sizeof($thesigs); $i++) + { + $cursig=array(); + $inSig=$thesigs[$i]; + for($j=0; $j<sizeof($inSig); $j++) + { + $cursig[]=new xmlrpcval($inSig[$j], 'string'); + } + $sigs[]=new xmlrpcval($cursig, 'array'); + } + $r=new xmlrpcresp(new xmlrpcval($sigs, 'array')); + } + else + { + $r=new xmlrpcresp(new xmlrpcval('undef', 'string')); + } + } + else + { + $r=new xmlrpcresp(0,$xmlrpcerr['introspect_unknown'], $xmlrpcstr['introspect_unknown']); + } + return $r; + } + + $_xmlrpcs_methodHelp_sig=array(array($xmlrpcString, $xmlrpcString)); + $_xmlrpcs_methodHelp_doc='Returns help text if defined for the method passed, otherwise returns an empty string'; + function _xmlrpcs_methodHelp($server, $m) + { + global $xmlrpcerr, $xmlrpcstr, $_xmlrpcs_dmap; + + $methName=$m->getParam(0); + $methName=$methName->scalarval(); + if (ereg("^system\.", $methName)) + { + $dmap=$_xmlrpcs_dmap; $sysCall=1; + } + else + { + $dmap=$server->dmap; $sysCall=0; + } + // print "<!-- ${methName} -->\n"; + if (isset($dmap[$methName])) + { + if ($dmap[$methName]['docstring']) + { + $r=new xmlrpcresp(new xmlrpcval($dmap[$methName]['docstring']), 'string'); + } + else + { + $r=new xmlrpcresp(new xmlrpcval('', 'string')); + } + } + else + { + $r=new xmlrpcresp(0, $xmlrpcerr['introspect_unknown'], $xmlrpcstr['introspect_unknown']); + } + return $r; + } + + $_xmlrpcs_multicall_sig = array(array($xmlrpcArray, $xmlrpcArray)); + $_xmlrpcs_multicall_doc = 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details'; + + function _xmlrpcs_multicall_error($err) + { + if (is_string($err)) + { + global $xmlrpcerr, $xmlrpcstr; + $str = $xmlrpcstr["multicall_${err}"]; + $code = $xmlrpcerr["multicall_${err}"]; + } + else + { + $code = $err->faultCode(); + $str = $err->faultString(); + } + $struct['faultCode'] = new xmlrpcval($code, 'int'); + $struct['faultString'] = new xmlrpcval($str, 'string'); + return new xmlrpcval($struct, 'struct'); + } + + function _xmlrpcs_multicall_do_call($server, $call) + { + if ($call->kindOf() != 'struct') + { + return _xmlrpcs_multicall_error('notstruct'); + } + $methName = $call->structmem('methodName'); + if (!$methName) + { + return _xmlrpcs_multicall_error('nomethod'); + } + if ($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string') + { + return _xmlrpcs_multicall_error('notstring'); + } + if ($methName->scalarval() == 'system.multicall') + { + return _xmlrpcs_multicall_error('recursion'); + } + + $params = $call->structmem('params'); + if (!$params) + { + return _xmlrpcs_multicall_error('noparams'); + } + if ($params->kindOf() != 'array') + { + return _xmlrpcs_multicall_error('notarray'); + } + $numParams = $params->arraysize(); + + $msg = new xmlrpcmsg($methName->scalarval()); + for ($i = 0; $i < $numParams; $i++) + { + $msg->addParam($params->arraymem($i)); + } + + $result = $server->execute($msg); + + if ($result->faultCode() != 0) + { + return _xmlrpcs_multicall_error($result); // Method returned fault. + } + + return new xmlrpcval(array($result->value()), 'array'); + } + + function _xmlrpcs_multicall($server, $m) + { + $calls = $m->getParam(0); + $numCalls = $calls->arraysize(); + $result = array(); + + for ($i = 0; $i < $numCalls; $i++) + { + $call = $calls->arraymem($i); + $result[$i] = _xmlrpcs_multicall_do_call($server, $call); + } + + return new xmlrpcresp(new xmlrpcval($result, 'array')); + } + + $_xmlrpcs_dmap=array( + 'system.listMethods' => array( + 'function' => '_xmlrpcs_listMethods', + 'signature' => $_xmlrpcs_listMethods_sig, + 'docstring' => $_xmlrpcs_listMethods_doc), + 'system.methodHelp' => array( + 'function' => '_xmlrpcs_methodHelp', + 'signature' => $_xmlrpcs_methodHelp_sig, + 'docstring' => $_xmlrpcs_methodHelp_doc), + 'system.methodSignature' => array( + 'function' => '_xmlrpcs_methodSignature', + 'signature' => $_xmlrpcs_methodSignature_sig, + 'docstring' => $_xmlrpcs_methodSignature_doc), + 'system.multicall' => array( + 'function' => '_xmlrpcs_multicall', + 'signature' => $_xmlrpcs_multicall_sig, + 'docstring' => $_xmlrpcs_multicall_doc + ) + ); + + $_xmlrpc_debuginfo=''; + function xmlrpc_debugmsg($m) + { + global $_xmlrpc_debuginfo; + $_xmlrpc_debuginfo=$_xmlrpc_debuginfo . $m . "\n"; + } + + class xmlrpc_server + { + var $dmap=array(); + + function xmlrpc_server($dispMap='', $serviceNow=1) + { + global $HTTP_RAW_POST_DATA; + // dispMap is a dispatch array of methods + // mapped to function names and signatures + // if a method + // doesn't appear in the map then an unknown + // method error is generated + /* milosch - changed to make passing dispMap optional. + * instead, you can use the class add_to_map() function + * to add functions manually (borrowed from SOAPX4) + */ + if($dispMap) + { + $this->dmap = $dispMap; + if($serviceNow) + { + $this->service(); + } + } + } + + function serializeDebug() + { + global $_xmlrpc_debuginfo; + if ($_xmlrpc_debuginfo!='') + { + return "<!-- DEBUG INFO:\n\n" . $_xmlrpc_debuginfo . "\n-->\n"; + } + else + { + return ''; + } + } + + function service() + { + global $xmlrpc_defencoding; + + $r=$this->parseRequest(); + $payload='<?xml version="1.0" encoding="' . $xmlrpc_defencoding . '"?>' . "\n" + . $this->serializeDebug() + . $r->serialize(); + Header("Content-type: text/xml\r\nContent-length: " . + strlen($payload)); + print $payload; + } + + /* + add a method to the dispatch map + */ + function add_to_map($methodname,$function,$sig,$doc) + { + $this->dmap[$methodname] = array( + 'function' => $function, + 'signature' => $sig, + 'docstring' => $doc + ); + } + + function verifySignature($in, $sig) + { + for($i=0; $i<sizeof($sig); $i++) + { + // check each possible signature in turn + $cursig=$sig[$i]; + if (sizeof($cursig)==$in->getNumParams()+1) + { + $itsOK=1; + for($n=0; $n<$in->getNumParams(); $n++) + { + $p=$in->getParam($n); + // print "<!-- $p -->\n"; + if ($p->kindOf() == 'scalar') + { + $pt=$p->scalartyp(); + } + else + { + $pt=$p->kindOf(); + } + // $n+1 as first type of sig is return type + if ($pt != $cursig[$n+1]) + { + $itsOK=0; + $pno=$n+1; $wanted=$cursig[$n+1]; $got=$pt; + break; + } + } + if ($itsOK) + { + return array(1); + } + } + } + return array(0, "Wanted ${wanted}, got ${got} at param ${pno})"); + } + + function parseRequest($data='') + { + global $_xh,$HTTP_RAW_POST_DATA; + global $xmlrpcerr, $xmlrpcstr, $xmlrpcerrxml, $xmlrpc_defencoding, + $_xmlrpcs_dmap; + + if ($data=='') + { + $data=$HTTP_RAW_POST_DATA; + } + $parser = xml_parser_create($xmlrpc_defencoding); + + $_xh[$parser]=array(); + $_xh[$parser]['st']=''; + $_xh[$parser]['cm']=0; + $_xh[$parser]['isf']=0; + $_xh[$parser]['params']=array(); + $_xh[$parser]['method']=''; + + // decompose incoming XML into request structure + + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true); + xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee'); + xml_set_character_data_handler($parser, 'xmlrpc_cd'); + xml_set_default_handler($parser, 'xmlrpc_dh'); + if (!xml_parse($parser, $data, 1)) + { + // return XML error as a faultCode + $r=new xmlrpcresp(0, + $xmlrpcerrxml+xml_get_error_code($parser), + sprintf('XML error: %s at line %d', + xml_error_string(xml_get_error_code($parser)), + xml_get_current_line_number($parser))); + xml_parser_free($parser); + } + else + { + xml_parser_free($parser); + $m=new xmlrpcmsg($_xh[$parser]['method']); + // now add parameters in + $plist=''; + for($i=0; $i<sizeof($_xh[$parser]['params']); $i++) + { + //print "<!-- " . $_xh[$parser]['params'][$i]. "-->\n"; + $plist.="$i - " . $_xh[$parser]['params'][$i]. " \n"; + eval('$m->addParam(' . $_xh[$parser]['params'][$i]. ');'); + } + // uncomment this to really see what the server's getting! + // xmlrpc_debugmsg($plist); + + $r = $this->execute($m); + } + return $r; + } + + function execute ($m) + { + global $xmlrpcerr, $xmlrpcstr, $_xmlrpcs_dmap; + // now to deal with the method + $methName = $m->method(); + $sysCall = ereg("^system\.", $methName); + $dmap = $sysCall ? $_xmlrpcs_dmap : $this->dmap; + + if (!isset($dmap[$methName]['function'])) + { + // No such method + return new xmlrpcresp(0, + $xmlrpcerr['unknown_method'], + $xmlrpcstr['unknown_method']); + } + + // Check signature. + if (isset($dmap[$methName]['signature'])) + { + $sig = $dmap[$methName]['signature']; + list ($ok, $errstr) = $this->verifySignature($m, $sig); + if (!$ok) + { + // Didn't match. + return new xmlrpcresp(0, + $xmlrpcerr['incorrect_params'], + $xmlrpcstr['incorrect_params'] . ": ${errstr}"); + } + } + + $func = $dmap[$methName]['function']; + + if ($sysCall) + { + return call_user_func($func, $this, $m); + } + else + { + return call_user_func($func, $m); + } + } + + function echoInput() + { + global $HTTP_RAW_POST_DATA; + + // a debugging routine: just echos back the input + // packet as a string value + + $r=new xmlrpcresp; + $r->xv=new xmlrpcval( "'Aha said I: '" . $HTTP_RAW_POST_DATA, 'string'); + print $r->serialize(); + } + } +?> diff --git a/play/index.php b/play/index.php new file mode 100644 index 00000000..0091db10 --- /dev/null +++ b/play/index.php @@ -0,0 +1,282 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/* + + This is the wrapper for opening music streams from this server. This script + will play the local version or redirect to the remote server if that be + the case. Also this will update local statistics for songs as well. + +*/ + +$no_session = true; +require_once('../modules/init.php'); +require_once(conf('prefix') . '/lib/Browser.php'); + + +/* These parameters has better come on the url. */ +$uid = htmlspecialchars($_REQUEST['uid']); +$song_id = htmlspecialchars($_REQUEST['song']); +$sid = htmlspecialchars($_REQUEST['sid']); + +/* Misc Housework */ +$dbh = dbh(); +$user = new User(0,$uid); + +if (conf('require_session') && !conf('xml_rpc')) { + if(!session_exists($sid)) { + die(_("Session Expired: please log in again at") . " " . conf('web_path') . "/login.php"); + } + + // Now that we've confirmed the session is valid + // extend it + extend_session($sid); +} + +/* If we are in demo mode.. die here */ +if (conf('demo_mode') || !$user->has_access('25')) { + if (conf('debug')) { + log_event($user->usrename,' access_denied ', "Streaming Access Denied, " . conf('demo_mode') . "is the value of demo_mode. Current user level is $user->access"); + } +// access_denied(); +} + +/* + If they are using access lists let's make sure + that they have enough access to play this mojo +*/ +if (conf('access_control')) { + + $access = new Access(0); + if (!$access->check("25", $_SERVER['REMOTE_ADDR'])) { + if (conf('debug')) { + log_event($user->username,' access_denied ', "Streaming Access Denied, " . $_SERVER['REMOTE_ADDR'] . " does not have stream level access"); + } + access_denied(); + } + +} // access_control is enabled + + +// require a uid and valid song +if ( isset( $uid ) ) { + $song = new Song($song_id); + $song->format_song(); + + // Create the user object if possible + + if (!$song->file OR ( !is_readable($song->file) AND $catalog->catalog_type != 'remote' ) ) { + if (conf('debug')) { + log_event($user->username,' file_not_found ',"Error song ($song->title) does not have a valid filename specified"); + } + echo "Error: No Song"; + exit; + } + if ($song->status === 'disabled') { + exit; + } + if ($user->access === 'disabled') { + if (conf('debug')) { + log_event($user->username,' user_disabled ',"Error $user->username is currently disabled, stream access denied"); + } + echo "Error: User Disabled"; + exit; + } + if (!$user->id && !$user->is_xmlrpc()) { + if (conf('debug')) { + log_event($user->username,' user_not_found ',"Error $user->id not found, stream access denied"); + } + echo "Error: No User Found"; + exit; + } + +} +else { + if (conf('debug')) { + log_event("Unknown", ' user_not_found ',"Error no UID passed with URL, stream access denied"); + } + echo "Error: No UID specified"; + exit; +} + +$catalog = new Catalog($song->catalog); + +if ( $catalog->catalog_type == 'remote' ) { + // redirect to the remote host's play path + header("Location: $song->file"); +} +else { + if ($user->prefs['play_type'] == 'downsample') { + $ds = $user->prefs['sample_rate']; + } + + // Update the users last seen + $user->update_last_seen(); + + // Garbage collection for stale entries in the now_playing table + $time = time(); + $expire = $time - 900; // 86400 seconds = 1 day + $sql = "DELETE FROM now_playing WHERE start_time < $expire"; + $db_result = mysql_query($sql, $dbh); + + // If we are running in Legalize mode, don't play songs already playing + if (conf('lock_songs') == 'true') { + $sql = "SELECT COUNT(*) FROM now_playing" . + " WHERE song_id = '$song_id'"; + $db_result = mysql_query($sql, $dbh); + while ($r = mysql_fetch_row($db_result)) { + if ($r[0] == 1) { + // Song is already playing, so exit without returning song + exit; + } + } + } + + // Put this song in the now_playing table + $end_time = time() - $song->time; + $sql = "INSERT INTO now_playing (`song_id`, `user_id`, `start_time`)" . + " VALUES ('$song_id', '$uid', '$end_time')"; + $db_result = mysql_query($sql, $dbh); + $lastid = mysql_insert_id($dbh); + + // make fread binary safe + set_magic_quotes_runtime(0); + + // don't abort the script if user skips this song because we need to update now_playing + ignore_user_abort(TRUE); + + /* Format the Song Name */ + if (conf('stream_name_format')) { + $song_name = conf('stream_name_format'); + $song_name = str_replace("%basename",basename($song->file),$song_name); + $song_name = str_replace("%filename",$song->file,$song_name); + $song_name = str_replace("%type",$song->type,$song_name); + $song_name = str_replace("%catalog",$catalog->name,$song_name); + $song_name = str_replace("%A",$song->f_album_full,$song_name); // this and next could be truncated version + $song_name = str_replace("%a",$song->f_artist_full,$song_name); + $song_name = str_replace("%C",$catalog->path,$song_name); + $song_name = str_replace("%c",$song->comment,$song_name); + $song_name = str_replace("%g",$song->f_genre,$song_name); + $song_name = str_replace("%T",$song->track,$song_name); + $song_name = str_replace("%t",$song->title,$song_name); + $song_name = str_replace("%y",$song->year,$song_name); + } + else { + $song_name = $song->f_artist_full . " - " . $song->title . "." . $song->type; + } + + + + // Send file, possible at a byte offset + $fp = @fopen($song->file, 'r'); + + if (!is_resource($fp)) { + if (conf('debug')) { + log_event($user->username,' file_read_error ',"Error: Unable to open $song->file for reading"); + } + cleanup_and_exit($lastid); + } + + $startArray = sscanf( $_SERVER[ "HTTP_RANGE" ], "bytes=%d-" ); + $start = $startArray[0]; + + // Generate browser class for sending headers + $browser = new Browser(); + header("Accept-Ranges: bytes" ); + header("Content-Length: " . $song->size); + + // Prevent the script from timing out + set_time_limit(0); + + if ($ds) { + $ds = $user->prefs['sample_rate']; + $dsratio = $ds/$song->bitrate*1024; + $browser->downloadHeaders($song_name, $song->mime, false,$dsratio*$song->size); + + /* Get Offset */ + $offset = ( $start*$song->time )/( $dsratio*$song->size ); + $offsetmm = floor($offset/60); + $offsetss = floor($offset-$offsetmm*60); + $offset = sprintf("%02d.%02d",$offsetmm,$offsetss); + + /* Get EOF */ + $eofmm = floor($song->time/60); + $eofss = floor($song->time-$eofmm*60); + $eof = sprintf("%02d.%02d",$eofmm,$eofss); + + /* Replace Variables */ + $downsample_command = conf('downsample_cmd'); + $downsample_command = str_replace("%FILE%",$song->file,$downsample_command); + $downsample_command = str_replace("%OFFSET%",$offset,$downsample_command); + $downsample_command = str_replace("%EOF%",$eof,$downsample_command); + $downsample_command = str_replace("%SAMPLE%",$ds,$downsample_command); + + // If we are debugging log this event + if (conf('debug')) { + $message = "Exec: $downsample_command"; + log_event($user->username,'downsample',$message); + } // if debug + + $fp = @popen($downsample_command, 'r'); + } // if downsampling + + elseif ($start) { + $browser->downloadHeaders($song_name, $song->mime, false, $song->size); + fseek( $fp, $start ); + $range = $start ."-". $song->size . "/" . $song->size; + header("HTTP/1.1 206 Partial Content"); + header("Content-Range: bytes=$range"); + } + /* Last but not least pump em out */ + else { + $browser->downloadHeaders($song_name, $song->mime, false, $song->size); + } + + + while (!feof($fp) && (connection_status() == 0)) { + print(fread($fp, 8192)); + } + + if ( ! $start ) { + $user->update_stats($song_id); + } + + // If the played flag isn't set, set it + if (!$song->played) { + $sql = "UPDATE song SET played='1' WHERE id='$song->id'"; + $db_results = mysql_query($sql, $dbh); + } + + // Remove the song from the now_playing table + $sql = "DELETE FROM now_playing WHERE id = '$lastid'"; + $db_result = mysql_query($sql, $dbh); + + /* Clean up any open ends */ + if ($ds) { + @pclose($fp); + } + else { + @fclose($fp); + } + +} + +?> diff --git a/play/pupload.php b/play/pupload.php new file mode 100644 index 00000000..2cc73f10 --- /dev/null +++ b/play/pupload.php @@ -0,0 +1,227 @@ +<?php
+/*
+
+ Copyright (c) 2001 - 2005 Ampache.org
+ All rights reserved.
+
+ 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+/*
+
+ This is the wrapper for opening music streams from this server. This script
+ will play the local version or redirect to the remote server if that be
+ the case. Also this will update local statistics for songs as well.
+
+*/
+
+$no_session = true;
+require_once('../modules/init.php');
+require_once(conf('prefix') . '/lib/Browser.php');
+
+$browser = new Browser();
+
+$dbh = setup_sess_db("song",
+ libglue_param('mysql_host'),
+ libglue_param('mysql_db'),
+ libglue_param('mysql_user'),
+ libglue_param('mysql_pass')
+ );
+
+/* These parameters has better come on the url. */
+$song = htmlspecialchars($_REQUEST['song']);
+$song_nm = htmlspecialchars($_REQUEST['song']);
+$action = htmlspecialchars($_REQUEST['action']);
+$uid = htmlspecialchars($_REQUEST['uid']);
+$web_path = conf('web_path');
+
+
+/* If we are in demo mode.. die here */
+if (conf('demo_mode')) {
+ exit();
+}
+
+/*
+ If they are using access lists let's make sure
+ that they have enough access to play this mojo
+*/
+if (conf('access_control') == "true") {
+
+ $access = new Access(0);
+ if (!$access->check("25", $_SERVER['REMOTE_ADDR'])) {
+ echo "Error: Access Denied, Invalid Source ADDR";
+ exit();
+ }
+
+} // access_control is enabled
+
+// Get site preferences
+$site = new User(0);
+$site->get_preferences();
+
+
+// require a uid and valid song
+if ( isset( $uid ) ) {
+ // Create the user object if possible
+ $user = new User(0,$uid);
+
+ $song = $site->prefs['upload_dir'] . $song;
+
+ if (!file_exists ( $song )) { echo "Error: No Song"; exit; }
+ if ($user->access === 'disabled') { echo "Error: User Disabled"; exit; }
+ if (!$user->id && !$user->is_xmlrpc()) { echo "Error: No User Found"; exit; }
+
+}
+else {
+ exit;
+}
+
+/* Get file info */
+$audio_info = new Audioinfo();
+$results = $audio_info->Info($song);
+
+$order = conf('id3tag_order');
+
+// set the $key to the first found tag style (according to their prefs)
+foreach($order as $key) {
+ if ($results[$key]) { break; }
+}
+
+// Fetch Song Info
+$artist_name = addslashes($results[$key]['artist']);
+$album_name = addslashes($results[$key]['album']);
+$title = addslashes($results[$key]['title']);
+$song_time = intval($results['playing_time']);
+$size = filesize($song);
+preg_match('/\.([A-Za-z0-9]+)$/', $song,$results);
+
+$type = $results[1];
+
+switch ($type) {
+ case "ogg":
+ $mime = "application/x-ogg";
+ break;
+ case "mp3":
+ case "mpeg3":
+ $mime = "audio/mpeg";
+ case "rm":
+ $mime = "audio/x-realaudio";
+ break;
+}
+
+
+if ( $_REQUEST['action'] == 'm3u' ) {
+
+ if($temp_user->prefs['play_type'] == 'local_play') {
+ // Play the song locally using local play configuration
+ $song_name = $artist . " - " . $title . "." . $type;;
+ $sess = $_COOKIE[libglue_param('sess_name')];
+ //echo "Song Name: $song_name<BR>\n";
+ $url = escapeshellarg("$web_path/play/pupload.php?song=$song_nm&uid=$user->id&sid=$sess");
+ $localplay_add = conf('localplay_add');
+ $localplay_add = str_replace("%URL%", $url, $localplay_add);
+ //echo "Executing: $localplay_add<BR>";
+ exec($localplay_add);
+ header("Location: $web_path");
+
+ }
+ else
+ {
+
+ if (conf('force_http_play')) {
+ $http_port = conf('http_port');
+ $web_path = preg_replace("/https/","http",$web_path);
+ $web_path = preg_replace("/:\d+/",":$http_port",$web_path);
+ }
+
+ // Send the client an m3u playlist
+ header("Cache-control: public");
+ header("Content-Disposition: filename=playlist.m3u");
+ header("Content-Type: audio/x-mpegurl;");
+ echo "#EXTM3U\n";
+
+ $song_name = $song . " - " . $title . "." . $type;
+ $song_name = $artist . " - " . $title . "." . $song->type;;
+ echo "#EXTINF:$song_time,$title\n";
+ $sess = $_COOKIE[libglue_param('sess_name')];
+ if($temp_user->prefs['down-sample'] == 'true')
+ $ds = $temp_user->prefs['sample_rate'];
+ echo "$web_path/play/pupload.php?song=" . rawurlencode($song_nm) . "&uid=$user->id&sid=$sess";
+
+ }
+ exit;
+}
+else
+{
+ // Check to see if we should be downsampling
+ $user->get_preferences();
+
+ if ($user->prefs['play_type'] == 'downsample') {
+ $ds = $user->prefs['sample_rate'];
+ }
+
+ // make fread binary safe
+ set_magic_quotes_runtime(0);
+
+ // don't abort the script if user skips this song because we need to update now_playing
+ ignore_user_abort(TRUE);
+
+ // Build the Song Name
+ $song_name = $artist_name . " - " . $title . "." . $type;
+
+ // Send headers
+ $browser->downloadHeaders($song_name, $mime, false, $size );
+ header("Accept-Ranges: bytes" );
+
+ // Send file, possible at a byte offset
+ $fp = fopen($song, 'r');
+
+ $startArray = sscanf( $_SERVER[ "HTTP_RANGE" ], "bytes=%d-" );
+ $start = $startArray[0];
+
+ // Prevent the script from timing out
+ set_time_limit(0);
+
+ if ($ds) {
+ // FIXME:: $dsratio needs to be set
+ $ds = $user->prefs['sample_rate']; //ds hack
+ $dsratio = $ds/$song->bitrate*1000; //resample hack
+ $browser->downloadHeaders($song_name, $mime, false,$dsratio*$size);
+ $offset = ( $start*$song_time )/( $dsratio*$size );
+ $offsetmm = floor($offset/60);
+ $offsetss = floor($offset-$offsetmm*60);
+ $offset = sprintf("%02d.%02d",$offsetmm,$offsetss);
+ $cmd = "mp3splt -qnf \"$song->file\" $offset EOF -o - | lame --mp3input -q 3 -b $ds -S - -";
+ if (!$handle = fopen('/tmp/ampache.log', 'a'));
+ fwrite($handle,"$offset |");
+ fclose($handle);
+ $fp = popen($cmd, 'r');
+ $close='pclose';
+ } // if downsampling
+
+ elseif ($start) {
+ fseek( $fp, $start );
+ header("Content-Range: bytes=$start-$size/$song");
+ }
+
+ while (!feof($fp) && (connection_status() == 0)) {
+ print(fread($fp, 8192));
+ }
+
+ fclose($fp);
+
+}
+
+?>
diff --git a/playlist.php b/playlist.php new file mode 100644 index 00000000..c92cdb0e --- /dev/null +++ b/playlist.php @@ -0,0 +1,299 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All Rights Reserved + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/* + + Playlist mojo for adding, viewing, deleting, etc. + +*/ + +require_once("modules/init.php"); + +// Get user object for later + +if (isset($_REQUEST['action'])) { + $action = scrub_in($_REQUEST['action']); +} + +$type = scrub_in($_REQUEST['type']); + +if (isset($_REQUEST['results'])) { + $results = scrub_in($_REQUEST['results']); +} +else { + $results = array(); +} + +if (isset($_REQUEST['artist_id'])) { + $artist_id = scrub_in($_REQUEST['artist_id']); +} + +if (isset($_REQUEST['playlist_name'])) { + $playlist_name = scrub_in($_REQUEST['playlist_name']); +} + +if (isset($_REQUEST['new_playlist_name'])) { + $new_playlist_name = scrub_in($_REQUEST['new_playlist_name']); +} + +if (isset($_REQUEST['playlist_id'])) { + $playlist_id = scrub_in($_REQUEST['playlist_id']); +} + +if (isset($_REQUEST['confirm'])) { + $confirm = scrub_in($_REQUEST['confirm']); +} + +if (isset($_REQUEST['song'])) { + $song_ids = scrub_in($_REQUEST['song']); +} + +/* Prepare the Variables */ +$playlist = new Playlist(scrub_in($_REQUEST['playlist_id'])); + +/* First Switch */ +// Have to handle this here, since we use this file +// for playback of the "Play Selected Stuff" and display (for now) +switch ($action) { + case _("Flag Selected"): + require_once(conf('prefix').'/lib/flag.php'); + $flags = scrub_in($_REQUEST['song']); + set_flag_value($flags, 'badid3',''); + header("Location:" . conf('web_path')."/admin/flags.php" ); + break; + case _("Edit Selected"): + require_once(conf('prefix').'/lib/flag.php'); + $flags = scrub_in($_REQUEST['song']); + set_flag_value($flags, 'badid3',''); + $count = add_to_edit_queue($flags); + session_write_close(); + header( 'Location: '.conf('web_path').'/admin/flags.php?action='.urlencode($action) ); + exit(); + break; + default: + break; +} // end first action switch + +show_template('header'); + +show_menu_items('Playlists'); + +show_playlist_menu(); + + +$playlist = new Playlist($playlist_id); + +if ( isset($playlist_id) && ($playlist_id != 0) && $_REQUEST['action'] != 'delete_playlist' ) { + // Get the playlist and check access + $pluser = new User(0,$playlist->owner); + + if (! isset($playlist->id)) { + show_playlist_access_error($playlist_id, $pluser->username); + } + + echo "<div style=\"width:50%;\" class=\"text-box\">\n"; + echo "<span class=\"header2\">$playlist->name</span><br />"; + echo " " . _("owned by") . " $pluser->fullname ($pluser->username)<br />"; + echo "<ul>"; + if ($pluser->id == $user->id || $user->access === 'admin') { + echo "<li><a href=\"" . conf('web_path') . "/playlist.php?action=edit&playlist_id=$playlist->id\">" . _("Edit Playlist") . "</a></li>\n"; + } + if (count($playlist->get_songs()) > 0) { + echo "<li><a href=\"" . conf('web_path') . "/song.php?action=m3u&playlist_id=$playlist->id\">" . _("Play Full Playlist") . "</a></li>\n"; + echo "<li><a href=\"" . conf('web_path') . "/song.php?action=random&playlist_id=$playlist->id\">" . _("Play Random") . "</a></li>\n"; + } + echo "</ul>"; + echo "</div>"; +} + + +switch($action) { + // Add to a playlist + case 'Add to': + case 'add_to': + if ($playlist_id == 0) { + // Creating a new playlist + $playlist_name = _("New Playlist") . " - " . date("m/j/y, g:i a"); + $playlist->create_playlist($playlist_name, $user->id, 'private'); + } + + if ($type === 'album') { + if ($song_ids = get_songs_from_type($type, $results, $artist_id)) { + $playlist->add_songs($song_ids); + } + } + else { + if (isset($song_ids) && is_array($song_ids)) { + $playlist->add_songs($song_ids); + } + } + show_playlist($playlist->id); + break; + + case 'Create': + $playlist->create_playlist($playlist_name, $user->id, $type); + show_playlists(); + break; + + case 'delete_playlist': + if ($_REQUEST['confirm'] === 'Yes') { + + $playlist->playlist($_REQUEST['playlist_id']); + $playlist->delete(); + show_confirmation("Playlist Deleted","The $playlist->name Playlist has been deleted","playlist.php"); + } + elseif ($_REQUEST['confirm'] === 'No') { + show_songs($playlist->get_songs(), $_REQUEST['playlist_id']); + } + else { + show_confirm_action("Are you sure you want to delete '$playlist->name' playlist?", + "playlist.php", + "action=delete_playlist&playlist_id=$playlist_id"); + } + break; + + case 'edit': + case 'Edit': + show_playlist_edit($playlist); + break; + + case 'new': + show_playlist_create(); + break; + + case 'remove_song': + case 'Remove Selected Tracks': + $playlist->remove_songs($song_ids); + show_songs($playlist->get_songs(), $playlist_id); + break; + + case 'Update': + $playlist->update_type($type); + $playlist->update_name($new_playlist_name); + echo _("Playlist updated."); + break; + + case 'Update Selected': + pl_update_selected(); + break; + case 'import_playlist': + $filename = scrub_in($_REQUEST['filename']); + $catalog = new Catalog(); + if ($catalog->import_m3u($filename)) { + show_confirmation($_REQUEST['playlist_type'] . " Imported",$filename . " was imported as a playlist","playlist.php"); + } // it worked + else { + show_confirmation("Import Failure",$filename . " failed to import correctly, this can be because the file wasn't found or no songs were matched","playlist.php"); + } // it didnt' work + break; + case 'view_list': + case 'view': + case 'View': + show_playlist($playlist->id); + break; + case 'show_import_playlist': + $playlist->show_import(); + break; + case 'set_track_numbers': + case 'Set Track Numbers': + $song_ids = scrub_in($_REQUEST['song']); + foreach ($song_ids as $song_id) { + $track = scrub_in($_REQUEST['tr_' . $song_id]); + $changes[] = array('song_id' => $song_id, 'track' => $track); + } + + $playlist->update_track_numbers($changes); + show_playlist($playlist->id); + break; + + default: + show_playlists(); + +} //switch($action) + +echo "<br /><br />"; +show_menu_items('Playlists'); + +?> +</body> +</html> + + +<?php + +/* Function definitions for this file */ + +/*************************/ +function pl_update_selected() { + + $username = scrub_in($_SESSION['userdata']['id']); + if ($user->has_access(100)) { + // we have to update the current numbers for the artist these were + // for and who they will become + $artists_to_update = array(); + $artists_to_update[] = $artist; + + while ( list($index, $s) = each($song) ) { + $info = get_song_info($s); + $artists_to_update[] = $info->artist; + + if ( $update_artist ) { + $info->artist = $artist; + } + + if ( $update_album ) { + $info->album = $album; + } + + if ( $update_genre ) { + $info->genre = $genre; + } + + // now just update the song in the db and you're good to go + update_song($info->id, $info->title, + $info->artist, $info->album, $info->genre); + + // let's update the local file (if we can) + if ( is_writable($info->file) ) { + $id3 = new id3( $info->file ); + $id3->artists = get_artist_name($info->artist); + $id3->album = get_album_name($info->album); + $genre_info = get_genre($info->genre); + $id3->genre = $genre_info->name; + $id3->genreno = $genre_info->id; + $id3->write(); + } + } + + $artists_to_update = array_unique($artists_to_update); + + foreach ($artists_to_update as $art) { + update_artist_info($art); + } + + header("Location:" . $HTTP_REFERER ); + }//admin access + else { + header("Location:" . conf('web_path') . "/index.php?access=denied" ); + } +} //function pl_update_selected +?> diff --git a/preferences.php b/preferences.php new file mode 100644 index 00000000..065f4512 --- /dev/null +++ b/preferences.php @@ -0,0 +1,70 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Preferences page + Preferences page for whole site, and where + the admins do editing of other users preferences + +*/ + +require('modules/init.php'); + +switch(scrub_in($_REQUEST['action'])) { + + case 'update_preferences': + $user_id = scrub_in($_REQUEST['user_id']); + update_preferences($user_id); + $preferences = $user->get_preferences(); + break; + default: + $user_id = $user->id; + $preferences = $user->get_preferences(); + break; + +} // End Switch Action + +if (!$user->fullname) { + $fullname = "Site"; +} +else { + $fullname = $user->fullname; +} + + +// HEADER +show_template('header'); +show_menu_items('Preferences'); +show_clear(); +// HEADER + +// Set Target +$target = "/preferences.php"; + +// Show the default preferences page +require (conf('prefix') . "/templates/show_preferences.inc"); + + +// FOOTER +show_menu_items('Preferences'); + +?> diff --git a/qtembed.php b/qtembed.php new file mode 100644 index 00000000..47c87806 --- /dev/null +++ b/qtembed.php @@ -0,0 +1,86 @@ +<?php
+/*
+
+ Copyright (c) 2004 Ampache.org
+ All rights reserved.
+
+ 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+/*
+
+ This script generates the HTML needed for
+ embeded QuickTime player.
+
+*/
+
+require('modules/init.php');
+
+
+/* Just pass on all the parameters */
+$web_path = conf('web_path');
+$play_url = "$web_path/play/?".$_SERVER["QUERY_STRING"];
+
+?>
+
+
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+<head>
+ <title>QT Embedded Player</title>
+
+<style type="text/css">
+<!--
+ BODY {
+ margin: 0pt;
+ padding: 0pt;
+ }
+-->
+</style>
+
+</head>
+
+
+
+<body>
+
+<OBJECT CLASSID="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"
+ WIDTH="100%"
+ HEIGHT="16"
+ NAME="movie"
+ CODEBASE="http://www.apple.com/qtactivex/qtplugin.cab">
+ <PARAM name="SRC" VALUE="<?=$play_url?>">
+ <PARAM name="AUTOPLAY" VALUE="true">
+ <PARAM name="CONTROLLER" VALUE="true">
+ <PARAM name="QTNEXT1" VALUE="<javascript:top.next_song();>">
+ <EMBED
+ NAME="movie"
+ SRC="<?=$play_url?>"
+ WIDTH="100%"
+ HEIGHT="16"
+ AUTOPLAY="true"
+ CONTROLLER="true"
+ QTNEXT1="<javascript:top.next_song();>"
+ PLUGINSPAGE="http://www.apple.com/quicktime/download/"
+ >
+ </EMBED>
+</OBJECT>
+
+</body>
+</html>
diff --git a/register.php b/register.php new file mode 100644 index 00000000..010fb380 --- /dev/null +++ b/register.php @@ -0,0 +1,116 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header User Registration page + @discussion this page handles new user + registration, this is by default disabled + (it allows public reg) + +*/ +$no_session = 1; +require_once ("modules/init.php"); + +/* Check Perms */ +if (!conf('allow_public_registration')) { + access_denied(); +} + + +$action = scrub_in($_REQUEST['action']); + + +show_template('header'); + +/* Start switch based on action passed */ +switch ($action) { + case 'add_user': + if (conf('demo_mode')) { break; } + $username = scrub_in($_REQUEST['username']); + $fullname = scrub_in($_REQUEST['fullname']); + $email = scrub_in($_REQUEST['email']); + $pass1 = scrub_in($_REQUEST['password_1']); + $pass2 = scrub_in($_REQUEST['password_2']); + if ( $pass1 != $pass2 ) { + echo "<CENTER><B>Your passwords do not match</b><br />"; + echo "Click <B><a href=\"javascript:history.back(1)\">here</a></B> to go back"; + break; + } +// INSERTED BY TERRY FOR MAIL ADDRESS CHECK + require("../templates/validateEmailFormat.php"); + require("../templates/validateEmail.php"); + // get the address from wherever you get it ... form input, etc. + // $email = "info@xs4all.nl"; + // $email = $_GET['email']; + // try a few extra times if we're concerned about fsockopen problems + $attempt = 0; + $max_attempts = 3; + $response_code = ""; + + while ( $response_code == "" || strstr( $response_code, "fsockopen error" )) { + $validate_results = validateEmail( $email ); + + $response_code = $validate_results[1]; + if($attempt == $max_attempts) break; + $attempt++; + } + + // display results + //echo "successful check during attempt #$attempt<br />"; + if ( $validate_results[0] ) { + $validation = str_rand(20); + $regdate = "2004-01-01"; + if (!$user->create($username, $fullname, $email, $pass1, $access, $validation)) { + echo "<CENTER>Registratie van gebruiker gefaald!<br />"; + echo "User ID of Email adres reeds in gebruik<br />"; + echo "Click <B><a href=\"javascript:history.back(1)\">here</a></B> to go back"; + break; + } + echo "<CENTER>Successvol gechecked na #$attempt poging<br />"; + echo "Email verificatie van het email adres <B>$email</B> is gelukt<br />"; + echo "<B>Your User ID is Created<br />"; + echo "<P>You will receive an email when your account is approved<B><br />"; + echo "<A HREF=\"http://www.pb1unx.com\">Ga naar homepagina</A><br />"; + } else { + echo "<CENTER>Geen successvolle check. Er is/zijn #$attempt pogingen gedaan!<br />"; + echo "D'oh! <B>$email</B> is niet in orde!<br />"; + echo "$validate_results[1]<br />"; + echo "Click <B><a href=\"javascript:history.back(1)\">here</a></B> to go back"; + } + break; + + + case 'show_add_user': + default: + if (conf('demo_mode')) { break; } + $values = array('type'=>"new_user"); + show_user_registration($values); + break; + +} + +echo "<br /><br />"; + +?> + +</body> +</html> diff --git a/rss.php b/rss.php new file mode 100644 index 00000000..9fa23d9f --- /dev/null +++ b/rss.php @@ -0,0 +1,28 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +$no_session = 1; +require('modules/init.php'); + +echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; +show_now_playingRSS(); +?> diff --git a/search.php b/search.php new file mode 100644 index 00000000..34208ee3 --- /dev/null +++ b/search.php @@ -0,0 +1,43 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Search page + Search stuff. Can do by artist, album and song title. + + Also case-insensitive for now. + +*/ + +require_once("modules/init.php"); + +show_template('header'); +show_menu_items('Search'); +show_clear(); +show_template('show_search'); + +if ($_REQUEST['action'] === 'search') { + run_search($_REQUEST['search_string'], $_REQUEST['search_field'], $_REQUEST['search_type']); +} +echo "<br /><br />"; +show_menu_items('Search'); +?> + +</body> +</html> diff --git a/server.php b/server.php new file mode 100644 index 00000000..8d21d22c --- /dev/null +++ b/server.php @@ -0,0 +1,37 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +$no_session = true; +require_once('modules/init.php'); +require_once(conf('prefix') . "/modules/xmlrpc/xmlrpcs.inc"); + +/* Setup the vars we are going to need */ +$access = new Access(); + +// ** check that the remote server has access to this catalog +if ($access->check('75',$_SERVER['REMOTE_ADDR'])) { + $s = new xmlrpc_server( array( "remote_server_query" => array("function" => "remote_server_query"), + "remote_song_query" => array("function" => "remote_song_query") ) ); +} +else { + // Access Denied... Sucka!! + $s = new xmlrpc_server( array( "remote_server_query" => array("function" => "remote_server_denied"))); +} + +?> diff --git a/song.php b/song.php new file mode 100644 index 00000000..e75f5aaf --- /dev/null +++ b/song.php @@ -0,0 +1,135 @@ +<?php +/* + + Copyright (c) 2001 - 2005 ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Song Document + @discussion Actually play files from albums, artists or just given + a bunch of id's. + Special thanx goes to Mike Payson and Jon Disnard for the means + to do this. +*/ + +require('modules/init.php'); + +/* If we are running a demo, quick while you still can! */ +if (conf('demo_mode') || !$user->has_access('25')) { + access_denied(); +} + + +$web_path = conf('web_path'); + +$song_ids = array(); +$web_path = conf('web_path'); + +$action = scrub_in($_REQUEST['action']); + +switch ($action) { + case 'play_selected': + $type = scrub_in($_REQUEST['type']); + if ($type == 'album') { + $song_ids = get_songs_from_type($type, $_POST['song'], $_REQUEST['artist_id']); + } + else { + $song_ids = $_POST['song']; + } + $_REQUEST['action'] = "m3u"; + break; + default: + break; + +} // end action switch + +if ($_REQUEST['album']) { + $song_ids = get_song_ids_from_album( $_REQUEST['album'] ); +} +elseif ( $_REQUEST['playlist_id']) { + $playlist = new Playlist($_REQUEST['playlist_id']); + if ($_REQUEST['action'] == "random") { + $song_ids = $playlist->get_random_songs(); + $_REQUEST['action'] = "m3u"; + } + else { + $song_ids = $playlist->get_songs(); + } +} +elseif ( $_REQUEST['artist'] ) { + $song_ids = get_song_ids_from_artist( $_REQUEST['artist'] ); +} +/*! + @action Random Song + @discussion takes a genre and catalog and + returns random songs based upong that +*/ +elseif ( $_REQUEST['random'] ) { + if($_REQUEST['genre'][0] != -1) { + $matchlist['genre'] = $_REQUEST['genre']; + } + if($_REQUEST['catalog'] != -1) { + $matchlist['catalog'] = $_REQUEST['catalog']; + } + + /* Setup the options array */ + $options = array('limit' => $_REQUEST['random'], 'unplayed' => $_REQUEST['unplayed'], 'full_album' => $_REQUEST['full_album'], 'full_artist' => $_REQUEST['full_artist']); + + + $song_ids = get_random_songs($options, $matchlist); +} +elseif ( $_REQUEST['artist_random'] ) { + $artist = new Artist($_REQUEST['artist_random']); + $artist->get_count(); + $song_ids = $artist->get_random_songs(); +} +elseif ( $_REQUEST['album_random'] ) { + $album = new Album($_REQUEST['album_random']); + $song_ids = $album->get_random_songs(); +} +elseif ( $_REQUEST['song'] AND !is_array($_REQUEST['song'])) { + $song_ids = array(); + $song_ids[0] = $_REQUEST['song']; +} +elseif ( $_REQUEST['popular_songs'] ) { + $song_ids = get_popular_songs($_REQUEST['popular_songs'], 'global'); +} +elseif ( $_REQUEST['your_popular_songs'] ) { + $song_ids = get_popular_songs($_REQUEST['your_popular_songs'], 'your', $user->id); +} + + +if ( !$_REQUEST['action'] or $_REQUEST['action'] == 'm3u' ) { + + $stream_type = conf('playlist_type'); + + if ($user->prefs['play_type'] != "stream" AND $user->prefs['play_type'] != "downsample") { + $stream_type = $user->prefs['play_type']; + } + $stream = new Stream($stream_type,$song_ids); + $stream->start(); +} // if streaming + +elseif ( $_REQUEST['action'] == 'show' ) { + // Show the song details, or the list + // of songs. + // TODO +} + + diff --git a/sql/ampache.sql b/sql/ampache.sql new file mode 100755 index 00000000..116bc2ae --- /dev/null +++ b/sql/ampache.sql @@ -0,0 +1,591 @@ +-- MySQL dump 10.9 +-- +-- Host: localhost Database: ampache +-- ------------------------------------------------------ +-- Server version 4.1.9-Debian_2-log +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO,MYSQL323' */; + +-- +-- Table structure for table `access_list` +-- + +DROP TABLE IF EXISTS `access_list`; +CREATE TABLE `access_list` ( + `id` int(11) unsigned NOT NULL auto_increment, + `name` varchar(255) NOT NULL default '', + `start` int(11) unsigned NOT NULL default '0', + `end` int(11) unsigned NOT NULL default '0', + `level` smallint(3) unsigned NOT NULL default '5', + PRIMARY KEY (`id`), + KEY `ip` (`start`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `access_list` +-- + + +/*!40000 ALTER TABLE `access_list` DISABLE KEYS */; +LOCK TABLES `access_list` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `access_list` ENABLE KEYS */; + +-- +-- Table structure for table `album` +-- + +DROP TABLE IF EXISTS `album`; +CREATE TABLE `album` ( + `id` int(11) unsigned NOT NULL auto_increment, + `name` varchar(255) NOT NULL default '', + `prefix` enum('The','An','A') default NULL, + `year` int(4) unsigned NOT NULL default '1984', + `art` mediumblob, + `art_mime` varchar(128) default NULL, + PRIMARY KEY (`id`), + KEY `name` (`name`), + KEY `id` (`id`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `album` +-- + + +/*!40000 ALTER TABLE `album` DISABLE KEYS */; +LOCK TABLES `album` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `album` ENABLE KEYS */; + +-- +-- Table structure for table `artist` +-- + +DROP TABLE IF EXISTS `artist`; +CREATE TABLE `artist` ( + `id` int(11) unsigned NOT NULL auto_increment, + `name` varchar(255) NOT NULL default '', + `prefix` enum('The','An','A') default NULL, + PRIMARY KEY (`id`), + KEY `name` (`name`), + KEY `id` (`id`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `artist` +-- + + +/*!40000 ALTER TABLE `artist` DISABLE KEYS */; +LOCK TABLES `artist` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `artist` ENABLE KEYS */; + +-- +-- Table structure for table `catalog` +-- + +DROP TABLE IF EXISTS `catalog`; +CREATE TABLE `catalog` ( + `id` int(11) unsigned NOT NULL auto_increment, + `name` varchar(128) NOT NULL default '', + `path` varchar(255) NOT NULL default '', + `catalog_type` enum('local','remote') NOT NULL default 'local', + `last_update` int(11) unsigned NOT NULL default '0', + `last_add` int(11) unsigned NOT NULL default '0', + `enabled` enum('true','false') NOT NULL default 'true', + `private` int(1) unsigned NOT NULL default '0', + `id3_set_command` varchar(255) NOT NULL default '/usr/bin/id3v2 -a "%a" -A "%A" -t "%t" -g %g -y %y -T %T -c "%c" "%filename"', + `rename_pattern` varchar(255) NOT NULL default '%a - %T - %t.mp3', + `sort_pattern` varchar(255) NOT NULL default '%C/%a/%A', + `gather_types` varchar(255) NOT NULL default '', + PRIMARY KEY (`id`), + KEY `enabled` (`enabled`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `catalog` +-- + + +/*!40000 ALTER TABLE `catalog` DISABLE KEYS */; +LOCK TABLES `catalog` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `catalog` ENABLE KEYS */; + +-- +-- Table structure for table `flagged` +-- + +DROP TABLE IF EXISTS `flagged`; +CREATE TABLE `flagged` ( + `id` int(11) NOT NULL auto_increment, + `user` int(10) unsigned NOT NULL default '0', + `type` enum('badmp3','badid3','newid3','setid3','del','sort','ren','notify','done') NOT NULL default 'badid3', + `song` int(11) unsigned NOT NULL default '0', + `date` int(10) unsigned NOT NULL default '0', + `comment` text, + UNIQUE KEY `id` (`id`), + UNIQUE KEY `song` (`song`), + KEY `type` (`type`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `flagged` +-- + + +/*!40000 ALTER TABLE `flagged` DISABLE KEYS */; +LOCK TABLES `flagged` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `flagged` ENABLE KEYS */; + +-- +-- Table structure for table `flagged_song` +-- + +DROP TABLE IF EXISTS `flagged_song`; +CREATE TABLE `flagged_song` ( + `id` int(11) unsigned NOT NULL auto_increment, + `song` int(10) unsigned NOT NULL default '0', + `file` varchar(255) NOT NULL default '', + `catalog` int(11) unsigned NOT NULL default '0', + `album` int(11) unsigned NOT NULL default '0', + `new_album` varchar(255) default NULL, + `comment` varchar(255) NOT NULL default '', + `year` mediumint(4) unsigned NOT NULL default '0', + `artist` int(11) unsigned NOT NULL default '0', + `new_artist` varchar(255) default NULL, + `title` varchar(255) NOT NULL default '', + `bitrate` mediumint(2) NOT NULL default '0', + `rate` mediumint(2) NOT NULL default '0', + `mode` varchar(25) default NULL, + `size` mediumint(4) unsigned NOT NULL default '0', + `time` mediumint(5) NOT NULL default '0', + `track` int(11) unsigned default NULL, + `genre` int(10) default NULL, + `played` enum('true','false') NOT NULL default 'false', + `enabled` enum('true','false') NOT NULL default 'true', + `update_time` int(11) unsigned default '0', + `addition_time` int(11) unsigned default '0', + PRIMARY KEY (`id`), + UNIQUE KEY `song` (`song`), + KEY `genre` (`genre`), + KEY `album` (`album`), + KEY `artist` (`artist`), + KEY `id` (`id`), + KEY `update_time` (`update_time`), + KEY `addition_time` (`addition_time`), + KEY `catalog` (`catalog`), + KEY `played` (`played`), + KEY `enabled` (`enabled`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `flagged_song` +-- + + +/*!40000 ALTER TABLE `flagged_song` DISABLE KEYS */; +LOCK TABLES `flagged_song` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `flagged_song` ENABLE KEYS */; + +-- +-- Table structure for table `flagged_types` +-- + +DROP TABLE IF EXISTS `flagged_types`; +CREATE TABLE `flagged_types` ( + `id` int(11) NOT NULL auto_increment, + `type` varchar(32) NOT NULL default '', + `value` varchar(128) NOT NULL default '', + `access` enum('user','admin') NOT NULL default 'user', + PRIMARY KEY (`id`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `flagged_types` +-- + + +/*!40000 ALTER TABLE `flagged_types` DISABLE KEYS */; +LOCK TABLES `flagged_types` WRITE; +INSERT INTO `flagged_types` VALUES (1,'badmp3','Corrupt or low-quality mp3','user'),(2,'badid3','Incomplete or incorrect song information','user'),(3,'newid3','Updated id3 information is available','admin'),(4,'del','Remove this file','admin'),(5,'sort','Put this file in a directory matching the conventions of its catalog','admin'),(6,'ren','Rename this file from id3 info','admin'),(7,'notify','Notify the user who flagged this song that it has been updated.','admin'),(8,'done','Take no action on this song.','admin'),(9,'setid3','Schedule file for id3 update','admin'),(10,'disabled','Disabled this song','admin'); +UNLOCK TABLES; +/*!40000 ALTER TABLE `flagged_types` ENABLE KEYS */; + +-- +-- Table structure for table `genre` +-- + +DROP TABLE IF EXISTS `genre`; +CREATE TABLE `genre` ( + `id` int(11) unsigned NOT NULL auto_increment, + `name` varchar(255) NOT NULL default '', + PRIMARY KEY (`id`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `genre` +-- + + +/*!40000 ALTER TABLE `genre` DISABLE KEYS */; +LOCK TABLES `genre` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `genre` ENABLE KEYS */; + +-- +-- Table structure for table `now_playing` +-- + +DROP TABLE IF EXISTS `now_playing`; +CREATE TABLE `now_playing` ( + `id` int(11) unsigned NOT NULL auto_increment, + `song_id` int(11) unsigned NOT NULL default '0', + `user_id` int(11) unsigned default NULL, + `start_time` int(11) unsigned NOT NULL default '0', + PRIMARY KEY (`id`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `now_playing` +-- + + +/*!40000 ALTER TABLE `now_playing` DISABLE KEYS */; +LOCK TABLES `now_playing` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `now_playing` ENABLE KEYS */; + +-- +-- Table structure for table `object_count` +-- + +DROP TABLE IF EXISTS `object_count`; +CREATE TABLE `object_count` ( + `id` int(11) unsigned NOT NULL auto_increment, + `object_type` enum('album','artist','song','playlist') NOT NULL default 'song', + `object_id` int(11) unsigned NOT NULL default '0', + `date` int(11) unsigned NOT NULL default '0', + `count` int(11) unsigned NOT NULL default '0', + `userid` varchar(128) default NULL, + PRIMARY KEY (`id`), + KEY `object_type` (`object_type`), + KEY `object_id` (`object_id`), + KEY `userid` (`userid`), + KEY `date` (`date`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `object_count` +-- + + +/*!40000 ALTER TABLE `object_count` DISABLE KEYS */; +LOCK TABLES `object_count` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `object_count` ENABLE KEYS */; + +-- +-- Table structure for table `playlist` +-- + +DROP TABLE IF EXISTS `playlist`; +CREATE TABLE `playlist` ( + `id` int(10) unsigned NOT NULL auto_increment, + `name` varchar(128) NOT NULL default '', + `owner` int(10) unsigned NOT NULL default '0', + `type` enum('private','public') NOT NULL default 'private', + `date` timestamp NOT NULL, + PRIMARY KEY (`id`), + KEY `name` (`name`), + KEY `id` (`id`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `playlist` +-- + + +/*!40000 ALTER TABLE `playlist` DISABLE KEYS */; +LOCK TABLES `playlist` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `playlist` ENABLE KEYS */; + +-- +-- Table structure for table `playlist_data` +-- + +DROP TABLE IF EXISTS `playlist_data`; +CREATE TABLE `playlist_data` ( + `playlist` int(11) unsigned NOT NULL default '0', + `song` int(11) unsigned NOT NULL default '0', + `track` int(11) unsigned NOT NULL default '0', + KEY `playlist` (`playlist`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `playlist_data` +-- + + +/*!40000 ALTER TABLE `playlist_data` DISABLE KEYS */; +LOCK TABLES `playlist_data` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `playlist_data` ENABLE KEYS */; + +-- +-- Table structure for table `playlist_permission` +-- + +DROP TABLE IF EXISTS `playlist_permission`; +CREATE TABLE `playlist_permission` ( + `id` int(11) unsigned NOT NULL auto_increment, + `userid` varchar(128) NOT NULL default '', + `playlist` int(11) unsigned NOT NULL default '0', + `level` smallint(3) NOT NULL default '0', + PRIMARY KEY (`id`), + KEY `userid` (`userid`), + KEY `playlist` (`playlist`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `playlist_permission` +-- + + +/*!40000 ALTER TABLE `playlist_permission` DISABLE KEYS */; +LOCK TABLES `playlist_permission` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `playlist_permission` ENABLE KEYS */; + +-- +-- Table structure for table `preferences` +-- + +DROP TABLE IF EXISTS `preferences`; +CREATE TABLE `preferences` ( + `id` int(11) unsigned NOT NULL auto_increment, + `name` varchar(128) NOT NULL default '', + `value` varchar(255) NOT NULL default '', + `description` varchar(255) NOT NULL default '', + `level` int(11) unsigned NOT NULL default '100', + `type` varchar(128) NOT NULL default '', + `locked` smallint(1) NOT NULL default '1', + PRIMARY KEY (`id`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `preferences` +-- + + +/*!40000 ALTER TABLE `preferences` DISABLE KEYS */; +LOCK TABLES `preferences` WRITE; +INSERT INTO `preferences` VALUES (1,'download','0','Allow Downloads',100,'user',0),(2,'upload','0','Allow Uploads',100,'user',0),(3,'quarantine','1','Quarantine All Uploads',100,'user',0),(4,'popular_threshold','10','Popular Threshold',25,'user',0),(5,'font','Verdana, Helvetica, sans-serif','Interface Font',25,'user',0),(6,'bg_color1','#ffffff','Background Color 1',25,'user',0),(7,'bg_color2','#000000','Background Color 2',25,'user',0),(8,'base_color1','#bbbbbb','Base Color 1',25,'user',0),(9,'base_color2','#dddddd','Base Color 2',25,'user',0),(10,'font_color1','#222222','Font Color 1',25,'user',0),(11,'font_color2','#000000','Font Color 2',25,'user',0),(12,'font_color3','#ffffff','Font Color 3',25,'user',0),(13,'row_color1','#cccccc','Row Color 1',25,'user',0),(14,'row_color2','#bbbbbb','Row Color 2',25,'user',0),(15,'row_color3','#dddddd','Row Color 3',25,'user',0),(16,'error_color','#990033','Error Color',25,'user',0),(17,'font_size','10','Font Size',25,'user',0),(18,'upload_dir','/tmp','Upload Directory',25,'user',0),(19,'sample_rate','32','Downsample Bitrate',25,'user',0),(20,'refresh_limit','0','Refresh Rate for Homepage',100,'system',0),(21,'local_length','900','Session Expire in Seconds',100,'system',0),(22,'site_title','For The Love of Music','Website Title',100,'system',0),(23,'lock_songs','0','Lock Songs',100,'system',1),(24,'force_http_play','1','Forces Http play regardless of port',100,'system',1),(25,'http_port','80','Non-Standard Http Port',100,'system',1),(26,'catalog_echo_count','100','Catalog Echo Interval',100,'system',0),(27,'album_cache_limit','25','Album Cache Limit',100,'system',0),(28,'artist_cache_limit','50','Artist Cache Limit',100,'system',0),(29,'play_type','stream','Type of Playback',25,'user',0),(30,'direct_link','1','Allow Direct Links',100,'user',0),(31,'lang','en_US','Language',100,'user',0),(32,'playlist_type','m3u','Playlist Type',100,'user',0); +UNLOCK TABLES; +/*!40000 ALTER TABLE `preferences` ENABLE KEYS */; + +-- +-- Table structure for table `session` +-- + +DROP TABLE IF EXISTS `session`; +CREATE TABLE `session` ( + `id` varchar(32) NOT NULL default '', + `username` varchar(16) NOT NULL default '', + `expire` int(11) unsigned NOT NULL default '0', + `value` text NOT NULL, + `type` enum('sso','mysql','ldap') NOT NULL default 'mysql', + PRIMARY KEY (`id`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `session` +-- + + +/*!40000 ALTER TABLE `session` DISABLE KEYS */; +LOCK TABLES `session` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `session` ENABLE KEYS */; + +-- +-- Table structure for table `song` +-- + +DROP TABLE IF EXISTS `song`; +CREATE TABLE `song` ( + `id` int(11) unsigned NOT NULL auto_increment, + `file` varchar(255) NOT NULL default '', + `catalog` int(11) unsigned NOT NULL default '0', + `album` int(11) unsigned NOT NULL default '0', + `comment` text NOT NULL, + `year` mediumint(4) unsigned NOT NULL default '0', + `artist` int(11) unsigned NOT NULL default '0', + `title` varchar(255) NOT NULL default '', + `bitrate` mediumint(2) NOT NULL default '0', + `rate` mediumint(2) NOT NULL default '0', + `mode` varchar(25) default NULL, + `size` int(11) unsigned NOT NULL default '0', + `time` mediumint(5) NOT NULL default '0', + `track` int(11) unsigned default NULL, + `genre` int(10) default NULL, + `played` enum('true','false') NOT NULL default 'false', + `status` enum('disabled','enabled') NOT NULL default 'enabled', + `update_time` int(11) unsigned default '0', + `addition_time` int(11) unsigned default '0', + PRIMARY KEY (`id`), + KEY `genre` (`genre`), + KEY `album` (`album`), + KEY `artist` (`artist`), + KEY `id` (`id`), + KEY `file` (`file`), + KEY `update_time` (`update_time`), + KEY `addition_time` (`addition_time`), + KEY `catalog` (`catalog`), + KEY `played` (`played`), + KEY `enabled` (`status`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `song` +-- + + +/*!40000 ALTER TABLE `song` DISABLE KEYS */; +LOCK TABLES `song` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `song` ENABLE KEYS */; + +-- +-- Table structure for table `update_info` +-- + +DROP TABLE IF EXISTS `update_info`; +CREATE TABLE `update_info` ( + `key` varchar(128) NOT NULL default '', + `value` varchar(255) NOT NULL default '', + KEY `key` (`key`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `update_info` +-- + + +/*!40000 ALTER TABLE `update_info` DISABLE KEYS */; +LOCK TABLES `update_info` WRITE; +INSERT INTO `update_info` VALUES ('db_version','330004'); +UNLOCK TABLES; +/*!40000 ALTER TABLE `update_info` ENABLE KEYS */; + +-- +-- Table structure for table `upload` +-- + +DROP TABLE IF EXISTS `upload`; +CREATE TABLE `upload` ( + `id` int(11) unsigned NOT NULL auto_increment, + `user` int(11) unsigned NOT NULL default '0', + `file` varchar(255) NOT NULL default '', + `comment` varchar(255) NOT NULL default '', + `action` enum('add','quarantine','delete') NOT NULL default 'quarantine', + `addition_time` int(11) unsigned default '0', + PRIMARY KEY (`id`), + KEY `action` (`action`), + KEY `user` (`user`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `upload` +-- + + +/*!40000 ALTER TABLE `upload` DISABLE KEYS */; +LOCK TABLES `upload` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `upload` ENABLE KEYS */; + +-- +-- Table structure for table `user` +-- + +DROP TABLE IF EXISTS `user`; +CREATE TABLE `user` ( + `id` int(11) unsigned NOT NULL auto_increment, + `username` varchar(128) NOT NULL default '', + `fullname` varchar(128) NOT NULL default '', + `email` varchar(128) default NULL, + `password` varchar(64) NOT NULL default '', + `access` varchar(64) NOT NULL default '', + `offset_limit` int(5) unsigned NOT NULL default '50', + `last_seen` int(11) unsigned NOT NULL default '0', + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `user` +-- + + +/*!40000 ALTER TABLE `user` DISABLE KEYS */; +LOCK TABLES `user` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `user` ENABLE KEYS */; + +-- +-- Table structure for table `user_catalog` +-- + +DROP TABLE IF EXISTS `user_catalog`; +CREATE TABLE `user_catalog` ( + `user` int(11) unsigned NOT NULL default '0', + `catalog` int(11) unsigned NOT NULL default '0', + `level` smallint(3) NOT NULL default '25' +) TYPE=MyISAM; + +-- +-- Dumping data for table `user_catalog` +-- + + +/*!40000 ALTER TABLE `user_catalog` DISABLE KEYS */; +LOCK TABLES `user_catalog` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `user_catalog` ENABLE KEYS */; + +-- +-- Table structure for table `user_preference` +-- + +DROP TABLE IF EXISTS `user_preference`; +CREATE TABLE `user_preference` ( + `user` int(11) unsigned NOT NULL default '0', + `preference` int(11) unsigned NOT NULL default '0', + `value` varchar(255) NOT NULL default '', + KEY `user` (`user`), + KEY `preference` (`preference`), + KEY `user_2` (`user`), + KEY `preference_2` (`preference`) +) TYPE=MyISAM; + +-- +-- Dumping data for table `user_preference` +-- + + +/*!40000 ALTER TABLE `user_preference` DISABLE KEYS */; +LOCK TABLES `user_preference` WRITE; +UNLOCK TABLES; +/*!40000 ALTER TABLE `user_preference` ENABLE KEYS */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; + diff --git a/stats.php b/stats.php new file mode 100644 index 00000000..4470ae0c --- /dev/null +++ b/stats.php @@ -0,0 +1,83 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/* + + Show us the stats for the server and this user + +*/ +require_once("modules/init.php"); + +//FIXME: Remove references +$uid = $user->id; + +show_template('header'); +show_menu_items('Stats'); +show_clear(); +?> + +<div class="header1"><?php echo $user->fullname; ; ?>'s Favorites:</div> + +<p> Below is a list of what you have been listening to the most. You can clear these statistics +by <a href="<?php echo conf('web_path'); ?>/user.php?action=show_edit_profile">editing your profile.</a></p> + +<table cellpadding="5" cellspacing="5" border="0" width="100%"> + <tr> + <td valign="top"> + <?php + if ( $items = $user->get_favorites('artist') ) { + $items = $user->format_favorites($items); + show_info_box('Your Favorite Artists', 'artist', $items); + } + else { + print("<p> Not enough data for favorite artists.</p>"); + } + ?> + </td> + + <td valign="top"> + <?php + if ( $items = $user->get_favorites('song') ) { + $items = $user->format_favorites($items); + show_info_box('Your Favorite Songs', 'your_song', $items); + } + else { + print("<p> Not enough data for favorite songs.</p>"); + } + ?> + </td> + + <td valign="top"> + <?php + if ( $items = $user->get_favorites('album') ) { + $items = $user->format_favorites($items); + show_info_box('Your Favorite Albums', 'album', $items); + } + else { + print("<p> Not enough data for favorite albums.</p>"); + } + ?> + </td> + </tr> +</table> +<br /> + +<?php show_menu_items('Stats'); ?> +</body> +</html> diff --git a/templates/add_catalog.inc b/templates/add_catalog.inc new file mode 100644 index 00000000..57ee6a9e --- /dev/null +++ b/templates/add_catalog.inc @@ -0,0 +1,109 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +$default_id3 = "/usr/bin/id3v2 -a "%a" -A "%A" -t "%t" -g %g -y %y -T %T -c "%c" %filename"; +$default_rename = "%a - %T - %t"; +$default_sort = "%a/%A"; + +?> + +<p class="header2"><?php echo _("Add a Catalog"); ?></p> + +<p><?php echo _("In the form below enter either a local path (i.e. /data/music) or the URL to a remote Ampache installation (i.e http://theotherampache.com)"); ?></p> + +<div class="text-box"> +<form name="update_catalog" method="post" action="<?php echo conf('web_path'); ?>/admin/catalog.php" enctype="multipart/form-data"> +<table class="tabledata" cellpadding="0" border="0" cellspacing="0"> +<tr> + <td><?php echo _("Catalog Name"); ?>: </td> + <td><input size="60" type="text" name="name" value="<?php echo $_REQUEST['name']; ?>" size="30" /></td> + <td style="vertical-align:top; font-family: monospace;" rowspan="6"> + <strong><?php echo _("Auto-inserted Fields"); ?>:</strong><br /> + %A = <?php echo _("album name"); ?><br /> + %a = <?php echo _("artist name"); ?><br /> + %c = <?php echo _("id3 comment"); ?><br /> + %g = <?php echo _("genre"); ?><br /> + %T = <?php echo _("track number (padded with leading 0)"); ?><br /> + %t = <?php echo _("song title"); ?><br /> + %y = <?php echo _("year"); ?><br /> + %o = <?php echo _("other"); ?><br /> + + </td> + +</tr> +<tr> + <td><?php echo _("Path"); ?>: </td> + <td><input size="60" type="text" name="path" value="<?php echo $_REQUEST['path']; ?>" size="30" /></td> +</tr> +<tr> + <td><?php echo _("Catalog Type"); ?>: </td> + <td> + <select name="type"> + <option value="local"><?php echo _("Local"); ?></option> + <option value="remote"><?php echo _("Remote"); ?></option> + </select> + </td> +</tr> +<tr> + <td><?php echo _("ID3 Set Command"); ?>: </td> + <td><input size="60" type="text" name="id3set_command" value="<?php echo $default_id3; ?>" size="30" /></td> +</tr> +<tr> + <td><?php echo _("Filename Pattern"); ?>: </td> + <td><input size="60" type="text" name="rename_pattern" value="<?php echo $default_rename; ?>" size="30" /></td> +</tr> +<tr> + <td><?php echo _("Folder Pattern"); ?>:<br /><?php echo _("(no leading or ending '/')"); ?></td> + <td valign="top"><input size="60" type="text" name="sort_pattern" value="<?php echo $default_sort; ?>" size="30" /></td> +</tr> +<tr> + <td valign="top"><?php echo _("Gather Album Art"); ?>:</td> + <td><input type="checkbox" onclick="flipField('artextra1');flipField('artextra2');flipField('artextra3');" name="gather_art" value="1" /><br /> + <table border="0" width="100%" cellpadding="0" cellspacing="0"> + <tr class="even"> + <td><?php echo _("ID3V2 Tags"); ?>:</td> + <td><input id="artextra1" disabled="disabled" type="checkbox" name="art_id3v2" value="1" /></td></tr> + <tr class="even"> + <td><?php echo _("Amazon"); ?>:</td> + <td><input id="artextra2" disabled="disabled" type="checkbox" name="art_amazon" value="1" /></td></tr> + <tr class="even"> + <td><?php echo _("File Folder"); ?>:</td> + <td><input id="artextra3" disabled="disabled" type="checkbox" name="art_folder" value="1" /></td></tr> + </table> + <br /> + </td> +</tr> +<tr> + <td valign="top"><?php echo _("Build Playlists from m3u Files"); ?>:</td> + <td><input type="checkbox" name="parse_m3u" value="1" /></td> +</tr> +<tr> + <td> </td> + <td> + <input type="hidden" name="action" value="add_catalog" /> + <input class="button" type="submit" value="<?php echo _("Add Catalog"); ?>" /> + <input class="button" type="submit" name="action" value="Cancel" /> + </td> +</tr> +</table> +</form> +</div> + diff --git a/templates/admin_menu.inc b/templates/admin_menu.inc new file mode 100644 index 00000000..0d875e70 --- /dev/null +++ b/templates/admin_menu.inc @@ -0,0 +1,54 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/* + + @header + A template file + +*/ + +$web_path = conf('web_path'); + +$items = array( + _("Users") => "$web_path/admin/users.php", + _("Mail Users") => "$web_path/admin/mail.php", + _("Catalog") => "$web_path/admin/catalog.php", + _("Admin Preferences") => "$web_path/admin/preferences.php", + _("Access Lists") => "$web_path/admin/access.php" + ); + +?> +<ul id="adminmenu"> + + <?php + foreach ( array_keys($items) as $item ) { + if ( $admin_highlight == $item ) { + echo "<li class=\"active\"><a class=\"active\" href=\"$items[$item]\">$item</a></li>\n"; + } + else { + echo "<li><a href=\"$items[$item]\">$item</a></li>\n"; + } + } + + ?> +</ul> diff --git a/templates/catalog.inc b/templates/catalog.inc new file mode 100644 index 00000000..79030725 --- /dev/null +++ b/templates/catalog.inc @@ -0,0 +1,110 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/* + + @header + A template file + +*/ + +?> + +<br /> +<?php if (!function_exists('iconv')) { ?> + <div class="fatalerror"><?php echo _("Error: ICONV not found, ID3V2 Tags will not import correctly. See <a href=\"http://php.oregonstate.edu/iconv\">Iconv</a> for information on getting ICONV"); ?></div> +<?php } ?> +<table cellpadding="5" border="0" cellspacing="0"> +<tr> + <td valign="top"><?php show_local_catalog_info(); ?></td> + <td valign="top"> + <form name="catalog" method="post" action="<?php echo conf('web_path'); ?>/admin/catalog.php" enctype="multipart/form-data"> + <table class="border" cellspacing="1" cellpadding="3"> + <tr class="table-header" align="center"> + <td colspan="4"><?php echo _("Update Catalogs"); ?></td> + </tr> + <?php + $catalogs = $catalog->get_catalogs(); + + if ( $catalogs ) { + foreach ($catalogs as $catalog) { + print("<tr class=\"even\"><td>". + "<input type=\"checkbox\" name=\"catalogs[]\" value=\"$catalog->id\"></input></td>". + "<td>". + "<a href=\"". conf('web_path') ."/admin/catalog.php?action=show_customize_catalog&catalog_id=$catalog->id\">". + "$catalog->path". + "</a>". + "</td>". + "<td>". + date("H:i - m/d/y",$catalog->last_update). + "</td>". + "<td>". + "<a href=\"" . conf('web_path') . "/admin/catalog.php?action=show_delete_catalog&catalog_id=$catalog->id\">" . _("Delete") . "</a>". + "</td></tr>\n"); + } // end foreach + ?> + <tr> + <td class="even" colspan="4"> + <input class="button" type="submit" name="action" value="<?php echo _("Add to Catalog(s)"); ; ?>" /> + <input class="button" type="submit" name="action" value="<?php echo _("Add to all Catalogs"); ; ?>" /><br /> + <?php echo _("Fast Add"); ; ?>:<input type="checkbox" name="update_type" value="fast_add" /> + </td> + </tr> + <tr> + <td class="even" colspan="4"> + <input class="button" type="submit" name="action" value="<?php echo _("Update Catalog(s)"); ; ?>" /> + <input class="button" type="submit" name="action" value="<?php echo _("Update All Catalogs"); ; ?>" /><br /> + <?php echo _("Fast Update"); ; ?>:<input type="checkbox" name="update_type" value="fast_update" /><br /> + </td> + </tr> + <tr> + <td class="even" colspan="4"> + <input class="button" type="submit" name="action" value="<?php echo _("Clean Catalog(s)"); ; ?>" /> + <input class="button" type="submit" name="action" value="<?php echo _("Clean All Catalogs"); ; ?>" /><br /> + </td> + </tr> + <?php + } // end if catalogs + else { + print("<tr class=\"even\"><td colspan=\"4\" >". + _("You don't have any catalogs.") . + "</td></tr>"); + } + ?> + </table> + </form> + </td> + <td valign="top"> + <?php + $tools = array(_("Add a catalog") => conf('web_path') . "/admin/catalog.php?action=show_add_catalog", + _("Access Lists") => conf('web_path') . "/admin/access.php", + _("Show Duplicate Songs") => conf('web_path') . "/admin/duplicates.php", + _("Show Disabled Songs") => conf('web_path') . "/admin/catalog.php?action=show_disabled", + _("Clear Catalog Stats") => conf('web_path') . "/admin/catalog.php?action=clear_stats", + _("Clear Now Playing") => conf('web_path') . "/admin/catalog.php?action=clear_now_playing", + _("Dump Album Art") => conf('web_path') . "/admin/catalog.php?action=dump_album_art", + _("Gather Album Art") => conf('web_path') . "/admin/catalog.php?action=gather_album_art", + _("View flagged songs") => conf('web_path') . "/admin/flags.php"); + show_tool_box(_("Catalog Tools"), $tools); + ?> + </td> + </tr> +</table> diff --git a/templates/customize_catalog.inc b/templates/customize_catalog.inc new file mode 100644 index 00000000..243e66ae --- /dev/null +++ b/templates/customize_catalog.inc @@ -0,0 +1,74 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +?> +<br /> +<div class="header2"><?php echo _("Settings for catalog in"); ?> <?php echo $catalog->path; ?></div><br /> +<div class="text-box"> +<form method="get" action="<?php echo conf('web_path'); ?>/admin/catalog.php" enctype="multipart/form-data"> +<table class="tabledata" cellspacing="0" cellpadding="0" border="0"> +<tr> + <td><?php echo _("Name"); ?>:</td> + <td><input size="60" type="text" name="name" value="<?= htmlspecialchars($catalog->name);?>"></input></td> + <td style="vertical-align:top; font-family: monospace;" rowspan="5"> + <strong><?php echo _("Auto-inserted Fields"); ?>:</strong><br /> + %A = <?php echo _("album name"); ?><br /> + %a = <?php echo _("artist name"); ?><br /> + %C = <?php echo _("catalog path"); ?><br /> + %c = <?php echo _("id3 comment"); ?><br /> + %g = <?php echo _("genre"); ?><br /> + %T = <?php echo _("track number (padded with leading 0)"); ?><br /> + %t = <?php echo _("song title"); ?><br /> + %y = <?php echo _("year"); ?><br /> + %o = <?php echo _("other"); ?><br /> + </td> +</tr> +<tr> + <td><?php echo _("ID3 set command"); ?>:</td> + <td> + <input size="60" type="text" name="id3_set_command" value="<?php echo htmlspecialchars($catalog->id3_set_command); ?>" /> + </td> +</tr> +<tr> + <td><?php echo _("Filename pattern"); ?>:</td> + <td> + <input size="60" type="text" name="rename_pattern" value="<?php echo htmlspecialchars($catalog->rename_pattern); ?>" /> + </td> +</tr> +<tr> + <td> + <?php echo _("Folder Pattern"); ?>:<br /><?php echo _("(no leading or ending '/')"); ?> + </td> + <td> + <input size="60" type="text" name="sort_pattern" value="<?php echo htmlspecialchars($catalog->sort_pattern);?>" /> + </td> +</tr> +<tr> + <td> </td> + <td> + <input type="hidden" name="catalog_id" value="<?php echo $catalog->id; ?>"> + <input type="hidden" name="action" value="update_catalog_settings" /> + <input type="submit" value="<?php echo _("Save Catalog Settings"); ?>" /> + </td> +</tr> +</table> +</form> +</div> diff --git a/templates/flag.inc b/templates/flag.inc new file mode 100644 index 00000000..2e9e50cf --- /dev/null +++ b/templates/flag.inc @@ -0,0 +1,149 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header + + A template file + +*/ + +// let's put a couple of things in this file + +if ( $type == 'show_flagged_form' ) { + $song = new Song($song_id); + $song->format_song(); + if(!preg_match('/\.mp3$/',$song->file)) + { + echo "<p>Ampache can only edit MP3 file tags currently.<br/>"; + echo "<a href=\"".$_SERVER['HTTP_REFERER']."\">Back</a>"; + return; + } +?> + +<p style="font-size: 10pt; font-weight: bold;"><?php echo _("Flag song"); ?></p> + +<p><?php echo _("Flag the following song as having one of the problems listed below. Site admins will then take the appropriate action for the flagged files."); ?></p> + +<?php if ( $flag_text ) { ?> +<p style="color: red;"><?php echo $flag_text ; ?></p> +<?php } ?> + +<form name="flag_song" method="post" action="<?php echo $_SERVER['PHP_SELF'];; ?>"> +<table class="tabledata" cellpadding="3" cellspacing="1"> + <tr class="even"> + <td>File:</td> + <td><?php echo $song->file ; ?></td> + </tr> + <tr class="even"> + <td><?php echo _("Song"); ?>:</td> + <td><b><?php echo $song->f_title ; ?></b> by <?php echo $song->f_artist_full; ; ?></td> + </tr> + <tr class="even"> + <td><?php echo _("Reason to flag"); ?>:</td> + <td><?php show_flagged_popup($reason); ?></td> + </tr> + <tr class="even"> + <td><?php echo _("Comment"); ?>:</td> + <td><input name="comment" type="text" size="50" value="<?php echo $comment ; ?>"></input> + </td> + </tr> + <tr class="odd"> + <td> </td> + <td> + <input type="submit" value="<?php echo _("Flag Song"); ?>" /> + <input type="hidden" name="action" value="flag_song" /> + </td> + </tr> +</table> +<input type="hidden" name="song" value="<?php echo $song->id ; ?>"> +</form> + +<?php + +} +elseif ( $type == 'show_flagged_songs' ) { + $flags = get_flagged(); +?> + +<p style="font-size: 10pt; font-weight: bold;">View Flagged Songs</p> + +<p>This is the list of songs that have been flagged by your Ampache users. Use +this list to determine what songs you need to re-rip or tags you need to update.</p> + +<?php if ( $flags ) { ?> +<form name="flag_update" action="<?php echo $_SERVER['PHP_SELF'];; ?>" method="post"> + +<table class="tabledata" cellspacing="0" cellpadding="0" border="1"> + <tr class="table-header"> + <td> </td> + <td>Song</td> + <td>Flag</td> + <td>New Flag:</td> + <td>Flagged by</td> + <td>ID3 Update:</td> + </tr> +<?php + foreach ($flags as $flag) { + $song = new Song($flag->song); + $song->format_song(); + $alt_title = $song->title; + + $artist = $song->f_artist; + $alt_artist = $song->f_full_artist; + + echo "<tr class=\"even\">". + "<td><input type=\"checkbox\" id=\"flag_".$flag->id."\" name=\"flag[]\" value=\"".$flag->id."\"></input></td>". + "<td><a href=\"".conf('web_path')."/song.php?song=$flag->song\" title=\"$alt_title\">$song->f_title</a> by ". + "<a href=\"".conf('web_path')."/artist.php?action=show&artist=$song->artist_id\" title=\"$alt_artist\">$artist</a></td>". + "<td>$flag->type</td>"; + echo "<td>"; + $onchange = "onchange=\"document.getElementById('flag_".$flag->id."').checked='checked';\""; + show_flagged_popup($flag->type,'type',$flag->id."_newflag", $onchange); + echo "</td>"; + echo "<td>".$flag->username."<br />".date('m/d/y',$flag->date)."</td>"; +/* echo "<td><a href=\"catalog.php?action=fixed&flag=$flag->id\">Fixed</a></td></tr>\n";*/ + if($flag->type === 'newid3') + { + echo "<td>"; + echo "<input type=\"radio\" name=\"accept_".$flag->id."\" value=\"accept\" />Accept"; + echo "<input type=\"radio\" name=\"accept_".$flag->id."\" value=\"reject\" />Reject"; + echo "</td>"; + } + else echo "<td><a href=\"".conf('web_path')."/admin/song.php?action=edit&song=".$flag->song."\">edit/view</a></td>"; + echo "</tr>\n"; + + } +?> +<tr class="even"><td colspan="6"><input type="submit" name="action" value="Update Flags"></input></td></tr> +</table> +</form> +<?php } else { ?> + +<p> You don't have any flagged songs. </p> + +<?php } ?> + +<?php + +} +?> diff --git a/templates/header.inc b/templates/header.inc new file mode 100644 index 00000000..67041227 --- /dev/null +++ b/templates/header.inc @@ -0,0 +1,130 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +$htmllang = str_replace("_","-",conf('lang')); + +?> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd"> +<html lang="<?php echo $htmllang; ?>"> +<head> +<link rel="shortcut icon" href="<?php echo conf('web_path'); ?>/favicon.ico"> +<meta http-equiv="Content-Type" content="text/html; charset=<?php echo conf('site_charset'); ?>" /> +<?php show_template('style'); ?> +<title><?php echo conf('site_title'); ?></title> +</head> +<body> +<script type="text/javascript" language="javascript"> +<!-- Begin +function disableField(element) { + var element_id = document.getElementById(element); + element_id.disabled=true; + element_id.value=''; + element_id.checked=false; +} +function enableField(element) { + var element_id = document.getElementById(element); + element_id.disabled=false; + +} +function flipField(element) { + var element_id = document.getElementById(element); + if (element_id.disabled == false) { + element_id.disabled=true; + } + else { + element_id.disabled=false; + } +} + var checkflag_song = "false"; + + function check_songs() { + if (checkflag_song == "false") { + if (document.forms.songs.elements["song[]"].length == undefined) { + document.forms.songs.elements["song[]"].checked = true; + } + else { + for (i = 0; i < document.forms.songs.elements["song[]"].length; i++) { + document.forms.songs.elements["song[]"][i].checked = true; + } + } + checkflag_song = "true"; + return "Unselect All"; + } + else { + if (document.forms.songs.elements["song[]"].length == undefined) { + document.forms.songs.elements["song[]"].checked = false; + } + else { + for (i = 0; i < document.forms.songs.elements["song[]"].length; i++) { + document.forms.songs.elements["song[]"][i].checked = false; + } + } + checkflag_song = "false"; + return "Select All"; + } + } + + var checkflag_results = "false"; + + function check_results() { + if (checkflag_results == "false") { + if (document.results.elements["results[]"].length == undefined) { + document.results.elements["results[]"].checked = true; + } + else { + for (i = 0; i < document.results.elements["results[]"].length; i++) { + document.results.elements["results[]"][i].checked = true; + } + } + checkflag_results = "true"; + return "Unselect All"; + } + else { + if (document.results.elements["results[]"].length == undefined) { + document.results.elements["results[]"].checked = false; + } + else { + for (i = 0; i < document.results.elements["results[]"].length; i++) { + document.results.elements["results[]"][i].checked = false; + } + } + checkflag_results = "false"; + return "Select All"; + } + } +// End --> +</script> + +<div id="pageheader"> +<a href="http://www.ampache.org"> + <img src="<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache.gif" border="0" title="Ampache: For the love of music" alt="Ampache: For the love of music" /> +</a> +</div> +<?php + if( isset( $_REQUEST['amp_error'] ) ) { + $msg = scrub_in( $_REQUEST['amp_error'] ); + echo( "<font class=\"error\">Error:\t$msg</font><br><br>" ); + if (conf('debug')) { log_event($_SESSION['userdata']['username'],' general_error ',"Error: $msg"); } + } +show_clear(); +?> diff --git a/templates/javascript_refresh.inc b/templates/javascript_refresh.inc new file mode 100644 index 00000000..2b04e46e --- /dev/null +++ b/templates/javascript_refresh.inc @@ -0,0 +1,41 @@ +<script language="JavaScript"> +// Set refresh interval (in seconds) +var refreshinterval=<?= conf('refresh_limit'); ?> + +// Display the countdown inside the status bar? +// Set "1" for yes or "0" for no +var displaycountdown=0 + +// main-code +var starttime +var nowtime +var reloadseconds=0 +var secondssinceloaded=0 + +function starttime() { + starttime=new Date() + starttime=starttime.getTime() + countdown() +} + +function countdown() { + nowtime= new Date() + nowtime=nowtime.getTime() + secondssinceloaded=(nowtime-starttime)/1000 + +reloadseconds=Math.round(refreshinterval-secondssinceloaded) + if (refreshinterval>=secondssinceloaded) { + var timer=setTimeout("countdown()",1000) + if (displaycountdown=="1") { + window.status="Page refreshing in "+reloadseconds+ +" seconds" + } + } else { + clearTimeout(timer) + window.location.reload(true) + } +} + +// start with page-load +window.onload=starttime +</script> diff --git a/templates/list_duplicates.inc b/templates/list_duplicates.inc new file mode 100644 index 00000000..ec1a8632 --- /dev/null +++ b/templates/list_duplicates.inc @@ -0,0 +1,87 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +/*! + @header + A template file for listing duplicate songs + +*/ +?> + +<?php show_duplicate_searchbox($search_type) ?> +<?php if ( $flags ) { ?> +</form> +<form method="post" enctype="multipart/form-data" action="<?php echo conf('web_path') . "/admin/song.php?action=disable"; ?>"> +<p style="font-size: 10pt; font-weight: bold;">Duplicate Songs</p> +<table class="tabledata" cellspacing="0" cellpadding="0" border="1"> + <tr class="table-header"> + <td>Disable</td> + <td>Song</td> + <td>Artist</td> + <td>Album</td> + <td>Length</td> + <td>Bitrate</td> + <td>Size</td> + <td>Filename</td> + </tr> +<?php + $class="odd"; + foreach ($flags as $flag) { + $song = new Song($flag['song']); + $song->format_song(); + $class = (++$i%2)?'odd':'even'; + $alt_title = $song->title; + $formated_title = $song->f_title; + + $artist = $song->f_artist; + $alt_artist = $song->f_full_artist; + + $dinfolist = get_duplicate_info($song,$search_type); + foreach ($dinfolist as $dinfo) + { + echo "<tr class=\"".$class."\">". + "<td><input type=\"checkbox\" name=\"song_ids[]\" value=\"" . $dinfo['songid'] . "\">". + "<td><a href=\"".conf('web_path')."/song.php?action=m3u&song=$song->id\">$formated_title</td>". + "<td><a href=\"".conf('web_path')."/artists.php?action=show&artist=".$dinfo['artistid']."\" title=\"".$dinfo['artist']."\">".$dinfo['artist']."</a> </td>". + "<td><a href=\"".conf('web_path')."/albums.php?action=show&album=".$dinfo['albumid']."\" title=\"".$dinfo['album']."\">".$dinfo['album']."</a> </td>". + "<td>".floor($dinfo['time']/60).":".sprintf("%02d", ($dinfo['time']%60) )."</td>". + "<td>".intval($dinfo['bitrate']/1000)."</td>". + "<td>".sprintf("%.2f", ($dinfo['size']/1000000))."Mb</td>". + "<td>".$dinfo['file']."</td>"; + echo "</tr>\n"; + + } + + + } +?> +<tr> +<td colspan="8" class="<?php echo $class; ?>"><input height="15px" type="submit" value="Disable Songs" /></td> +</tr> +</table> +<?php } else { ?> + +<p> You don't have any duplicate songs. </p> + +<?php } ?> + diff --git a/templates/list_flagged.inc b/templates/list_flagged.inc new file mode 100644 index 00000000..607206b5 --- /dev/null +++ b/templates/list_flagged.inc @@ -0,0 +1,92 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header + A template file + +*/ +?> + +<p style="font-size: 10pt; font-weight: bold;">View Flagged Songs</p> + +<p>This is the list of songs that have been flagged by you or other Ampache users. Use +this list to determine what songs you need to re-rip or tags you need to update.</p> + +<?php if ( $flags ) { ?> +<form name="songs" action="<?php echo $_SERVER['PHP_SELF'];; ?>" method="post"> + +<table class="tabledata" cellspacing="0" cellpadding="0" border="1"> + <tr class="table-header"> + <td><a href="#" onclick="check_songs(); return false;">Select</a></td> + <td><?php echo _("Song"); ?></td> + <td><?php echo _("Flag"); ?></td> + <td><?php echo _("New Flag"); ?>:</td> + <td><?php echo _("Flagged by"); ?></td> + <td><?php echo _("ID3 Update"); ?>:</td> + <td><?php echo _("Comment"); ?>:</td> + </tr> +<?php + foreach ($flags as $flag) { + $song = new Song($flag['song']); + $song->format_song(); + $alt_title = $song->title; + + $artist = $song->f_artist; + $alt_artist = $song->artist; + + echo "<tr class=\"even\">" . + "<td><input type=\"checkbox\" name=\"song[]\" id=\"flag_".$flag['song']."\" value=\"".$flag['song']."\"></input></td>" . + "<td><a href=\"".conf('web_path')."/song.php?song=".$flag['song']."\" title=\"$alt_title\">$song->f_title</a> by " . + "<a href=\"".conf('web_path')."/artist.php?action=show&artist=$song->artist_id\" title=\"$alt_artist\">$artist</a></td>" . + "<td>".$flag['type']."</td>"; + echo "<td>"; + $onchange = "onchange=\"document.getElementById('flag_".$flag['song']."').checked='checked';\""; + show_flagged_popup($flag['type'],'type',$flag['song']."_newflag", $onchange); + echo "</td>"; + echo "<td>".$flag['username']."<br />".date('m/d/y',$flag['date'])."</td>"; + if($flag['type'] === 'newid3') { + echo "<td>"; + echo "<input type=\"radio\" name=\"".$flag['song']."_accept\" value=\"accept\" $onchange />" . _("Accept") . "<br />"; + echo "<input type=\"radio\" name=\"".$flag['song']."_accept\" value=\"reject\" $onchange />" . _("Reject"); + echo "</td>"; + } + else { + echo "<td><a href=\"".conf('web_path')."/admin/song.php?action=edit&song=".$flag['song']."\">edit/view</a></td>"; + } + echo "<td>" . $flag['comment'] . "</td>"; + echo "</tr>\n"; + } +?> +<tr class="even"><td colspan="7"> + <input type="submit" name="action" value="Update Flags"></input> + <input type="submit" name="action" value="Edit Selected"></input> + <input type="submit" name="action" value="Clear Edit List"></input> + </td></tr> +</table> +</form> +<?php } else { ?> + +<p><?php echo ("You don't have any flagged songs"); ?>. </p> + +<?php } ?> + diff --git a/templates/list_header.inc b/templates/list_header.inc new file mode 100644 index 00000000..658d0972 --- /dev/null +++ b/templates/list_header.inc @@ -0,0 +1,99 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header + The default pager widget for moving through a list of many items. + + This relies heavily on the View object to get pieces about how + to layout this page. + +*/ + +if (!$total_items) { $total_items = $_SESSION['view_total_items']; } +// do some math here +if ( $view->offset >= $view->offset_limit ) { + $offset2 = $view->offset - 25; +} +else { + $offset2 = $view->offset; + if (!$view->offset) { $offset2 = "0"; } +} + +// Get the prev page offset +$offset1 = $view->offset - $view->offset_limit; +if ($offset1 < 1) { $offset1 = 0; } + + +// since we have an array of objects, let's build a purdy thingie +$pages = ceil($total_items/$view->offset_limit); + +$offset4 = ($pages - 1); +$offset4 = ($offset4 * $view->offset_limit); + +//We do this one last to make sure we don't go beyond offset4 +$offset3 = $view->offset + $view->offset_limit; +if ($offset3 >= $offset4) { $offset3 = $offset4; } + + +//setup the next action +preg_match("/.*\/(.+\.php)$/",$_SERVER['SCRIPT_NAME'],$matches); + +$action = "action=" . scrub_in($_REQUEST['action']); +$script = conf('web_path') . "/" . $admin_menu . $matches[1]; + +// are there enough items to even need this view? +if ( $pages > 1 && $_SESSION['view_script']) { +?> + +<table border="0" cellpadding="2" cellspacing="0" width="100%"> + <tr> + <td align="center" valign="top"> + <a href="<?php echo $script; ?>?<?php echo $action; ?>&offset=<?php echo $offset1; ?>&keep_view=true">[ <?php echo _("Prev"); ?> ]</a> + </td> + <td align="center"> + <?php + $counter = 1; + $offset_pages = 0; + + while ( $counter <= $pages ) { + if ( $view->offset == $offset_pages ) { + ?> + <a href="<?php echo $script; ?>?<?php echo $action ; ?>&sort_type=<?php echo $view->sort_type ; ?>&offset=<?php echo $offset_pages ; ?>&keep_view=true"><b><?php echo $counter; ?></b></a> + <?php + } else { + ?> + <a href="<?php echo $script; ?>?<?php echo $action; ?>&sort_type=<?php echo $view->sort_type; ?>&offset=<?php echo $offset_pages; ?>&keep_view=true"><?php echo $counter; ?></a> + <?php + } + $offset_pages += $view->offset_limit; + $counter++; + } + ?> + </td> + <td align="center" valign="top"> + <a href="<?php echo $script; ?>?<?php echo $action; ?>&offset=<?php echo $offset3; ?>&keep_view=true">[ <?php echo _("Next"); ?> ]</a> + </td> + </tr> +</table> + +<?php } // if ?> diff --git a/templates/menu.inc b/templates/menu.inc new file mode 100644 index 00000000..c98658dd --- /dev/null +++ b/templates/menu.inc @@ -0,0 +1,85 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header + A template file + +*/ + +$items = array( + _("Home") => htmlspecialchars(conf('web_path') . "/index.php"), + _("Albums") => htmlspecialchars(conf('web_path') . "/albums.php"), + _("Artists") => htmlspecialchars(conf('web_path') . "/artists.php"), + _("Playlists") => htmlspecialchars(conf('web_path') . "/playlist.php"), + _("Search") => htmlspecialchars(conf('web_path') . "/search.php"), + _("Preferences") => htmlspecialchars(conf('web_path') . "/preferences.php") + ); +if ($GLOBALS['user']->prefs['upload']) { + $items = array_merge($items, array(_("Upload") => conf('web_path') . "/upload.php")); +} + + +?> + +<ul id="mainmenu"> +<? + foreach ( array_keys($items) as $item ) { + if ( $high == $item ) { + print("\t\t<li class=\"active\"><a class=\"active\" href=\"$items[$item]\">$item</a></li>\n"); + } + else { + print("\t\t<li><a href=\"$items[$item]\">$item</a></li>\n"); + } + } // end foreach items + + + if (!conf('use_auth') || $GLOBALS['user']->has_access(100) ) { + if ( $high == 'Admin' ) { + print("\t\t<li class=\"active\"><a class=\"active\" href=\"" . conf('web_path') . "/admin/\">" . _("Admin") . "</a></li>\n"); + } + else { + print("\t\t<li><a href=\"" . conf('web_path') . "/admin/\">" . _("Admin") . "</a></li>\n"); + } + } // if we aren't using auth or 100 + + // now do the user specific stuff + if (conf('use_auth')) { + if ( $high == 'Stats' || $high == 'Profile' ) { + print("\t\t<li class=\"active\">".$GLOBALS['user']->username.": <a class=\"active\" href=\"".conf('web_path')."/user.php?action=show_edit_profile\">" . _("Account") . "</a> |". + " <a class=\"active\" href=\"".conf('web_path')."/stats.php\">" . _("Stats") . "</a> |". + " <a class=\"active\" href=\"".conf('web_path')."/logout.php\">" . _("Logout") . "</a> </li>\n"); + } // if stats or profile + else { + echo "\t\t<li>".$GLOBALS['user']->username.": " . + "<a href=\"".conf('web_path')."/user.php?action=show_edit_profile\">" . _("Account") . "</a> |". + " <a href=\"".conf('web_path')."/stats.php\">" . _("Stats") . "</a> |". + " <a href=\"".conf('web_path')."/logout.php\">" . _("Logout") . "</a> </li>\n"; + } // else + } // if use_auth + elseif ( $GLOBALS['user']->id != '0') { + print("\t\t<li bgcolor=\"" . conf('primary_color') . "\" align=\"center\">". + "<a href=\"".conf('web_path')."/logout.php\">" . _("Logout") . "</a> </li>\n"); + } // else no user +?> + + </ul> diff --git a/templates/show_access_list.inc b/templates/show_access_list.inc new file mode 100644 index 00000000..b5c7207b --- /dev/null +++ b/templates/show_access_list.inc @@ -0,0 +1,72 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + $CVSHeader: ampache/modules/class/song.php,v 1.7 2004/01/12 08:27:26 vollmerk Exp $ + $Source: /data/cvsroot/ampache/modules/class/song.php,v $ + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header show access list + @discussion default display for access admin page + +*/ +$row_classes = array('even','odd'); +?> + +<p style="font-size: 10pt; font-weight: bold;"><?php print _("Host Access to Your Catalog"); ?></p> + +<p>Since your catalog can be accessed remotely you may want to limit the access from +remote sources so you are not in violation of copyright laws. By default your +server will allow anyone with an account to stream music. It will not allow any +other Ampache servers to connect to it to share catalog information. Use tool below +to add any server's IP address that you want to access your Ampache catalog or be able to +stream from this server.</p> + +<p><a href="<?php print conf('web_path'); ?>/admin/access.php?action=show_add_host"><?php print _("Add Entry"); ?></a></p> + +<table cellspacing="1" cellpadding="3" class="border"> + <tr class="table-header" align="center"> + <td><?php print _("Name"); ?></td> + <td><?php print _("Start Address"); ?></td> + <td><?php print _("End Address"); ?></td> + <td><?php print _("Level"); ?></td> + <td><?php print _("Action"); ?></td> + </tr> +<?php +if (count($list)) { +/* Start foreach List Item */ +foreach ($list as $access) { +?> +<tr class="<?php print $row_classes[0]; ?>"> + <td><?php print $access->name; ?></td> + <td><?php print int2ip($access->start); ?></td> + <td><?php print int2ip($access->end); ?></td> + <td><?php print $access->get_level_name(); ?></td> + <td> + Edit | + <a href="<?php print conf('web_path'); ?>/admin/access.php?action=show_confirm_delete&access_id=<?php print $access->id; ?>"><?php print _("Revoke"); ?></a> + </td> +</tr> +<?php $row_classes = array_reverse($row_classes); ?> +<?php } // end foreach ?> +<?php } // end if count ?> +</table> + diff --git a/templates/show_add_access.inc b/templates/show_add_access.inc new file mode 100644 index 00000000..89ea79e5 --- /dev/null +++ b/templates/show_add_access.inc @@ -0,0 +1,76 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Add Access List Entry + A template file + +*/ + +?> + +<p style="font-size: 10pt; font-weight: bold;"><?php print _("Add Access for a Host"); ?></p> + +<p><?php print _("Use the form below to add a host that you want to have access to your Ampache catalog."); ?></p> + +<p> +<form name="update_catalog" method="post" enctype="multipart/form-data" action="<?= conf('web_path'); ?>/admin/access.php"> + +<table cellpadding="5" cellspacing="0" border="0"> + <tr> + <td><?php print _("Name"); ?>: </td> + <td> + <input type="text" name="name" value="<?= $_REQUEST['name']; ?>" size="30"> + </td> + </tr> + <tr> + <td><?php print _("Start IP Address"); ?>:</td> + <td> + <input type="text" name="start" value="<?= $_REQUEST['start']; ?>" size="20" maxlength="15"> + </td> + </tr> + <tr> + <td><?php print _("End IP Address"); ?>:</td> + <td> + <input type="text" name="end" value="<?= $_REQUEST['end']; ?>" size="20" maxlength="15"> + </td> + </tr> + <tr> + <td><?php print _("Level"); ?>:</td> + <td> + <select name="level"> + <option value="5" SELECTED>Demo</option> + <option value="25">Stream</option> + <option value="50">Stream/Download</option> + <option value="75">XML-RPC</option> + </select> + </td> + </tr> + <tr> + <td> </td> + <td> + <input type="hidden" name="action" value="add_host"> + <input type="submit" value="<?php print _("Add Host"); ?>"> + </td> + </tr> +</table> +</form> diff --git a/templates/show_admin_index.inc b/templates/show_admin_index.inc new file mode 100644 index 00000000..edf39b5b --- /dev/null +++ b/templates/show_admin_index.inc @@ -0,0 +1,35 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @header Show Admin Index + @discussion shows the admin index +*/ +?> +<p><font size="+1"><?php print _("Admin Section"); ?>:</font></p> +<dl> + <li><a href="<?php echo conf('web_path'); ?>/admin/users.php"><?php print _("Users"); ?></a> - <?php print _("Create/Modify User Accounts for Ampache"); ?></li> + <li><a href="<?php echo conf('web_path'); ?>/admin/mail.php"><?php print _("Mail"); ?></a> - <?php print _("Mail your users to notfiy them of changes"); ?></li> + <li><a href="<?php echo conf('web_path'); ?>/admin/catalog.php"><?php print _("Catalog"); ?></a> - <?php print _("Create/Update/Clean your catalog here"); ?></li> + <li><a href="<?php echo conf('web_path'); ?>/admin/preferences.php"><?php print _("Admin Preferences"); ?></a> - <?php print _("Modify Site-wide preferences"); ?></li> + <li><a href="<?php echo conf('web_path'); ?>/admin/access.php"><?php print _("Access Lists"); ?></a> - <?php print _("Modify Access List Permissions"); ?> (<?php print _("Must have access_control=true in ampache.cfg") ?>)</li> +</dl> + diff --git a/templates/show_album.inc b/templates/show_album.inc new file mode 100644 index 00000000..e5934c05 --- /dev/null +++ b/templates/show_album.inc @@ -0,0 +1,67 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @header Show Album + @discussion shows a single album +*/ + +// Build array of the table classes we are using +$row_classes = array('even','odd'); + +//FIXME: I hate having to create this here again... LAME +$user = new User($_SESSION['userdata']['username']); +?> +<br /> +<table class="border" cellspacing="1" cellpadding="3" border="0"> +<tr class="table-header"> + <td colspan="2"> + <font size="+1"><?php echo $album->name; ; ?> -- + <?php echo $album->f_artist; ; ?></font> + </td> +</tr> +<tr class="even"> + <td width="140" align="center"> + <?php + if ($album_name != "Unknown (Orphaned)") { + echo "<a target=\"_blank\" href=\"" . conf('web_path') . "/albumart.php?id=" . $album->id . "\">"; + echo "<img border=\"0\" src=\"" . conf('web_path') . "/albumart.php?id=" . $album->id . "\" alt=\"Album Art\" height=\"128\">"; + echo "</a>\n"; + } + ?> + </td> + <td valign="top"> + <b>Actions:</b><br /> + <a href="<?php echo conf('web_path'); ; ?>/song.php?action=m3u&album=<?php echo $album->id; ; ?>"><?php echo _("Play Album"); ; ?></a><br /> + <a href="<?php echo conf('web_path'); ; ?>/song.php?action=m3u&album_random=<?php echo $album->id; ; ?>"><?php echo _("Play Random from Album"); ; ?></a><br /> + <a href="<?php echo conf('web_path'); ; ?>/albums.php?action=clear_art&album_id=<?php echo $album->id; ; ?>"><?php echo _("Reset Album Art"); ; ?></a><br /> + <a href="<?php echo conf('web_path'); ; ?>/albums.php?action=find_art&album_id=<?php echo $album->id; ; ?>"><?php echo _("Find Album Art"); ; ?></a><br /> + <?php if ($user->has_access('100') || !conf('use_auth')) { ?> + <a href="<?php echo conf('web_path'); ; ?>/albums.php?action=update_from_tags&album_id=<?php echo $album->id; ; ?>"><?php echo _("Update from tags"); ; ?></a><br /> + <?php } ?> + <?php if( batch_ok() ) { ?> + <a href="<?php echo conf('web_path'); ; ?>/batch.php?action=alb&id=<?php echo $album->id; ; ?>"><?php echo _("Download"); ?></a><br /> + <?php } ?> + + </td> +</tr> +</table> +<br /> diff --git a/templates/show_albums.inc b/templates/show_albums.inc new file mode 100644 index 00000000..24cc4497 --- /dev/null +++ b/templates/show_albums.inc @@ -0,0 +1,82 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @header Show Albums + @discussion shows a list of albums +*/ + +// Build array of the table classes we are using +$total_items = $view->total_items; +?> +<table class="border" cellspacing="0" cellpadding="0" border="0"> +<tr class="even" align="center"> + <td colspan="5"> + <?php if ($view->offset_limit) { require (conf('prefix') . "/templates/list_header.inc"); } ?> + </td> +</tr> +<tr class="table-header"> + <td> + <a href="<?php echo conf('web_path'); ?>/<?php echo $_SESSION['view_script']; ?>?action=<?php echo $_REQUEST['action']; ?>&keep_view=true&sort_type=album.name&sort_order=0"><?php echo _("Album"); ?></a> + </td> + <td> <?php echo _("Artist"); ?> </td> + <td> <?php echo _("Songs"); ?> </td> + <td> + <a href="<?php echo conf('web_path'); ?>/<?php echo $_SESSION['view_script']; ?>?action=<?php echo $_REQUEST['action']; ?>&keep_view=true&sort_type=album.year&sort_order=0"><?php echo _("Year"); ?></> + </td> + <td> <?php echo _("Action"); ?> </td> + +</tr> +<?php +/* Foreach through the albums */ +foreach ($albums as $album) { +?> +<tr class="<?php echo flip_class(); ?>"> + <td><?php echo $album->f_name; ?></td> + <td><?php echo $album->f_artist; ?></td> + <td><?php echo $album->songs; ?></td> + <td><?php echo $album->year; ?></td> + <td nowrap> <?php echo _("Play"); ?> : + <a href="<?php echo conf('web_path'); ?>/song.php?action=m3u&album=<?php echo $album->id; ?>"><?php echo _("All"); ?></a> | + <a href="<?php echo conf('web_path'); ?>/song.php?action=m3u&album_random=<?php echo $album->id; ?>"><?php echo _("Random"); ?></a> + <?php if( batch_ok() ) { ?> + | <a href="<?php echo conf('web_path'); ?>/batch.php?action=alb&id=<?php echo $album->id; ?>"><?php echo _("Download"); ?></a> + <?php } ?> + </td> +</tr> +<?php } ?> +<tr class="table-header"> + <td> + <a href="<?php echo conf('web_path'); ?>/<?php echo $_SESSION['view_script']; ?>?action=<?php echo $_REQUEST['action']; ?>&keep_view=true&sort_type=album.name&sort_order=0"><?php echo _("Album"); ?></a> + </td> + <td> <?php echo _("Artist"); ?> </td> + <td> <?php echo _("Songs"); ?> </td> + <td> <?php echo _("Year"); ?> </td> + <td> <?php echo _("Action"); ?> </td> + +</tr> +<tr class="even" align="center"> + <td colspan="5"> + <?php if ($view->offset_limit) { require (conf('prefix') . "/templates/list_header.inc"); } ?> + </td> +</tr> +</table> +<br /> diff --git a/templates/show_artist.inc b/templates/show_artist.inc new file mode 100644 index 00000000..1b6609d6 --- /dev/null +++ b/templates/show_artist.inc @@ -0,0 +1,96 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +//FIXME: I don't like having to re-create this friggin object.. :( +global $user; +$artist_id = $artist->id; +?> +<br /> +<table class="text-box"> +<tr> + <td> + <span class="header1"><?php print _("Albums by") . " " . $artist->full_name; ?></span> +<ul> + <li><a href="<?php print $web_path; ?>/artists.php?action=show_all_songs&artist=<?php print $artist_id; ?>"><?php print _("Show All Songs By") . " " . $artist->full_name; ?></a></li> + <li><a href="<?php print $web_path; ?>/song.php?action=m3u&artist=<?php print $artist_id; ?>"><?php print _("Play All Songs By") . " " . $artist->full_name; ?></a></li> + <li><a href="<?php print $web_path; ?>/song.php?action=m3u&artist_random=<?php print $artist_id; ?>"><?php print _("Play Random Songs By") . " " . $artist->full_name; ?></a></li> + <?php if ($user->has_access('100')) { ?> + <li><a href="<?php print $web_path; ?>/artists.php?action=update_from_tags&artist=<?php print $artist_id; ?>"><?php print _("Update from tags"); ?></a></li> + <?php } ?> +</ul> + </td> +</tr> +</table> +<!-- *** Multi-Album Art Display Thx MrBlahh Updated by clader *** --> +<br /> +<form name="songs" method="post" enctype="multipart/form-data"> + +<table class="border" cellspacing="0" cellpadding="0" border="0"> + <tr class="table-header"> + <th align="center"> + <a href="#" onclick="check_songs(); return false;"><?php print _("Select"); ?></a> + </th> + <th><?php print _("Cover"); ?></th> + <th><?php print _("Album Name"); ?></th> + <th><?php print _("Album Year"); ?></th> + <th><?php print _("Total Tracks"); ?></th> + <th><?php print _("Action"); ?></th> + </tr> + +<?php + foreach ($albums as $album) { + $id = $album->id; + $album_name = $album->name; + $count = $album->songs; +?> + + <tr class="<?php echo flip_class(); ?>"> + <td align="center"> + <input name="song[]" value="<?php print $id; ?>" type="checkbox" /> + </td> + <td height="87"> + <a href="<?php print $web_path; ?>/albums.php?album=<?php print $id; ?>&artist=<?php print $artist_id; ?>"> + <img border="0" src="<?php print $web_path; ?>/albumart.php?id=<?php print $id; ?>&fast=1" alt="<?php print $album_name; ?>" title="<?php print $album_name; ?>" height="75" width="75"> + </a> + </td> + <td><a href="<?php print $web_path; ?>/albums.php?album=<?php print $id; ?>&artist=<?php print $artist_id; ?>"><?php print $album_name; ?></a></td> + <td><?php print $album->year; ?></td> + <td><?php print $count; ?></td> + <td> + <a href="<?php print $web_path; ?>/song.php?action=m3u&album=<?php print $id; ?>"><?php print _("Play"); ?></a> + <?php if( batch_ok() ) { ?> + | <a href="<?php echo conf('web_path'); ; ?>/batch.php?action=alb&id=<?php echo $album->id; ; ?>"><?php echo _("Download"); ; ?></a> + <?php } ?> + </td> + + + </td> + </tr> +<?php } ?> +</table> + <br /><br /> + +<?php show_play_selected(); ?> +<input type="hidden" name="type" value="album" /> +<input type="hidden" name="artist_id" value="<?php print $artist_id; ?>" /> +</form> + diff --git a/templates/show_artists.inc b/templates/show_artists.inc new file mode 100644 index 00000000..068f1655 --- /dev/null +++ b/templates/show_artists.inc @@ -0,0 +1,76 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/*! + @header Show Artists + Shows multiple artists.... takes an array of artist objects + +*/ + +// Build array of the table classes we are using +$total_items = $view->total_items; +?> +<table class="border" cellspacing="0" cellpadding="0" border="0"> +<tr class="even" align="center"> + <td colspan="5"> + <?php if ($view->offset_limit) { require (conf('prefix') . "/templates/list_header.inc"); } ?> + </td> +</tr> +<tr class="table-header"> + <td> + <a href="<?php print conf('web_path'); ?>/<?php print $_SESSION['view_script']; ?>?action=<?php print $_REQUEST['action']; ?>&keep_view=true&sort_type=artist.name&sort_order=0"> <?php print _("Artist"); ?> </a> + </td> + <td> <?php print _("Songs"); ?> </td> + <td> <?php print _("Albums"); ?> </td> + <td> <?php print _("Action"); ?> </td> +</tr> +<?php +/* Foreach through every artist that has been passed to us */ +//FIXME: These should come in as objects... +foreach ($artists as $artist) { +?> +<tr class="<?php echo flip_class(); ?>"> + <td><?php print $artist['name']; ?></td> + <td><?php print $artist['songs']; ?></td> + <td><?php print $artist['albums']; ?></td> + <td nowrap> <?php print _("Play"); ?> : + <a href="<?php print conf('web_path'); ?>/song.php?action=m3u&artist=<?php print $artist['id']; ?>"><?php print _("All"); ?></a> | + <a href="<?php print conf('web_path'); ?>/song.php?action=m3u&artist_random=<?php print $artist['id']; ?>"><?php print _("Random"); ?></a> + </td> +</tr> +<?php } ?> +<tr class="table-header"> + <td> + <a href="<?php print conf('web_path'); ?>/<?php print $_SESSION['view_script']; ?>?action=<?php print $_REQUEST['action']; ?>&keep_view=true&sort_type=artist.name&sort_order=0"> <?php print _("Artist"); ?> </a> + </td> + <td> <?php print _("Songs"); ?> </td> + <td> <?php print _("Albums"); ?> </td> + + <td> <?php print _("Action"); ?> </td> + +</tr> +<tr class="even" align="center"> + <td colspan="4"> + <?php if ($view->offset_limit) { require (conf('prefix') . "/templates/list_header.inc"); } ?> + </td> +</tr> +</table> + diff --git a/templates/show_box.inc b/templates/show_box.inc new file mode 100644 index 00000000..c3c1dfa3 --- /dev/null +++ b/templates/show_box.inc @@ -0,0 +1,43 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @header + A template file + +*/ + +?> + +<table class="border" cellspacing=1 cellpadding=3> + <tr align=center> + <td background="<?= conf('web_path'); ?>/images/ampache-light-bg.gif" bgcolor="#c0c0c0"><?= $title ?></td> + </tr> + <tr> + <td class="even"> + <? + foreach ($items as $item) { + echo $item; + } + ?> + </td> + </tr> +</table> diff --git a/templates/show_confirm_action.inc.php b/templates/show_confirm_action.inc.php new file mode 100644 index 00000000..89730057 --- /dev/null +++ b/templates/show_confirm_action.inc.php @@ -0,0 +1,32 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +?> + +<br /> +<div class="text-box" style="margin-right:25%;text-align:center;margin-left:25%;"> +<form name="confirm" method="post" action="<?php echo $web_path; ?>/<?php echo $script; ?>?<?php echo $arg; ?>" enctype="multpart/form-data"> + <p><?php echo $text; ?></p> + <p> + <input type="submit" name="confirm" value="<?php echo _("Yes"); ?>" /> + <input type="submit" name="confirm" value="<?php echo _("No"); ?>" /> + </p> +</form> +</div> diff --git a/templates/show_confirmation.inc.php b/templates/show_confirmation.inc.php new file mode 100644 index 00000000..0268a081 --- /dev/null +++ b/templates/show_confirmation.inc.php @@ -0,0 +1,33 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +?> +<br /> +<table class="text-box" align="center" cellspacing="7"> +<tr><td align="center"> + <span class="header1"><?php echo $title; ?></span> + <br /> + <?php echo $text; ?> +</td></tr> +<tr><td> + [ <a href="<?php echo $path; ?>"><?php echo _("Continue"); ?></a> ] + <br /> +</td></tr> +</table> diff --git a/templates/show_disabled_songs.inc b/templates/show_disabled_songs.inc new file mode 100644 index 00000000..6f40c082 --- /dev/null +++ b/templates/show_disabled_songs.inc @@ -0,0 +1,67 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/*! + @header Disabled Songs + +*/ + + +?> +<br /> +<form name="songs" method="post" action="<?php echo conf('web_path'); ?>/admin/catalog.php" enctype="multipart/form-data" style="Display:inline"> +<table class="tabledata" cellspacing="0" cellpadding="3" border="0" width="100%"> +<tr class="table-header"> + <td class="table-header"><a href="#" onclick="check_songs(); return false;">Select</a></td> + <td>Title</td> + <td>Album</td> + <td>Artist</td> + <td>Filename</td> + <td>Addition Time</td> +</tr> +<?php foreach ($songs as $song) { + $class = (++$i%2)?'odd':'even'; +?> +<tr class="<?php echo $class; ?>"> + <td><input type="checkbox" name="song[]" value="<?php echo $song->id; ?>" /></td> + <td><?php echo $song->title; ?></td> + <td><?php echo $song->get_album_name($song->album); ?></td> + <td><?php echo $song->get_artist_name($song->album); ?></td> + <td><?php echo $song->file; ?></td> + <td><?php echo date("h:i:s, m/d/y",$song->addition_time); ?></td> + +</tr> +<?php } ?> +<?php $class = (++$i%2)?'odd':'even'; ?> +<tr class="<?php echo $class; ?>"> + <td> </td> + <td colspan="10"> + <input type="submit" value="Remove" /> + <input type="reset" value="Reset Form" /> + <input type="hidden" name="action" value="remove_disabled" /> + </td> +</tr> +<tr> + <td colspan="10" class="table-header"> </td> +</tr> +</table> +</form> + diff --git a/templates/show_import_playlist.inc.php b/templates/show_import_playlist.inc.php new file mode 100644 index 00000000..b8ea3a94 --- /dev/null +++ b/templates/show_import_playlist.inc.php @@ -0,0 +1,53 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +?> +<form method="post" name="import_playlist" action="<?php echo conf('web_path'); ?>/playlist.php" enctype="multipart/form-data"> +<table border="0" cellpadding="0" cellspacing="0" class="border"> +<tr class="table-header" > + <td colspan="2" align="center"><?php echo _("Importing a Playlist from a File"); ?></td></tr> +<tr class="<?php echo flip_class(); ?>"> + <td> + <?php echo _("Filename"); ?>: + <?php $GLOBALS['error']->print_error('filename'); ?> + </td> + <td><input type="textbox" name="filename" value="<?php echo $_REQUEST['filename']; ?>" size="45" /></td> +</tr> +<tr class="<?php echo flip_class(); ?>"> + <td> + <?php echo _("Playlist Type"); ?> + </td> + <td> + <select name="playlist_type"> + <option name="m3u">M3U</option> +<!-- <option name="pls">PLS</option> --> + </select> + </td> +</tr> +<tr class="<?php echo flip_class(); ?>"> + <td> </td> + <td> + <input type="hidden" name="action" value="import_playlist" /> + <input type="submit" value="<?php echo _("Import Playlist"); ?>" /> + </td> +</tr> +</table> +</form> diff --git a/templates/show_install.inc b/templates/show_install.inc new file mode 100644 index 00000000..ea1666b6 --- /dev/null +++ b/templates/show_install.inc @@ -0,0 +1,82 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @header Show Install Form and instructions + +*/ +?> +<html> +<head> +<title>Ampache :: For The Love Of Music - Install</title> +</head> +<body> +<?php require_once(conf('prefix') . "/templates/style.inc"); ?> +<div class="text-box"> +<span class="header1"><?php echo _("Ampache Installation"); ?></span> +<p> +<?php echo _("This Page handles the installation of the ampache database and the creation of the ampache.cfg.php file. Before you continue please make sure that you have the following pre-requisits"); ?> +</br /> +<ul> + <li><?php echo _("A MySQL Server with a username and password that can create/modify databases"); ?></li> + <li><?php echo _("Your webserver has read access to the /sql/ampache.sql file and the /config/ampache.cfg.php.dist file"); ?></li> +</ul> +<?php echo _("Once you have ensured that you have the above requirements please fill out the information below. You will only be asked for the required config values. If you would like to make changes to your ampache install at a later date simply edit /config/ampache.cfg.php"); ?> +</p> +</div> +<div class="text-box"> +<b><?php echo _("Step 1 - Creating and Inserting the Ampache Database"); ?></b><br /> +<dl> + <dd><?php echo _("This step creates and inserts the Ampache database, as such please provide a mysql account with database creation rights. This step may take a while depending upon the speed of your computer"); ?></dd> +</dl> +<?php echo _("Step 2 - Creating the Ampache.cfg.php file"); ?><br /> +<?php echo _("Step 3 - Setup Initial Account"); ?><br /> +<br /><br /> +<span class="header2">Insert Ampache Database</span> +<form method="post" action="<?php echo $http_type . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'] . "?action=create_db"; ?>" enctype="multipart/form-data" > +<table cellpadding="5" cellspacing="0" border="0"> +<tr> + <td><?php echo _("Desired Database Name"); ?></td> + <td><input type="textbox" name="local_db" value="ampache" /></td> +</tr> +<tr> + <td><?php echo _("MySQL Hostname"); ?></td> + <td><input type="textbox" name="local_host" value="localhost" /></td> +</tr> +<tr> + <td><?php echo _("MySQL Administrative Username"); ?></td> + <td><input type="textbox" name="local_username" value="root" /></td> +</tr> +<tr> + <td><?php echo _("MySQL Administrative Password"); ?></td> + <td><input type="password" name="local_pass" /></td> +</tr> +<tr> + <td> </td> + <td><input type="submit" value="<?php echo _("Insert Database"); ?>" /></td> +</tr> + +</table> +</form> +</div> + +</body> +</html> diff --git a/templates/show_install_account.inc.php b/templates/show_install_account.inc.php new file mode 100644 index 00000000..014e48dc --- /dev/null +++ b/templates/show_install_account.inc.php @@ -0,0 +1,76 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @header Show Install Config File + +*/ + +?> +<html> +<head> +<title>Ampache :: For The Love Of Music - Install</title> +</head> +<body> +<?php require_once(conf('prefix') . "/templates/style.inc"); ?> +<div class="text-box"> +<span class="header1"><?php echo _("Ampache Installation"); ?></span> +<p> +<?php echo _("This Page handles the installation of the ampache database and the creation of the ampache.cfg.php file. Before you continue please make sure that you have the following pre-requisits"); ?> +</br /> +<ul> + <li><?php echo _("A MySQL Server with a username and password that can create/modify databases"); ?></li> + <li><?php echo _("Your webserver has read access to the /sql/ampache.sql file and the /config/ampache.cfg.dist.php file"); ?></li> +</ul> +<?php echo _("Once you have ensured that you have the above requirements please fill out the information below. You will only be asked for the required config values. If you would like to make changes to your ampache install at a later date simply edit /config/ampache.cfg.php"); ?> +</p> +</div> + +<div class="text-box"> +<?php echo _("Step 1 - Creating and Inserting the Ampache Database"); ?><br /> +<?php echo _("Step 2 - Creating the ampache.cfg.php file"); ?><br /> +<b><?php echo _("Step 3 - Setup Initial Account"); ?></b><br /> +<dl> + <dd><?php echo _("This step creates your initial Ampache admin account. Once your admin account has been created you will be directed to the login page"); ?></dd> +</dl> +<br /><br /> +<span class="header2">Create Admin Account</span> +<form method="post" action="<?php echo $_SERVER['PHP_SELF'] . "?action=create_account"; ?>" enctype="multipart/form-data" > +<table cellpadding="5" cellspacing="0" border="0"> +<tr> + <td><?php echo _("Username"); ?></td> + <td><input type="textbox" name="local_username" value="admin" /></td> +</tr> +<tr> + <td><?php echo _("Password"); ?></td> + <td><input type="password" name="local_pass" value="" /></td> +</tr> +<tr> + <td> </td> + <td><input type="submit" value="<?php echo _("Create Account"); ?>" /></td> +</tr> +</table> +</form> +</div> + +</body> +</html> + diff --git a/templates/show_install_config.inc b/templates/show_install_config.inc new file mode 100644 index 00000000..28544569 --- /dev/null +++ b/templates/show_install_config.inc @@ -0,0 +1,136 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @header Show Install Config File + +*/ + +?> +<html> +<head> +<title>Ampache :: For The Love Of Music - Install</title> +</head> +<body> +<?php require_once(conf('prefix') . "/templates/style.inc"); ?> +<div class="text-box"> +<span class="header1"><?php echo _("Ampache Installation"); ?></span> +<p> +<?php echo _("This Page handles the installation of the ampache database and the creation of the ampache.cfg.php file. Before you continue please make sure that you have the following pre-requisits"); ?> +</br /> +<ul> + <li><?php echo _("A MySQL Server with a username and password that can create/modify databases"); ?></li> + <li><?php echo _("Your webserver has read access to the /sql/ampache.sql file and the /config/ampache.cfg.php.dist file"); ?></li> +</ul> +<?php echo _("Once you have ensured that you have the above requirements please fill out the information below. You will only be asked for the required config values. If you would like to make changes to your ampache install at a later date simply edit /config/ampache.cfg.php"); ?> +</p> +</div> + +<div class="text-box"> +<?php echo _("Step 1 - Creating and Inserting the Ampache Database"); ?><br /> +<b><?php echo _("Step 2 - Creating the Ampache.cfg.php file"); ?></b><br /> +<dl> + <dd><?php echo _("This steps takes the basic config values, and first attempts to write them out directly to your webserver. If access is denied it will prompt you to download the config file. Please put the downloaded config file in /config"); ?></dd> +</dl> +<?php echo _("Step 3 - Setup Initial Account"); ?></a><br /> +<br /><br /> +<span class="header2">Generate Config File</span> +<form method="post" action="<?php echo $_SERVER['PHP_SELF'] . "?action=create_config"; ?>" enctype="multipart/form-data" > +<table cellpadding="5" cellspacing="0" border="0"> +<tr> + <td><?php echo _("Web Path"); ?></td> + <td><input type="textbox" name="web_path" value="<?php echo $web_path; ?>" /></td> +</tr> +<tr> + <td><?php echo _("Desired Database Name"); ?></td> + <td><input type="textbox" name="local_db" value="<?php echo $_REQUEST['local_db']; ?>" /></td> +</tr> +<tr> + <td><?php echo _("MySQL Hostname"); ?></td> + <td><input type="textbox" name="local_host" value="<?php echo $_REQUEST['local_host']; ?>" /></td> +</tr> +<tr> + <td><?php echo _("MySQL Username"); ?></td> + <td><input type="textbox" name="local_username" value="username" /></td> +</tr> +<tr> + <td><?php echo _("MySQL Password"); ?></td> + <td><input type="password" name="local_pass" value="" /></td> +</tr> +<tr> + <td> </td> + <td><input type="submit" value="<?php echo _("Write Config"); ?>" /></td> +</tr> +</table> +</form> +<br /> +<table border="0" cellpadding="0"> +<tr> + <td valign="top"><?= _("Ampache.cfg.php Exists"); ?></td> + <td valign="top">[ + <? + if (!read_config_file($configfile)) { + $status['read_config'] = 'false'; + echo " <font color=\"red\">ERROR</font> "; + } + else { + $status['read_config'] = 'true'; + echo " <font color=\"green\"> OK   </font> "; + } + ?> + ] + </td> +</tr> +<tr> + <td valign="top"> + <?= _("Ampache.cfg.php Configured?"); ?> + </td> + <td valign="top">[ + <? + $results = read_config($configfile, 0, 0); + if (!check_config_values($results)) { + $status['parse_config'] = 'false'; + echo " <font color=\"red\">ERROR</font> "; + } + else { + $status['parse_config'] = 'true'; + echo " <font color=\"green\"> OK </font> "; + } + ?> + ] + </td> +</tr> +<tr> + <td> </td> + <td> + <a href="<?php echo $http_type . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF']; ?>?action=show_create_config&local_db=<?php echo $_REQUEST['local_db']; ?>&local_host=<?php echo $_REQUEST['local_host']; ?>">[<?php echo _("Check for Config"); ?>]</a> + </td> +</tr> +</table> +<br /> +<form method="post" action="<?php echo $_SERVER['PHP_SELF'] . "?action=show_create_account"; ?>" enctype="multipart/form-data"> +<input type="Submit" value="Continue to Step 3" /> +</form> +</div> + +</body> +</html> + diff --git a/templates/show_localplay.inc b/templates/show_localplay.inc new file mode 100644 index 00000000..93e5be14 --- /dev/null +++ b/templates/show_localplay.inc @@ -0,0 +1,68 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Show localplay form +*/ +$web_path = conf('web_path'); +?> + <table class="border" cellspacing="1" cellpadding="3" width="100%" border="0"> + <tr class="table-header"> + <td colspan="2"><?php echo _("Local Play Control"); ?></td> + </tr> + <tr class="even"><td> + <table border="0" cellpadding="0" cellspacing="0"><tr> + <td> + <?php echo _("Playback") . ":"; ?> + </td> + <td> + <form action="<?php echo $web_path; ?>/localplay.php" method="post" name="playcontrol" style="display:inline;"> + <input type="submit" title="<?php echo _("Prev"); ?>" name="submit" value="|< "> + <input type="submit" title="<?php echo _("Stop"); ?>" name="submit" value=" X "> + <input type="submit" title="<?php echo _("Play"); ?>" name="submit" value=" > "> + <input type="submit" title="<?php echo _("Pause"); ?>" name="submit" value=" = "> + <input type="submit" title="<?php echo _("Next"); ?>" name="submit" value=" >|"> + </form> + </td> + </tr> + <tr class="even"> + <td> + <?php echo _("Volume") . ":"; ?> + </td> + <td> + <form action="<?php echo $web_path; ?>/localplay.php" method="post" name="playcontrol" style="display:inline;"> + <input type="submit" title="<?php echo _("Increase Volume"); ?>" name="submit" value=" +1 "> + <input type="submit" title="<?php echo _("Increase Volume"); ?>" name="submit" value=" +5 "> + <input type="submit" title="<?php echo _("Decrease Volume"); ?>" name="submit" value=" -1 "> + <input type="submit" title="<?php echo _("Decrease Volume"); ?>" name="submit" value=" -5 "> + </form> + </td> + </tr> + <tr class="even"> + <td colspan="2"> + <a href="<?php echo $web_path; ?>/localplay.php?submit=clear"><?php echo _("Clear queue"); ?></a><br /> + <?php @system(conf('localplay_status')) ?> + </td></tr> + </table> + </td> + </tr> + </table> diff --git a/templates/show_login_form.inc b/templates/show_login_form.inc new file mode 100644 index 00000000..b1e4e9a2 --- /dev/null +++ b/templates/show_login_form.inc @@ -0,0 +1,83 @@ +<?php +/* + Copyright (c) 2001 - 2005 Ampache.org + All Rights Reserved + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Login Template +Login Template + +*/ + +$referrer = $_SERVER['HTTP_REFERER']; + +$subject = "/ampache.com/"; + +if (preg_match($subject,$_SERVER['HTTP_HOST'])) { + $show_copyright = 1; +} + +?> +<br /><br /> +<p align="center"> + <a href="http://www.ampache.org"><img src="<?php echo conf('web_path'); ?> + <?php echo conf('theme_path'); ?>/images/ampache.gif" title="<?php echo conf('site_title'); ?>" border="0" /> + </a> +</p> +<form name="login" method="post" enctype="multipart/form-data" action="<?php echo conf('web_path'); ; ?>/login.php" style="Display:inline"> +<table bgcolor="<?php echo conf('base_color2'); ?>" border="0" align="center"> + <tr> + <td align="center" colspan="2"><?php echo conf('login_message'); ; ?> </td> + </tr> + <tr> + <td><?php echo _("Login"); ; ?>:</td> + <td><input type="text" name="username" value="<?php echo $_REQUEST['username']; ; ?>" /></td> + </tr> + <tr> + <td><?php echo _("Password"); ; ?>:</td> + <td><input type="password" name="password" value="" /></td> + </tr> + <tr> + <td> </td> + <td><input type="checkbox" name="rememberme" /> + <?php echo _("Remember Me"); ?></td> + </tr> + <tr> + <td colspan="2" align="center"> + <input class="button" type="submit" value="<?php echo _("Login"); ?>" /> + <input type="hidden" name="referrer" value="<?php echo $referrer; ?>" /> + <input type="hidden" name="action" value="login" /> + </td> + </tr> +</table> +</form> +<?php if ($show_copyright == 1) { ?> +<p align="center"> + <font color="red" size="+2"> + <a href="http://www.ampache.org/donations.php">Ampache.org</a><br /> + Ampache.com Domain Squatter<br /> + All Rights Reserved, Copyright © 2005<br /> + </font> +</p> +<? } ?> +<?php if (isset($auth['error'])) { ?> +<p align="center"> + <font color="red"><?php echo trim($auth['error']); ?></font> +</p> +<?php } ?> diff --git a/templates/show_mpd.inc b/templates/show_mpd.inc new file mode 100644 index 00000000..bac3b8de --- /dev/null +++ b/templates/show_mpd.inc @@ -0,0 +1,97 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Show mpd form +*/ +?> +<div align="center"> +<table border="0" cellpadding="0" cellspacing="0"> +<tr> + <td> + <table border="0" cellpadding="0" cellspacing="0" bgcolor="<?php conf('base_color1'); ?>" width="100%"> + <TR><TD CLASS="content"> +State: <?php + switch ($myMpd->state) { + case MPD_STATE_PLAYING: echo "<B>Playing</B> [<A HREF='".$_SERVER[PHP_SELF]."?m=pause'>Pause</A>] [<A HREF='".$_SERVER[PHP_SELF]."?m=stop'>Stop</A>]"; break; + case MPD_STATE_PAUSED: echo "<B>Paused</B> [<A HREF='".$_SERVER[PHP_SELF]."?m=pause'>Unpause</A>]"; break; + case MPD_STATE_STOPPED: echo "<B>Idle</B> [<A HREF='".$_SERVER[PHP_SELF]."?m=play'>Play</A>]"; break; + default: echo "(Unknown State!)"; break; + } ?> + </TD></TR> + <TR><TD CLASS="content"> + Loop: <?php + switch ($myMpd->repeat) { + case 0: echo "<B>Loop is off</B> [<A HREF='".$_SERVER[PHP_SELF]."?m=loop&val=1'>On</A>]"; break; + case 1: echo "<B>Loop is on</B> [<A HREF='".$_SERVER[PHP_SELF]."?m=loop&val=0'>Off</A>]"; break; + default: echo "(Unknown State!)"; break; + } ?> + </TD></TR> + <TR><TD CLASS="content"> + + </TD></TR> + </TABLE> + </TR></TD> +<?php if ( $myMpd->state == MPD_STATE_PLAYING or $myMpd->state == MPD_STATE_PAUSED ) { ?> + <TR><TD> + <TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0 BGCOLOR="WHITE" WIDTH="100%"> + <TR><TD CLASS="content">Now Playing: (<?php echo (round(($myMpd->current_track_position/$myMpd->current_track_length),2)*100) ?>% cmpl.)</TD></TR> + <TR><TD CLASS="content" ALIGN=CENTER><SPAN CLASS=SongPlaying><?php echo $myMpd->playlist[$myMpd->current_track_id]['Artist']." - ".$myMpd->playlist[$myMpd->current_track_id]['Title'] ?> </SPAN></TD></TR> + </TABLE> + </TD></TR> +<?php } ?> + <TR><TD> + <TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0 BGCOLOR="WHITE" WIDTH="100%"> + <TR><TD CLASS="content" ALIGN=CENTER>[ <A TITLE="Refresh the Playlist Window" HREF="<?php echo $_SERVER[PHP_SELF] ?>">refresh now</A> ]</TD></TR> + </TABLE> + </TD></TR> +</TABLE> +<BR> +<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0 WIDTH=<?php echo $PG_WIDTH ?>> + <TR><TD ALIGN="CENTER"><B>Server Playlist</B></TD></TR> + <TR><TD> + <TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0 BGCOLOR="WHITE" WIDTH="100%"> +<?php + $pl = $myMpd->GetPlaylist(); + if ( is_null($pl) ) echo "ERROR: " .$myMpd->errStr."\n"; + else { + foreach ($pl as $id => $entry) { + $tblClass = ( $id == $myMpd->current_track_id ? "SongPlaying" : "Song" ); + echo "<TR HEIGHT=35><TD CLASS=\"content\"><SPAN CLASS=\"".$tblClass."\">"; + echo "<A TITLE=\"Click to remove '".$entry['Title']."' from playlist\" HREF='".$_SERVER[PHP_SELF]."?m=rem&val=".$id."'>".($id+1)."</A>" . ". <A TITLE=\"Click to jump to '".$entry['Title']."' from playlist\" HREF='".$_SERVER[PHP_SELF]."?m=skipto&id=".$id."'>".$entry['Artist']." - ".$entry['Title']."</A>"; + echo "</SPAN></TD></TR>"; + echo "\n"; // make it perty + } + } + if ( $myMpd->playlist_count == 0 ) { + echo "<TR><TD CLASS=\"content\" ALIGN=CENTER><SPAN CLASS=\"Song\"><I>(Playlist is empty)</I></SPAN></TD></TR>"; + } +?> + </TABLE> + </TD></TR> +<?php if ( $myMpd->playlist_count > 0 ) { ?> + <TR HEIGHT=20><TD ALIGN=CENTER>[<A TITLE="Click to shuffle (randomize) the playlist" HREF="<?php echo $_SERVER[PHP_SELF] ?>?m=shuffle">shuffle</A>] [<A TITLE="Click the clear the playlist" HREF="<?php echo $_SERVER[PHP_SELF] ?>?m=clear">clear</A>]</TD></TR> +<?php } ?> +</TABLE> +</DIV> +<!-- MPD-Class version: <?php echo $myMpd->mpd_class_version ?> --> +</BODY></HTML> diff --git a/templates/show_mpdplay.inc b/templates/show_mpdplay.inc new file mode 100644 index 00000000..2825547c --- /dev/null +++ b/templates/show_mpdplay.inc @@ -0,0 +1,152 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Show mpd controls, this doesn't + include the playlist, status and what have you. + this looks a goodbit like local_play +*/ +$web_path = conf('web_path'); +?> +<div align="center"> +<table border="0" cellpadding="3" cellspacing="0"> +<tr class="table-header"> + <td colspan="2"><?php echo _("MPD Play Control"); ?></td> +</tr> +<tr class="even"><td> + <table border="0" cellpadding="0" cellspacing="0" width="100%"> + <tr> + <td> +<?php + ${$myMpd->state} = "style=\"background-color: black;color:white;\""; +?> + <form action="<?php echo $web_path; ?>/amp-mpd.php" method="post" name="playcontrol" style="display:inline;"> + <input class="button" type="submit" title="<?php echo _("Prev"); ?>" name="action" value="|< " /> + <input class="button" type="submit" title="<?php echo _("Stop"); ?>" name="action" value=" X " <?php echo $stop; ?> /> + <input class="button" type="submit" title="<?php echo _("Play"); ?>" name="action" value=" > " <?php echo $play; ?> /> + <input class="button" type="submit" title="<?php echo _("Pause"); ?>" name="action" value=" = " <?php echo $paused; ?> /> + <input class="button" type="submit" title="<?php echo _("Next"); ?>" name="action" value= " >|" /> + </form> + </td> +</tr> +<tr> + <td> + <?php echo _("Loop"); ?>: + <form action="<?php echo $web_path; ?>/amp-mpd.php?action=loop" method="post" name="repeatcontrol" style="display:inline;"> + <?php + $repeat_name = "repeat_" . intval($myMpd->repeat); + ${$repeat_name} = "style=\"background-color: black;color:white;\""; + ?> + + <input class="button" type="submit" title="<?php echo _("On"); ?>" name="val" value="On" <?php echo $repeat_1; ?> /> + <input class="button" type="submit" title="<?php echo _("Off"); ?>" name="val" value="Off" <?php echo $repeat_0; ?> /> + </form> + </td> +</tr> +<tr> + <td> + <?php echo _("Random"); ?>: + <form action="<?php echo $web_path; ?>/amp-mpd.php?action=random" method="post" name="randomcontrol" style="display:inline;"> + <?php + $random_name = "random_" . intval($myMpd->random); + ${$random_name} = "style=\"background-color: black;color:white;\""; + ?> + <input class="button" type="submit" title="<?php echo _("On"); ?>" name="val" value="On" <?php echo $random_1; ?> /> + <input class="button" type="submit" title="<?php echo _("Off"); ?>" name="val" value="Off" <?php echo $random_0; ?> /> + </form> + </td> +</tr> +<tr> + <td class="content"> + + </td> +</tr> +</table> +</tr> + </td> +<?php if ( $myMpd->state == MPD_STATE_PLAYING or $myMpd->state == MPD_STATE_PAUSED ) { ?> + <tr><td> + <table border="0" cellpadding="0" cellspacing="0" width="100%"> + <tr> + <td><?php echo _("Now Playing"); ?>: (<?php echo (round(($myMpd->current_track_position/$myMpd->current_track_length),2)*100) ?>% cmpl.) + </td> + </tr> + <tr> + <td align="center"><?php echo $myMpd->playlist[$myMpd->current_track_id]['Artist']." - ".$myMpd->playlist[$myMpd->current_track_id]['Title'] ?></td> + </tr> + </table> + </td></tr> +<?php } ?> +<tr> + <td> + <table border="0" cellpadding="0" cellspacing="0" width="100%"> + <tr> + <td align="center">[ <a title="<?php echo _("Refresh the Playlist Window"); ?>" href="<?php echo conf('web_path'); ?>"><?php echo _("refresh now"); ?></a> ]</td> + </tr> + </table> + </td> +</tr> +</table> +<br /> +<table border="0" cellpadding="0" cellspacing="0" WIDTH=<?php echo $PG_WIDTH ?>> + <tr><td align="center"><b><?php echo _("Server Playlist"); ?></b></td></tr> + <tr><td> +<?php + $pl = $myMpd->GetPlaylist(); + if ( is_null($pl) ) echo "ERROR: " .$myMpd->errStr."\n"; + else { + $maxlen = strlen(count($pl)); + foreach ($pl as $id => $entry) { + $tblClass = ( $id == $myMpd->current_track_id ? "SongPlaying" : "Song" ); + $track = $id+1; + + // Make all number lengths equal + $len = strlen($track); + + while ($len < $maxlen) { + $track = "0" . $track; + $len++; + } + + $song_name = truncate_with_ellipse($entry['Artist'],conf('ellipse_threshold_artist')-3) . " - " . truncate_with_ellipse($entry['Title'],conf('ellipse_threshold_title')-3); + + echo "\t"; + echo "<a title=\"Click to remove'".$entry['Title']." '\" href=\"".conf('web_path')."/amp-mpd.php?action=rem&id=".$id."\">[" . $track . "]</a>"; + echo " <a title=\"Click to jump to '".$entry['Title']."'\" href='".conf('web_path')."/amp-mpd.php?action=skipto&val=".$id."'>$song_name</a>"; + echo "<br />\n"; + } + } + if ( $myMpd->playlist_count == 0 ) { + echo "<i>(Playlist is empty)</i><br />\n"; + } +?> + </td></tr> +<?php if ( $myMpd->playlist_count > 0 ) { ?> +<tr height="20"> + <td align="center"> + [<a title="<?php echo _("Click to shuffle (randomize) the playlist"); ?>" href="<?php echo conf('web_path'); ?>/amp-mpd.php?action=shuffle"><?php echo _("shuffle"); ?></a>] + [<a title="<?php echo _("Click the clear the playlist"); ?>" href="<?php echo conf('web_path'); ?>/amp-mpd.php?action=clear"><?php echo _("clear"); ?></a>] + </td> +</tr> +<?php } ?> +</table> +</div> diff --git a/templates/show_now_playing.inc b/templates/show_now_playing.inc new file mode 100644 index 00000000..875b1771 --- /dev/null +++ b/templates/show_now_playing.inc @@ -0,0 +1,65 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @header Show Now Playing + +*/ + +?> +<?php if (count($results)) { ?> +<table class="border" cellspacing="1" cellpadding="3" border="0" width="100%"> + <tr class="table-header"> + <td colspan="4"><?php echo _("Now Playing"); ?></td> + </tr> +<?php + foreach($results as $item) { + $song = $item['song']; + $np_user = $item['user']; + if (is_object($song)) { + $artist = $song->f_artist; + $album = $song->get_album_name(); + $text = "$song->f_title"; + if (!$np_user->fullname) { $np_user->fullname = "Unknown User"; } + echo "<tr class=\"even\">\n"; + if (conf('use_auth')) { + echo "\t<td valign=\"center\">$np_user->fullname</td>\n"; + } + echo "\t<td><a title=\"$song->title\" href=\"$web_path/song.php?action=m3u&song=$song->id\">$text</a></td>\n"; + echo "\t<td><a title=\"$song->f_artist\" href=\"$web_path/artists.php?action=show&artist=$song->artist\">$song->f_artist</a> / "; + echo "\t<a title=\"$album\" href=\"$web_path/albums.php?action=show&album=$song->album\">$song->f_album</a></td>"; + if (conf('play_album_art')) { + echo "\t<td align=\"center\">"; + echo "<a target=\"_blank\" href=\"" . conf('web_path') . "/albumart.php?id=" . $song->album . "\">"; + echo "<img align=\"center\" border=\"0\" src=\"" . conf('web_path') . "/albumart.php?id=" . $song->album . "&fast=1\" alt=\"Album Art\" height=\"75\">"; + echo "</a>\n"; + echo "\t</td>\n"; + echo "</tr>\n"; + } // if album art on now playing + else { + echo "\n</tr>"; + } + } // if it's a song + } // while we're getting songs +?> + </tr> +</table> +<? } ?> diff --git a/templates/show_play_selected.inc.php b/templates/show_play_selected.inc.php new file mode 100644 index 00000000..7c3107fb --- /dev/null +++ b/templates/show_play_selected.inc.php @@ -0,0 +1,65 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +?> +<script language=javascript> +<!-- +function ToPlaylist(action) +{ + document.songs.action = "<?php echo conf('web_path'); ?>/playlist.php?action=" + action; + document.songs.submit(); // Submit the page + return true; +} + +function ToSong(action) +{ + document.songs.action = "<?php echo conf('web_path'); ?>/song.php?action=" + action; + document.songs.submit(); // Submit the page + return true; +} +--> +</script> +<table border="0" cellpadding="14" cellspacing="0" class="text-box"> +<tr align="left"> + <td> + <input class="button" type="button" name="super_action" value="<?php echo _("Play Selected"); ?>" onclick="return ToSong('play_selected');" /> +<!-- <input class="button" type="button" name="super_action" value="<?php echo _("Flag Selected"); ?>" /> + <input class="button" type="button" name="super_action" value="<?php echo _("Edit Selected"); ?>" /> +--> + </td> +</tr> +<?php if ($GLOBALS['playlist_id']) { ?> +<tr> + <td> + <input class="button" type="button" name="super_action" value="<?php echo _("Set Track Numbers"); ?>" onclick="return ToPlaylist('set_track_numbers');" /> + <input class="button" type="button" name="super_action" value="<?php echo _("Remove Selected Tracks"); ?>" onclick="return ToPlaylist('remove_song');" /> + </td> +</tr> +<?php } ?> +<tr align="center"> + <td colspan="2"> + <?php echo _("Playlist"); ?>: <input type="button" name="super_action" value="<?php echo _("Add to"); ?>" onclick="return ToPlaylist('add_to');" /> + <?php show_playlist_dropdown($GLOBALS['playlist_id']); ?> + <input class="button" type="button" name="super_action" value="<?php echo _("View"); ?>" onclick="return ToPlaylist('view');" /> + <input class="button" type="button" name="super_action" value="<?php echo _("Edit"); ?>" onclick="return ToPlaylist('edit');" /> + </td> +</tr> +</table> diff --git a/templates/show_preferences.inc b/templates/show_preferences.inc new file mode 100644 index 00000000..35982178 --- /dev/null +++ b/templates/show_preferences.inc @@ -0,0 +1,92 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Show Preferences + @discussion shows edit page for preferences + +*/ +?> + +<div class="header1"> + <?php echo _("Editing"); ?> <?php echo $fullname; ?> <?php echo _("preferences"); ?> + <?php if ($user->has_access(100)) { ?> + [<a href="<?php echo conf('web_path'); ?>/admin/preferences.php?action=fix_preferences&user_id=<?php echo $user_id; ?>"><?php echo _("Rebuild Preferences"); ?></a>] + <? } ?> +</div> +<form method="post" name="preferences" action="<?php echo conf('web_path'); ?><?php echo $target; ?>" enctype="multipart/form-data"> +<table class="border" border="0" cellpadding="0" cellspacing="0"> +<tr class="table-header"> + <th><?php echo _("Preference"); ?></th> + <th><?php echo _("Value"); ?></th> + <?php if ($user->has_access(100) AND $user_id == '0') { ?> + <th><?php echo _("Type"); ?></th> + <th><?php echo _("Apply to All"); ?></th> + <?php } ?> +</tr> +<?php + + foreach ($preferences as $pref) { + if ($pref->type == "system") { + $table_break = 1; + } + if ($pref->type == "user" AND $table_break == '1') { + $table_break = 0; + ?> + <tr class="table-header"> + <td colspan="4" style="background:<?php echo conf('bg_color1'); ?>;"> </td> + </tr> + <?php + } +?> +<tr class="<?php echo flip_class(); ?>"> + <td><?php echo _($pref->description); ?></td> + <td><table> + <tr> + <td><?php create_preference_input($pref->name,$pref->value); ?></td> + <?php if(preg_match('/Color/',$pref->description)) { ?> + <td><table width="40" height="20" border=3 bgcolor="<?php echo $pref->value;?>"><tr><td></td></tr></table></td> + <?php } else { ?> + <td><table></table></td> + <?php } ?> + </tr> + </table> + </td> + + <?php if ($user->has_access(100) AND $user_id == '0') { ?> + <td><?php echo $pref->type; ?></td> + <td align="center"><input type="checkbox" name="check_<?php echo $pref->name; ?>" value="1" /></td> + <?php } ?> +</tr> +<?php } ?> +<tr class="<?php echo flip_class(); ?>"> + <td colspan="4"> + <input class="button" type="submit" value="<?php echo _("Update Preferences"); ?>" /> + <input type="hidden" name="action" value="update_preferences" /> + <input type="hidden" name="user_id" value="<?php echo $user_id; ?>" /> + + <input class="button" type="submit" name="action" value="<?php echo _("Cancel"); ?>" /> + </td> +</tr> +</table> +</form> +<br /><br /> diff --git a/templates/show_search.inc b/templates/show_search.inc new file mode 100644 index 00000000..78433854 --- /dev/null +++ b/templates/show_search.inc @@ -0,0 +1,89 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +/*! + @header search template + @discussion This is the template for the searches... amazing! + +*/ + +?> +<form name="search" method="post" action="<?php echo conf('web_path'); ?>/search.php" enctype="multipart/form-data" style="Display:inline"> +<table class="tabledata" cellspacing="0" cellpadding="3" border="0" width="450px" style="clear:both;"> +<tr class="table-header"> + <td colspan="2"><b><?php echo _("Search Ampache"); ?>...</b></td> +</tr> +<tr class="<?php echo flip_class(); ?>"> + <td><?php echo _("Search"); ?>:</td> + <td><input type="text" name="search_string" value="<?php echo $_REQUEST['search_string']; ?>" /></td> +</tr> +<tr class="<?php echo flip_class(); ?>"> + <td><?php echo _("Object Type"); ?>:</td> + <td> + <?php + $search_type = $_REQUEST['search_field']; + if (isset($_REQUEST['search_field'])) { + $search_field = $_REQUEST['search_field']; + ${$search_field} = 1; + } else { + $search_field = conf('search_field'); + ${$search_field} = 1; + } + + if (isset($_REQUEST['search_type'])) { + $search_type = $_REQUEST['search_type']; + ${$search_type} = 1; + } else { + $search_type = conf('search_type'); + ${$search_type} = 1; + } + ?> + <select name="search_field"> + <option value="artist" <?php if ($artist) { echo "SELECTED"; } ?>>Artist</option> + <option value="album" <?php if ($album) { echo "SELECTED"; } ?>>Album</option> + <option value="song_title" <?php if ($song_title) { echo "SELECTED"; } ?>>Song Title</option> + <option value="song_genre" <?php if ($song_genre) { echo "SELECTED"; } ?>>Song Genre</option> + <option value="song_year" <?php if ($song_year) { echo "SELECTED"; } ?>>Song Year</option> + <option value="song_bitrate" <?php if ($song_bitrate) { echo "SELECTED"; } ?>>Song Bitrate</option> + <option value="song_min_bitrate" <?php if ($song_min_bitrate) { echo "SELECTED"; } ?>>Minimum Bitrate</option> + <option value="song_filename" <?php if ($song_filename) { echo "SELECTED"; } ?>>Song Filename</option> + </select> + </td> +</tr> +<tr class="<?php echo flip_class(); ?>"> + <td><?php echo _("Search Type"); ?>:</td> + <td> + <input type="radio" name="search_type" value="exact" <?php if ($_REQUEST['search_type'] === 'exact') { echo "CHECKED"; } ?>>Exact<br /> + <input type="radio" name="search_type" value="fuzzy" <?php if ($_REQUEST['search_type'] !== 'exact') { echo "CHECKED"; } ?>>Fuzzy<br /> + </td> +</tr> +<tr class="<?php echo flip_class(); ?>"> + <td> </td> + <td> + <input type="submit" value="<?php echo _("Search"); ; ?>" /> + <input type="reset" value="Reset Form" /> + <input type="hidden" name="action" value="search" /> + </td> +</tr> +</table> +</form> diff --git a/templates/show_songs.inc b/templates/show_songs.inc new file mode 100644 index 00000000..13819f3e --- /dev/null +++ b/templates/show_songs.inc @@ -0,0 +1,150 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +$web_path = conf('web_path'); +?> + + +<form name="songs" method="post" enctype="multipart/form-data"> +<table border="0"> + <tr><td colspan="2"> + <table class="border" cellspacing="0" cellpadding="0" border="0"> + <tr class="table-header"> + <th> <a href="#" onclick="check_songs(); return false;">Select</a></th> + <?php if (isset($playlist_id) && $playlist_id != 0 && ($user->id == $pluser->id || $user->has_access('100'))) { $tab = 1; ?> <th><?php echo _("Track"); ?></th> <?php } ?> + <th><?php echo _("Song title"); ?></th> + <th><?php echo _("Artist"); ?></th> + <th><?php echo _("Album"); ?></th> + <th><?php echo _("Track"); ?></td> + <th><?php echo _("Time"); ?></th> + <th><?php echo _("Size"); ?></th> + <th><?php echo _("Bitrate"); ?></th> + <th><?php echo _("Genre"); ?></th> + <th><?php echo _("Flag"); ?></th> + <th><?php echo _("Action"); ?></th> + </tr> + <?php + foreach ($song_ids as $song_id) { + + unset($text_class); + $song = new Song($song_id); + $song->format_song(); + + // Still needed crap + $totalsize += $song->size; + $totaltime += $song->time; + if ($song->status == "disabled") { $text_class = "class=\"disabled\""; } + ?> + <tr class="<?php echo flip_class(); ?>"> + <td align="center"> + <input type="checkbox" name="song[]" value="<?php echo $song->id; ?>" id="song_<?php echo $song->id; ?>"></input> + </td> + <?php + if (isset($playlist_id) && $playlist_id != 0 && ($user->id == $pluser->id || $user->has_access('100'))) { + $tracknum = get_playlist_track_from_song($playlist_id, $song->id); + ?> + <td> + <input type="text" tabindex="<?php echo $tab; ?>" size="3" name="<?php echo "tr_" . $song->id; ?>" value="<?php echo $tracknum ?>" onchange="<?php echo "document.getElementById('song_" . $song->id . "').checked='checked';"; ?>" /> + </td> + <?php + $tab++; + } + ?> + <td> + <a href="<?php echo $web_path; ?>/song.php?action=m3u&song=<?php echo $song->id; ?>" title="<?php echo $song->title; ?>" <?php echo $text_class; ?>> <?php echo $song->f_title; ?> </a> + </td> + <td> + <a href="<?php echo $web_path; ?>/artists.php?action=show&artist=<?php echo $song->artist; ?>" title="<?php echo $song->f_artist_full; ?>" <?php echo $text_class; ?>> <?php echo $song->f_artist; ?> </a> + </td> + <td> + <a href="<?php echo $web_path; ?>/albums.php?album=<?php echo $song->album; ?>" title="<?php echo $song->f_album_full; ?>" <?php echo $text_class; ?>> <?php echo $song->f_album; ?> </a> + </td> + <td align="right"> + <?php echo $song->track; ?> + </td> + <td align="right"> + <?php echo $song->f_time; ?> + </td> + <td align="right" nowrap> + <?php echo $song->f_size; ?> MB + </td> + <td align="right"> + <?php echo $song->f_bitrate; ?> + </td> + <td> + <?php echo $song->f_genre; ?> + </td> + <td <?php echo $song->f_style; ?> title="<?php echo $song->flagcomment; ?>"> + <?php echo $song->flagtype; ?> + </td> + <td> + <?php if ($user->has_access('100')) { ?> + <a href="<?php echo $web_path; ?>/admin/song.php?action=edit&song=<?php echo $song->id; ?>">Edit</a> | <a href="<?php echo $web_path; ?>/flag.php?song=<?php echo $song->id; ?>&action=flag">Flag</a> | + <?php if ($song->status === 'enabled') { ?> + <a href="<?php echo $web_path; ?>/admin/song.php?action=disable&song_ids=<?php echo $song->id; ?>">Disable</a> + <?php } else { ?> + <a href="<?php echo $web_path; ?>/admin/song.php?action=enabled&song_ids=<?php echo $song->id; ?>">Enable</a> + <?php } //status ?> + <?php } else { ?> + <a href="<?php echo $web_path; ?>/flag.php?song=<?php echo $song->id; ?>&action=flag">Flag</a> + <?php } //access ?> + <?php if ($user->prefs['download']) { ?> + | <a href="<?php echo $web_path; ?>/download/index.php?action=download&song_id=<?php echo $song->id; ?>&fn=<?php echo rawurlencode($song->f_artist_full . " - " . $song->title . "." . $song->type); ?>"><?php echo _("Download"); ?></a> + <?php } ?> + <?php if ($user->prefs['direct_link']) { ?> + | <a href="<?php echo $web_path; ?>/play/index.php?song=<?php echo $song->id; ?>&uid=<?php echo $user->id . "&sid=" . session_id(); ?>&fn=<?php echo rawurlencode($song->f_artist_full . " - " . $song->title . "." . $song->type); ?>"> + <?php echo _("Direct Link"); ?> + <?php } ?> + </a> + </td> + </tr> + <? + }// foreach loop + + // + // Another here doc + // + $time = floor($totaltime/60) . ":" . sprintf("%02d", ($totaltime%60) ); + $megs = sprintf("%.2f", ($totalsize/1048576)); + $num = count($song_ids); + + ?> + <tr class="table-header"> + <td></td> + <?php if (isset($playlist_id) && $playlist_id != 0 && ($user->id == $pluser->id || $user->access === 'admin')) { ?> <td></td> <?php } ?> + <td><?php echo _("Total"); ?>:</td> + <td nowrap><?php echo $num; ?> song(s)</td> + <td></td> + <td></td> + <td align="right" nowrap><?php echo $time; ?></td> + <td align="right" nowrap><?php echo $megs; ?> MB</td> + <td></td> + <td></td> + <td></td> + <td colspan="2"></td> + </tr> + </table> +</td></tr> +</table> +<br /> +<?php show_play_selected(); ?> +</form> + diff --git a/templates/show_test.inc b/templates/show_test.inc new file mode 100644 index 00000000..6bafa1da --- /dev/null +++ b/templates/show_test.inc @@ -0,0 +1,271 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +$row_classes = array('even','odd'); +?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd"> +<html lang="en-US"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> +<title>Ampache -- Debug Page</title> +</head> +<body bgcolor="#f0f0f0"> +<h2><?php echo _("Ampache Debug"); ?></h2> +<p><?php echo _("You've reached this page because a configuration error has occured. Debug Information below"); ?></p> + +<table border="0" cellpadding="3" cellspacing="0"> +<tr> + <td><font size="+1"><?php echo _("CHECK"); ?></font></td> + <td> + <font size="+1"><?php echo _("STATUS"); ?></font> + </td> + <td><font size="+1"><?php echo _("DESCRIPTION"); ?></font></td> +</tr> +<tr> + <td valign="top"><?php echo _("PHP Version"); ?></td> + <td valign="top">[ + <? + if (!check_php_ver()) { + $status['php_ver'] = 'false'; + echo " <font color=\"red\">ERROR</font> "; + } + else { + $status['php_ver'] = 'true'; + echo " <font color=\"green\"> OK </font> "; + } + ?> + ] + </td> + <td> + <?php echo _("This tests to make sure that you are running a version of PHP that is known to work with Ampache."); ?> + </td> +</tr> +<tr> + <td valign="top"><?php echo _("Mysql for PHP"); ?></td> + <td valign="top">[ + <? + if (!check_php_mysql()) { + $status['mysql_php'] = 'false'; + echo " <font color=\"red\">ERROR</font> "; + } + else { + $status['mysql_php'] = 'true'; + echo " <font color=\"green\"> OK </font> "; + } + ?> + ] + </td> + <td> + <?php echo _("This test checks to see if you have the mysql extensions loaded for PHP. These are required for Ampache to work."); ?> + </td> +</tr> +<tr> + <td valign="top"><?php echo _("PHP Session Support"); ?></td> + <td valign="top">[ + <? + if (!check_php_session()) { + $status['session_php'] = 'false'; + echo " <font color=\"red\">ERROR</font> "; + } + else { + $status['session_php'] = 'true'; + echo " <font color=\"green\"> OK </font> "; + } + ?> + ] + </td> + <td> + <?php echo _("This test checks to make sure that you have PHP session support enabled. Sessions are required for Ampache to work."); ?> + </td> +</tr> +<tr> + <td valing="top"><?php echo _("PHP ICONV Support"); ?></td> + <td valing="top">[ + <? + if (!check_php_iconv()) { + $status['iconv_php'] = 'false'; + echo " <font color=\"red\">ERROR</font> "; + } + else { + $status['iconv_php'] = 'true'; + echo "<font color=\"green\"> OK </font> "; + } + ?>] + </td> + <td> + <?php echo _("This test checks to make sure you have Iconv support installed. Iconv support is not required for Ampache, but it is highly recommended"); ?> + </td> +</tr> +<tr> + <td valign="top"><?php echo _("Ampache.cfg.php Exists"); ?></td> + <td valign="top">[ + <? + if (!read_config_file($configfile)) { + $status['read_config'] = 'false'; + echo " <font color=\"red\">ERROR</font> "; + } + else { + $status['read_config'] = 'true'; + echo " <font color=\"green\"> OK   </font> "; + } + ?> + ] + </td> + <td width="350px"> + <?php echo _("This attempts to read /config/ampache.cfg.php If this fails either the ampache.cfg.php is not in the correct locations or + it is not currently readable by your webserver."); ?> + </td> +</tr> +<tr> + <td valign="top"> + <?php echo _("Ampache.cfg.php Configured?"); ?> + </td> + <td valign="top">[ + <? + $results = read_config($configfile, 0, 0); + if (!check_config_values($results)) { + $status['parse_config'] = 'false'; + echo " <font color=\"red\">ERROR</font> "; + } + else { + $status['parse_config'] = 'true'; + echo " <font color=\"green\"> OK </font> "; + } + ?> + ] + </td> + <td> + <?php echo _("This test makes sure that you have set all of the required config variables and that we are able to + compleatly parse your config file"); ?> + </td> +</tr> +<tr> + <td valign="top"> + <?php echo _("Ampache.cfg.php Up to Date?"); ?> + </td> + <td valign="top">[ + <?php + $difference = debug_compare_configs($configfile,$configfile . ".dist"); + if (count($difference)) { + $status['check_config_uptodate'] = 'false'; + echo " <font color=\"red\">ERROR</font> "; + } + else { + $status['check_config_uptodate'] = 'true'; + echo " <font color=\"green\"> OK </font> "; + } + ?> + ] + </td> + <td> + <?php if (count($difference)) { + if (!count($difference['conf'])) { $difference['conf'] = array(); } + if (!count($difference['libglue'])) { $difference['libglue'] = array(); } + echo _("Ampache.cfg.php is missing the following:"); + echo "<br />" . _("Under CONF") . "<br /><dl>\n"; + foreach ($difference['conf'] as $key=>$value) { + echo "\t<dd>$key = \"$value\"</dd>\n"; + } + echo "</dl>\n<br />" . _("Under LIBGLUE") . "<br /><dl>\n"; + foreach ($difference['libglue'] as $key=>$value) { + echo "\t<dd>$key = \"$value\"</dd>\n"; + } + echo "</dl><br />\n"; + } else { ?> + + <?php } ?> + </td> +</tr> +<tr> + <td valign="top"><?php echo _("DB Connection"); ?></td> + <td valign="top">[ + <? + $db = check_database($results['libglue']['local_host'], $results['libglue']['local_username'], $results['libglue']['local_pass'],$results['libglue']['local_db']); + if (!$db) { + $status['check_db'] = 'false'; + echo " <font color=\"red\">ERROR</font> "; + } + else { + $status['check_db'] = 'true'; + echo " <font color=\"green\"> OK </font> "; + } + ?> + ] + </td> + <td> + <?php echo _("This attempts to connect to your database using the values from your ampache.cfg.php"); ?> + </td> +</tr> +<tr> + <td valign="top">DB Inserted</td> + <td valign="top">[ + <? + $db_inserted = check_database_inserted($db,$results['libglue']['local_db']); + if (!$db_inserted) { + $status['check_db_insert'] = 'false'; + echo " <font color=\"red\">ERROR</font> "; + } + else { + $status['check_db_insert'] = 'true'; + echo " <font color=\"green\"> OK </font> "; + } + ?> + ] + </td> + <td> + This checks a few key tables to make sure that you have successfully inserted the ampache database and + that the user has access to the database + </td> +</tr> +<tr> + + <td valign="top">Web Path</td> + <td valign="top">[ + <? + /* + Check to see if this is Http or https + */ + if ($_SERVER['HTTPS'] == 'on') { + $http_type = "https://"; + } + else { + $http_type = "http://"; + } + $results['conf']['web_path'] = $http_type . $_SERVER['SERVER_NAME'] . ":" . $_SERVER['SERVER_PORT'] . $results['conf']['web_path']; + if ($status['parse_config']) { + echo "<img src=\"" . $results['conf']['web_path'] ."/images/ampache.gif\" width=\"80\" height=\"15\"/>"; + } + else { + $status['check_webpath'] = false; + echo "<font color=\"red\">ERROR</font>"; + } + + ?> + ] + </td> + <td> + This test makes sure that your web_path variable is set correctly and that we are able to get to the index page. If you do not see the ampache + logo here then your web_path is not set correctly. + </td> +</tr> + +</table> + + + diff --git a/templates/show_upload.inc b/templates/show_upload.inc new file mode 100644 index 00000000..94ec45bc --- /dev/null +++ b/templates/show_upload.inc @@ -0,0 +1,83 @@ +<?php
+/*
+
+ Copyright (c) 2004 Ampache.org
+ All rights reserved.
+
+ *Created by Lamar*
+
+ 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+?>
+<center>
+ <h3>
+ <?php echo _("Please Ensure All Files Are Tagged Correctly"); ?>!!<br>
+ </h3>
+ <p>
+ <?php echo _("Ampache relies on id3 tags to sort data. If your file is not tagged it may be deleted."); ?>
+ </p>
+
+<form action="<?php echo conf('web_path'); ?>/upload.php?" method="post" name="upload_form" enctype="multipart/form-data" id="upload_form">
+ <input type="hidden" name="MAX_FILE_SIZE" value="<?php echo _("max_upload_size"); ?>" />
+ <table class="tabledata" cellspacing="0" cellpadding="0" border="0" width="90%" align="center">
+ <tr align="center">
+ <td>
+ <input size=40 type="file" name="ul_path1" id="ul_path1">
+ </td>
+ </tr>
+ <tr align="center">
+ <td>
+ <input size=40 type="file" name="ul_path2" id="ul_path2">
+ </td>
+ </tr>
+ <tr align="center">
+ <td>
+ <input size=40 type="file" name="ul_path3" id="ul_path3">
+ </td>
+ </tr>
+ <tr align="center">
+ <td>
+ <input size=40 type="file" name="ul_path4" id="ul_path4">
+ </td>
+ </tr>
+ <tr align="center">
+ <td>
+ <input type="hidden" name="action" value="upload_now">
+ <input type="submit" value="<?php echo _("Upload"); ?>">
+ </td>
+ </tr>
+ </table>
+</form>
+
+<p>
+<table align="center" border = "0">
+ <tr>
+ <td>
+ Acceptable formats include:
+ <li>.mp3</li>
+ <li>.ogg</li>
+ <li>.wma</li>
+ <li>.flac</li>
+ </td>
+ </tr>
+</table>
+</p>
+<!--
+<p>
+ Minimum Accepted Bitrate is <?= $site->prefs['upload_bitrate'] ?> kbps
+</p>
+-->
+</center>
diff --git a/templates/show_user.inc.php b/templates/show_user.inc.php new file mode 100644 index 00000000..239543a4 --- /dev/null +++ b/templates/show_user.inc.php @@ -0,0 +1,93 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +?> +<br /><br /> +<div class="text-box"> +<form name="change_password" method="post" action="<?php echo conf('web_path'); ?>/user.php?action=update_user" enctype="multipart/form-data" > +<p class="header2">Changing User Information for <?php echo $this_user->fullname; ?></p> +<table> + +<tr> + <td> + <?php echo _("Name"); ?>: + </td> + <td> + <input type="text" name="fullname" size="30" value="<?php echo $this_user->fullname; ?>" /> + </td> + </tr> + +<tr> + <td> + <?php echo _("E-mail"); ?>: + </td> + <td> + <input type="text" name="email" size="30" value="<?php echo $this_user->email; ?>" /> + </td> +</tr> +<tr> + <td> + <?php echo _("View Limit"); ?>: + </td> + <td> + <input type="text" name="offset_limit" size="5" value="<?php echo $this_user->offset_limit; ?>" /> + </td> +</tr> +</table> + <input type="hidden" name="user_id" value="<?php echo $this_user->username; ?>" /> + <input type="submit" name="action" value="<?php echo _("Update Profile"); ?>" /> +</form> +</div> +<br /> +<div class="text-box"> +<form name="change_password" method="post" action="<?php echo conf('web_path'); ?>/user.php?action=change_password" enctype="multipart/form-data" > +<span class="header2">Changing User Password</span> +<?php $GLOBALS['error']->print_error('password'); ?> +<table border="0" cellpadding="5" cellspacing="0"> +<tr> + <td> + <?php echo _("Enter password"); ?>: + </td> + <td> + <input type="password" name="password" size="30" /> + </td> +</tr> +<tr> + <td> + <?php echo _("Confirm Password"); ?>: + </td> + <td> + <input type="password" name="confirm_password" size="30" /> + </td> +</tr> +</table> + <input type="hidden" name="user_id" value="<?php echo $this_user->username; ?>" /> + <input type="submit" name="action" value="<?php echo _("Change Password"); ?>" /> +</form> +</div> +<br /> +<div class="text-box"> +<form name="clear_statistics" method="post" action="<?php echo conf('web_path'); ?>/user.php?action=clear_stats" enctype="multipart/form-data"> +<span class="header2">Delete Your Personal Statistics</span><br /> +<input type="hidden" name="user_id" value="<?php echo $this_user->username; ?>" /> +<input type="submit" value="<?php echo _("Clear Stats"); ?>"> +</form> +</div> diff --git a/templates/show_user_registration.inc.php b/templates/show_user_registration.inc.php new file mode 100644 index 00000000..e3e9f3a4 --- /dev/null +++ b/templates/show_user_registration.inc.php @@ -0,0 +1,73 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +?> + +<form name="update_user" method="post" action="<?php echo conf('web_path'); ?>/register.php" enctype="multipart/form-data"> +<table class="tabledata" cellspacing="0" cellpadding="0" border="0" width="90%"> +<tr> + <td> + <?php echo _("Username"); ?>: + </td> + <td> + <input type="textbox" name="username" value="<?php echo $_REQUEST['username']; ?>" /> + </td> +</tr> +<tr> + <td> + <?php echo _("Full Name"); ?>: + </td> + <td> + <input type="textbox" name="fullname" size="30" value="<?php echo $_REQUEST['fullname']; ?>" /> + </td> +</tr> +<tr> + <td> + <?php echo _("E-mail"); ?>: + </td> + <td> + <input type="textbox" name="email" size="30" value="<?php echo $_REQUEST['email']; ?>" /> + </td> +</tr> +<tr> + <td> + <?php echo _("Password"); ?> : + </td> + <td> + <input type="password" name="password_1" size="30" value="" /> + </td> +</tr> +<tr> + <td> + <?php echo _("Confirm Password"); ?>: + </td> + <td> + <input type="password" name="password_2" size="30" value="" /> + </td> +</tr> +<tr> + <td colspan="2"> + <input type="hidden" name="action" value="add_user" /> + <input type="submit" value="<?php echo _("Register User"); ?>" /> + </td> +</tr> +</table> +</form> diff --git a/templates/show_users.inc b/templates/show_users.inc new file mode 100644 index 00000000..3822947d --- /dev/null +++ b/templates/show_users.inc @@ -0,0 +1,130 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @header Show Users (admin section) + +*/ +$total_items = $view->total_items; +$admin_menu = "admin/"; +?> + + +<table class="tabledata" cellpadding="0" cellspacing="0" border="0"> +<tr class="even" align="center"> + <td colspan="8"> + <?php if ($view->offset_limit) { require (conf('prefix') . "/templates/list_header.inc"); } ?> + </td> +</tr> +<tr class="table-header"> + <td align="center"> + <a href="<?php echo conf('web_path'); ?>/<?php echo $_SESSION['view_script']; ?>?action=<?php echo $_REQUEST['action']; ?>&keep_view=true&sort_type=username&sort_order=0"> + <b><?php echo _("Username"); ?></b> + </a> + <a href="<?php echo conf('web_path'); ?>/<?php echo $_SESSION['view_script']; ?>?action=<?php echo $_REQUEST['action']; ?>&keep_view=true&sort_type=fullname&sort_order=0" + <b>(<?php echo _("Fullname"); ?>)</b> + </a> + </td> + <td align="center"> + <a href="<?php echo conf('web_path'); ?>/<?php echo $_SESSION['view_script']; ?>?action=<?php echo $_REQUEST['action']; ?>&keep_view=true&sort_type=last_seen&sort_order=0"> + <b><?php echo _("Last Seen"); ?></b> + </a> + </td> + <td align="center"> + <b><?php echo _("Edit"); ?></b> + </td> + <td align="center"> + <b><?php echo _("Prefs"); ?></b> + </td> + <td align="center"> + <b><?php echo _("Delete"); ?></b> + </td> + <td align="center"> + <b><?php echo _("Set Access"); ?></b> + </td> + <td align="center"> + <b><?php echo _("Disable"); ?></b> + </td> + <td align="center"> + <b><?php echo _("On-line"); ?></b> + </td> +</tr> +<?php + +while ( $results = mysql_fetch_object($db_result) ) { + $user = new User($results->username); + $last_seen = date("m\/d\/Y - H:i",$user->last_seen); + if (!$user->last_seen) { $last_seen = "Never"; } + +?> +<tr class="even"> + <td> + <a href="<?php echo conf('web_path'); ?>/admin/users.php?action=edit&user=<?php echo $user->username; ?>"> + <?php echo $user->fullname; ?> (<?php echo $user->username; ?>) + </a> + </td> + <td align="center"> + <?php echo $last_seen; ?> + </td> + <td> + <a href="<?php echo conf('web_path'); ?>/admin/users.php?action=edit&user=<?php echo $user->username; ?>"> + <?php echo _("edit"); ?> + </a> + </td> + <td> + <a href="<?php echo conf('web_path'); ?>/admin/preferences.php?action=user&user_id=<?php echo $user->id; ?>"> + <?php echo _("prefs"); ?> + </a> + </td> + <td> + <a href="<?php echo conf('web_path'); ?>/admin/users.php?action=delete&user=<?php echo $user->username; ?>"> + <?php echo _("delete"); ?> + </a> + </td> + <?php + //FIXME: Fix this for the extra permission levels + if ($user->access == 'admin') { + echo "<td><a href=\"".conf('web_path')."/admin/users.php?action=update&user=$user->username&level=user\">" . _("set to user") . "</a></td>"; + echo "<td><a href=\"".conf('web_path')."/admin/users.php?action=update&user=$user->username&level=disabled\">" . _("disable") . "</a></td>"; + } + elseif ($user->access == 'user') { + echo "<td><a href=\"".conf('web_path')."/admin/users.php?action=update&user=$user->username&level=admin\">" . _("set to admin") . "</a></td>"; + echo "<td><a href=\"".conf('web_path')."/admin/users.php?action=update&user=$user->username&level=disabled\">" . _("disable") . "</a></td>"; + } + elseif ($user->access == 'disabled') { + echo "<td><a href=\"".conf('web_path')."/admin/users.php?action=update&user=$user->username&level=admin\">" . _("set to admin") . "</a></td>"; + echo "<td><a href=\"".conf('web_path')."/admin/users.php?action=update&user=$user->username&level=user\">" . _("set to user") . "</a></td>"; + } + elseif ($user->access == '1') { + echo "<td><a href=\"".conf('web_path')."/admin/users.php?action=update&user=$user->username&level=user\">" . _("set to user") . "</a></td>"; + echo "<td><a href=\"".conf('web_path')."/admin/users.php?action=update&user=$user->username&level=disabled\">" . _("disable") ."</a></td>"; + } + if ( $user->is_logged_in() and $user->is_online() ) { + echo "<td bgcolor=\"green\"> </td>"; + } + else { + echo "<td bgcolor=\"red\"> </td>"; + } +} // end while +?> + </td> +</tr> +</table> diff --git a/templates/song_edit.inc b/templates/song_edit.inc new file mode 100644 index 00000000..c4eab4a7 --- /dev/null +++ b/templates/song_edit.inc @@ -0,0 +1,86 @@ +<?php +/* + + Copyright (c) 2004 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +preg_match("/^.*\/(.*?)$/",$song->file, $short); +$filename = htmlspecialchars($short[1]); +$target = conf('web_path').'/admin/flags.php'; +?> + +<form name="update_song" method="post" action="<?= $target; ?>"> +<table class="tabledata" cellspacing="0" cellpadding="0" border="0"> + <tr> + <td>File:</td> + <td colspan="2"><?= $filename; ?></td> + </tr> + <tr> + <td>Title:</td> + <td><input type="text" name="title" size="60" value="<?= $song->title; ?>"></td> + </tr> + <tr> + <td>Artist:</td> + <td> +<?php show_artist_pulldown($song->artist); ?> + </td> + <td>or <input type="text" name="new_artist" size="30" value=""></td> + </tr> + + <tr> + <td>Album:</td> + <td> +<?php show_album_pulldown($song->album); ?> + </td> + <td>or <input type="text" name="new_album" size="30" value=""></td> + </tr> + <tr> + <td>Track:</td> + <td><input type="text" size="4" maxlength="4" name="track" value="<?=$song->track?>"></input></td> + </tr> + <tr> + <td>Genre:</td> + <td> +<?php show_genre_pulldown($song->genre, 1); ?> + <tr> + <td>Year</td> + <td><input type="text" size="4" maxlength="4" name="year" value="<?=$song->year?>"></input></td> + </tr> + + <tr> + <td> </td> + <td><input type="checkbox" name="update_id3" value="yes" checked="checked"> Update id3 tags</input></td> + <td> </td> + </tr> + <tr> + <td> </td> + <td> <input type=hidden name="song" value="<?=$song->id?>"> + <input type=hidden name="flag" value="<?=$flagid?>"> + <input type=hidden name="current_artist_id" value="<?=$song->artist?>"> + +<?php if(count($_SESSION['edit_queue'])){ ?> + <input type=submit name="action" value="Next"></input> + <input type=submit name="action" value="Skip"></input> + <input type="submit" name="action" value="Clear Edit List"></input></td> +<?php } else { ?> + <input type=submit name="action" value="Done"> </td> +<?php } ?> + </tr> +</table> +</form> + diff --git a/templates/style.inc b/templates/style.inc new file mode 100644 index 00000000..e6810f0e --- /dev/null +++ b/templates/style.inc @@ -0,0 +1,256 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Style File + @discussion this is the css that is included on every page of + ampache, mod this to change the look and feel of the site +*/ +?> +<style type="text/css"> +<!-- + body + { + padding-top: 5px; + background: <?php echo conf('bg_color1'); ?>; + font-family: <?php echo conf('font') ?>; + font-size: <?php echo conf('font_size'); ?>px; + color: <?php echo conf('font_color2'); ?>; + } + + p + { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font')?>; + font-size: <?php echo conf('font_size'); ?>px; + } + a + { + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + } + a:visited + { + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + } + a:active + { + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + } + .text-box + { + padding-left:5px; + padding-top:5px; + margin-bottom:10px; + background-color: <?php echo conf('base_color1'); ?>; + border-right:2px solid <?php echo conf('bg_color2'); ?>; + border-bottom:2px solid <?php echo conf('bg_color2'); ?>; + border-left:2px solid <?php echo conf('bg_color2'); ?>; + border-top:2px solid <?php echo conf('bg_color2'); ?>; + } + table.tabledata + { + } + + td + { + padding: 0px 8px 0px 8px; + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + font-size: <?php echo conf('font_size'); ?>px; + } + th + { + padding-right: 10px; + } + input + { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font'); ?>; + font-size: <?php echo conf('font_size'); ?>px; + font-weight: bold; + background-color: <?php echo conf('base_color2') ?>; + margin: 2px 2px 2px 2px; + } + select { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font')?>; + font-size: <?php echo conf('font_size'); ?>px; + background-color: <?php echo conf('base_color2'); ?>; + } + textarea + { + background-color: <?php echo conf('base_color2'); ?>; + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + font-size: <?php echo conf('font_size'); ?>px; + } + .table-header + { + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2')?> repeat-x; + vertical-align: top; + } +/*************** Main Menu *****************/ + #mainmenu { + float: left; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + border-top: 1px solid #000; + border-bottom: 1px solid #000; + border-right: 1px solid #000; + border-left: 1px solid #000; + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2');?> repeat-x; + } + #mainmenu li { + float: left; + margin: 0; + padding: 0 10px 0 10px; + border-right: 1px solid #000; + display: inline; + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2');?> repeat-x; + } + #mainmenu li.active { + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-dark-bg.gif) <?php echo conf('base_color2');?> repeat-x; + } + #mainmenu a { + text-decoration: none; + } + #mainmenu a:hover { + color: #000; + } + #mainmenu a:active { + color: #00a; + } +/*************** END Main Menu *************/ +/*************** Admin Menu *************/ + #adminmenu { + float: left; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + border-bottom: 1px solid #000; + border-right: 1px solid #000; + border-left: 1px solid #000; + border-top: 1px solid #000; + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2')?> repeat; + } + #adminmenu li { + float: left; + margin: 0; + padding: 0 20px 0 20px; + border-right: 1px solid #000; + display: inline; + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2');?> repeat-x; + } + #adminmenu li.active { + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-dark-bg.gif) <?php echo conf('base_color2');?> repeat-x; + } + #adminmenu a { + text-decoration: none; + } + #adminmenu a:hover { + color: #000; + } + #adminmenu a:active { + color: #000; + } +/*************** END Main Menu *************/ +/*************** Page Header *********************/ + #pageheader { + background: <?php echo conf('bg_color1');?>; + } +/*************** END Page Header *****************/ + .header1 + { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font'); ?>; + font-size: <?php echo conf('font_size') + 6; ?>px; + font-weight: 900; + } + .header2 + { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font'); ?>; + font-size: <?php echo conf('font_size') + 2; ?>px; + font-weight: 900; + } + .headrow + { + background:<?php echo conf('row_color1'); ?>; + font-size: <?php echo conf('font_size'); ?>px; + } + .odd + { + background:<?php echo conf('row_color2'); ?>; + font-size: <?php echo conf('font_size'); ?>px; + } + .even + { + background:<?php echo conf('row_color3'); ?>; + font-size: <?php echo conf('font_size'); ?>px; + } + .blank + { + background: #fff; + } + .border + { + background:<?php echo conf('bg_color2'); ?>; + } + .header + { + font-size: <?php echo conf('font_size'); ?>px; + } + .error + { + color: <?php echo conf('error_color'); ?>; + } + .fatalerror + { + padding-top: 3px; + padding-bottom: 3px; + color: <?php echo conf('error_color'); ?>; + border-right:4px solid <?php echo conf('error_color'); ?>; + border-bottom:4px solid <?php echo conf('error_color'); ?>; + border-left:4px solid <?php echo conf('error_color'); ?>; + border-top:4px solid <?php echo conf('error_color'); ?>; + font-size: <?php echo conf('font_size')+2; ?>px; + font-weight: 900; + text-align: center; + } + .disabled + { + text-decoration: line-through; + } + .alphabet + { + font-size: <?php echo conf('font_size'); ?>px; + font-weight: normal; + } +--> +</style> + diff --git a/templates/tool_box.inc b/templates/tool_box.inc new file mode 100644 index 00000000..a81e6b15 --- /dev/null +++ b/templates/tool_box.inc @@ -0,0 +1,43 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +/*! + @header + A template file + +*/ + +?> + +<table class="border" cellspacing=1 cellpadding=3> + <tr class="table-header" align=center> + <td><?php echo $title; ?></td> + </tr> + <tr> + <td class="even"> + <? + foreach (array_keys($items) as $item) { + print("\t\t\t>> <a href=\"$items[$item]\"> $item</a><br />\n"); + } + ?> + </td> + </tr> +</table> diff --git a/templates/userform.inc b/templates/userform.inc new file mode 100644 index 00000000..3e487355 --- /dev/null +++ b/templates/userform.inc @@ -0,0 +1,108 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +if ($type === 'new_user') { + $userfield = "<input type=\"text\" name=\"new_username\" size=\"30\" value=\"$username\" />"; + $title = _("Adding a New User"); +} +else { + $userfield = "$username"; + $title = _("Editing existing User"); +} +?> + +<br /> +<div class="header2"><?php echo $title; ?></div> +<?php $GLOBALS['error']->print_error('general'); ?> +<form name="update_user" method="post" action="<?php echo conf('web_path') . "/admin/users.php"; ?>"> +<table class="text-box" cellspacing="0" cellpadding="0" border="0"> + +<tr> + <td> + <?php echo _("Username"); ; ?>: + </td> + <td> + <?php echo $userfield; ?> + <?php $GLOBALS['error']->print_error('username'); ?> + </td> +</tr> +<tr> + <td><?php echo _("Full Name"); ; ?>:</td> + <td> + <input type="text" name="new_fullname" size="30" value="<?php echo $fullname; ?>" /> + </td> +</tr> +<tr> + <td> + <?php echo _("E-mail"); ?>: + </td> + <td> + <input type="text" name="new_email" size="30" value="<?php echo $email; ?>" /> + </td> +</tr> +<tr> + <td> + <?php echo _("Password"); ?> : + </td> + <td> + <input type="password" name="new_password_1" size="30" value="" /> + <?php $GLOBALS['error']->print_error('password'); ?> + </td> +</tr> +<tr> + <td> + <?php echo _("Confirm Password"); ?>: + </td> + <td> + <input type="password" name="new_password_2" size="30" value="" /> + </td> +</tr> +<tr> + <td> + <?php echo _("User Access Level"); ; ?>: + </td> + <td> + <select name="user_access"> + <option value="1" <?php if($access==='1') echo "selected=\"selected\""; ?>>Guest</option> + <option value="user" <?php if($access==='user') echo "selected=\"selected\""; ?>>User</option> + <option value="admin" <?php if($access==='admin') echo "selected=\"selected\""; ?>>Admin</option> + <option value="disabled" <?php if($access==='diabled') echo "selected=\"selected\""; ?>>Disabled</option> + </select> + </td> +</tr> +</table> +<?php + + if ( $type == 'new_user' ) + { + echo "<input type=\"hidden\" name=\"action\" value=\"add_user\" />"; + echo "<input type=\"submit\" value=\"" . _("Add User") . "\" />"; + } + else + { + echo "<input type=\"hidden\" name=\"action\" value=\"update_user\" />\n"; + echo "<input type=\"submit\" value=\"" . _("Update User") . "\" />\n"; + echo "<input type=\"hidden\" name=\"new_username\" value=\"$username\" />"; + } + +?> +</form> diff --git a/test.php b/test.php new file mode 100644 index 00000000..7607b258 --- /dev/null +++ b/test.php @@ -0,0 +1,45 @@ +<?php +/* + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ +// Set the Error level manualy... I'm to lazy to fix notices +error_reporting(E_ALL ^ E_NOTICE); + + +$prefix = dirname(__FILE__); +$configfile = "$prefix/config/ampache.cfg.php"; + + +require_once($prefix . "/lib/general.php"); +require_once($prefix . "/lib/ui.php"); +require_once($prefix . "/lib/debug.php"); + +switch ($_REQUEST['action']) { + + case 'verify_config': + // This reads the ampache.cfg and compares the potential options against + // those in ampache.cfg.dst + show_compare_config($prefix); + break; + default: + require_once($prefix . "/templates/show_test.inc"); + break; +} // end switch on action + + + +?> diff --git a/themes/burgundy/images/ampache-dark-bg.gif b/themes/burgundy/images/ampache-dark-bg.gif Binary files differnew file mode 100755 index 00000000..8d2df723 --- /dev/null +++ b/themes/burgundy/images/ampache-dark-bg.gif diff --git a/themes/burgundy/images/ampache-light-bg.gif b/themes/burgundy/images/ampache-light-bg.gif Binary files differnew file mode 100755 index 00000000..8d2df723 --- /dev/null +++ b/themes/burgundy/images/ampache-light-bg.gif diff --git a/themes/burgundy/images/ampache-mid.gif b/themes/burgundy/images/ampache-mid.gif Binary files differnew file mode 100755 index 00000000..0175d1ed --- /dev/null +++ b/themes/burgundy/images/ampache-mid.gif diff --git a/themes/burgundy/images/ampache.gif b/themes/burgundy/images/ampache.gif Binary files differnew file mode 100755 index 00000000..2f6dbf43 --- /dev/null +++ b/themes/burgundy/images/ampache.gif diff --git a/themes/burgundy/images/blank-pixel.gif b/themes/burgundy/images/blank-pixel.gif Binary files differnew file mode 100644 index 00000000..17d43908 --- /dev/null +++ b/themes/burgundy/images/blank-pixel.gif diff --git a/themes/burgundy/images/blankalbum.gif b/themes/burgundy/images/blankalbum.gif Binary files differnew file mode 100644 index 00000000..a1d25b40 --- /dev/null +++ b/themes/burgundy/images/blankalbum.gif diff --git a/themes/burgundy/images/blankalbum.jpg b/themes/burgundy/images/blankalbum.jpg Binary files differnew file mode 100644 index 00000000..468301bd --- /dev/null +++ b/themes/burgundy/images/blankalbum.jpg diff --git a/themes/burgundy/images/headphone.gif b/themes/burgundy/images/headphone.gif Binary files differnew file mode 100755 index 00000000..74a66e11 --- /dev/null +++ b/themes/burgundy/images/headphone.gif diff --git a/themes/burgundy/images/table.gif b/themes/burgundy/images/table.gif Binary files differnew file mode 100755 index 00000000..89761b38 --- /dev/null +++ b/themes/burgundy/images/table.gif diff --git a/themes/burgundy/templates/style.inc b/themes/burgundy/templates/style.inc new file mode 100644 index 00000000..e0d928e8 --- /dev/null +++ b/themes/burgundy/templates/style.inc @@ -0,0 +1,271 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Style File + @discussion this is the css that is included on every page of + ampache, mod this to change the look and feel of the site +*/ +?> +<style type="text/css"> +<!-- + body + { + background: <?php echo conf('bg_color1'); ?>; + font-family: <?php echo conf('font') ?>; + font-size: <?php echo conf('font_size'); ?>px; + color: <?php echo conf('font_color2'); ?>; + } + + p + { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font')?>; + font-size: <?php echo conf('font_size'); ?>px; + } + a + { + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + } + a:visited + { + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + } + a:active + { + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + } + .text-box + { + padding-left:5px; + padding-top:5px; + margin-bottom:10px; + background-color: <?php echo conf('base_color1'); ?>; + border-right:2px solid <?php echo conf('bg_color2'); ?>; + border-bottom:2px solid <?php echo conf('bg_color2'); ?>; + border-left:2px solid <?php echo conf('bg_color2'); ?>; + border-top:2px solid <?php echo conf('bg_color2'); ?>; + } + table.tabledata + { + } + + td + { + padding: 0px 8px 0px 8px; + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + font-size: <?php echo conf('font_size'); ?>px; + } + th + { + padding-right: 10px; + } + input + { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font'); ?>; + font-size: <?php echo conf('font_size'); ?>px; + font-weight: bold; + background-color: <?php echo conf('base_color2') ?>; + border-style: solid; + border-width: 1px; + border-color: <?php echo conf('bg_color2'); ?>; + margin: 2px 2px 2px 2px; + } + select { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font')?>; + font-size: <?php echo conf('font_size'); ?>px; + background-color: <?php echo conf('base_color2') ?>; + } + textarea + { + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + font-size: <?php echo conf('font_size'); ?>px; + } + .table-header + { + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2')?> repeat-x; + vertical-align: top; + } +/*************** Main Menu *****************/ + #mainmenu { + float: left; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + border-top: 0px solid #000; + border-bottom: 0px solid #000; + border-right: 0px solid #000; + border-left: 0px solid #000; + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2');?> repeat-x; +peat-x; + } + #mainmenu li { + float: left; + margin: 0; + padding: 0 10px 0 10px; + border-right: 0px solid #000; + display: inline; + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2');?> repeat-x; +peat-x; + } + #mainmenu li.active { + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-dark-bg.gif) <?php echo conf('base_color2');?> rep +eat-x; + } + #mainmenu a { + text-decoration: none; + } + #mainmenu a:hover { + color: #000; + } + #mainmenu a:active { + color: #00a; + } +/*************** END Main Menu *************/ +/*************** Admin Menu *************/ + #adminmenu { + float: left; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + border-bottom: 0px solid #000; + border-right: 0px solid #000; + border-left: 0px solid #000; + border-top: 0px solid #000; + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2')?> repeat; + } + #adminmenu li { + float: left; + margin: 0; + padding: 0 20px 0 20px; + border-right: 0px solid #000; + display: inline; + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2');?> repeat-x; + } + #adminmenu li.active { + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-dark-bg.gif) <?php echo conf('base_color2');?> repeat-x; + } + #adminmenu a { + text-decoration: none; + } + #adminmenu a:hover { + color: #000; + } + #adminmenu a:active { + color: #000; + } +/*************** END Main Menu *************/ +/*************** Page Header *********************/ + #pageheader { + background: <?php echo conf('bg_color1');?>; + } +/*************** END Page Header *****************/ + .navitem + { + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2')?> repeat-x; + vertical-align: top; + text-align: center; + } + .header1 + { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font'); ?>; + font-size: <?php echo conf('font_size') + 6; ?>px; + font-weight: 900; + } + .header2 + { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font'); ?>; + font-size: <?php echo conf('font_size') + 2; ?>px; + font-weight: 900; + } + .active_navitem + { + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-dark-bg.gif) <?php echo conf('bg_color1')?> repeat-x; + vertical-align: top; + text-align: center; + } + .headrow + { + background:<?php echo conf('row_color1'); ?>; + font-size: <?php echo conf('font_size'); ?>px; + } + .odd + { + background:<?php echo conf('row_color2'); ?>; + font-size: <?php echo conf('font_size'); ?>px; + } + .even + { + background:<?php echo conf('row_color3'); ?>; + font-size: <?php echo conf('font_size'); ?>px; + } + .blank + { + background: #fff; + } + .border + { + background:<?php echo conf('bg_color2'); ?>; + } + .header + { + font-size: <?php echo conf('font_size'); ?>px; + } + .error + { + color: <?php echo conf('error_color'); ?>; + } + .fatalerror + { + padding-top: 3px; + padding-bottom: 3px; + color: <?php echo conf('error_color'); ?>; + border-right:4px solid <?php echo conf('error_color'); ?>; + border-bottom:4px solid <?php echo conf('error_color'); ?>; + border-left:4px solid <?php echo conf('error_color'); ?>; + border-top:4px solid <?php echo conf('error_color'); ?>; + font-size: <?php echo conf('font_size')+2; ?>px; + font-weight: 900; + text-align: center; + } + .disabled + { + text-decoration: line-through; + } + .alphabet + { + font-size: <?php echo conf('font_size'); ?>px; + font-weight: normal; + } +--> +</style> diff --git a/themes/burgundy/theme.cfg.php b/themes/burgundy/theme.cfg.php new file mode 100644 index 00000000..f6ca16b7 --- /dev/null +++ b/themes/burgundy/theme.cfg.php @@ -0,0 +1,68 @@ +################## +#<?php exit(); ?># +################## + +########################### +# Burgundy Ampache Theme +########################### + +# Theme Name +# This is the actual name of the theme that +# will be displayed in the preferences screen +# DEFAULT: ampache-theme +name = "Burgundy" + +# Theme Author +# This is just a way of giving credit to the +# person who actually created this theme +# DEFAULT: N/A +author = "S1amson" + +# Theme Maintainer +# This is just a way of listing who is responsible for +# maintaining this theme incase it's not working right +# please include an e-mail address so you can be contacted +# DEFAULT: N/A +#maintainer = "Ben Shields <foo@ampache.org>" + +# Theme Colors +################### +[color] +################### +# Below is a list of the default colors for this theme, upon +# applying this theme the users color preferences will be reset +# to what is listed below... + +# Background Color 1 +bg_color1 = "#320000" + +# Background Color 2 +bg_color2 = "#7A0000" + +# Base Color 1 +base_color1 = "#320000" + +# Base Color 2 +base_color2 = "#9F0000" + +# Font Color 1 +font_color1 = "#222222" + +# Font Color 2 +font_color2 = "#ffffcc" + +# Font Color 3 +font_color3 = "#ffffff" + +# Row Color 1 +row_color1 = "#ffffcc" + +# Row Color 2 +row_color2 = "#9F0000" + +# Row Color 3 +row_color3 = "#7A0000" + +# Error Color +error_color = "#ffffcc" + diff --git a/themes/classic/images/ampache-dark-bg.gif b/themes/classic/images/ampache-dark-bg.gif Binary files differnew file mode 100755 index 00000000..e9fc72c9 --- /dev/null +++ b/themes/classic/images/ampache-dark-bg.gif diff --git a/themes/classic/images/ampache-light-bg.gif b/themes/classic/images/ampache-light-bg.gif Binary files differnew file mode 100755 index 00000000..262430b8 --- /dev/null +++ b/themes/classic/images/ampache-light-bg.gif diff --git a/themes/classic/images/ampache-mid.gif b/themes/classic/images/ampache-mid.gif Binary files differnew file mode 100755 index 00000000..57376ea4 --- /dev/null +++ b/themes/classic/images/ampache-mid.gif diff --git a/themes/classic/images/ampache.gif b/themes/classic/images/ampache.gif Binary files differnew file mode 100755 index 00000000..fb110191 --- /dev/null +++ b/themes/classic/images/ampache.gif diff --git a/themes/classic/images/blank-pixel.gif b/themes/classic/images/blank-pixel.gif Binary files differnew file mode 100644 index 00000000..17d43908 --- /dev/null +++ b/themes/classic/images/blank-pixel.gif diff --git a/themes/classic/images/blankalbum.gif b/themes/classic/images/blankalbum.gif Binary files differnew file mode 100644 index 00000000..a1d25b40 --- /dev/null +++ b/themes/classic/images/blankalbum.gif diff --git a/themes/classic/images/blankalbum.jpg b/themes/classic/images/blankalbum.jpg Binary files differnew file mode 100644 index 00000000..468301bd --- /dev/null +++ b/themes/classic/images/blankalbum.jpg diff --git a/themes/classic/images/headphone.gif b/themes/classic/images/headphone.gif Binary files differnew file mode 100755 index 00000000..74a66e11 --- /dev/null +++ b/themes/classic/images/headphone.gif diff --git a/themes/classic/images/table.gif b/themes/classic/images/table.gif Binary files differnew file mode 100755 index 00000000..89761b38 --- /dev/null +++ b/themes/classic/images/table.gif diff --git a/themes/classic/theme.cfg.php b/themes/classic/theme.cfg.php new file mode 100644 index 00000000..123f99d7 --- /dev/null +++ b/themes/classic/theme.cfg.php @@ -0,0 +1,68 @@ +################## +#<?php exit(); ?># +################## + +########################### +# Classic Ampache Theme +########################### + +# Theme Name +# This is the actual name of the theme that +# will be displayed in the preferences screen +# DEFAULT: ampache-theme +name = "Classic Ampache" + +# Theme Author +# This is just a way of giving credit to the +# person who actually created this theme +# DEFAULT: N/A +#author = "Ben Shields" + +# Theme Maintainer +# This is just a way of listing who is responsible for +# maintaining this theme incase it's not working right +# please include an e-mail address so you can be contacted +# DEFAULT: N/A +#maintainer = "Ben Shields <foo@ampache.org>" + +# Theme Colors +################### +[color] +################### +# Below is a list of the default colors for this theme, upon +# applying this theme the users color preferences will be reset +# to what is listed below... + +# Background Color 1 +bg_color1 = "#ffffff" + +# Background Color 2 +bg_color2 = "#000000" + +# Base Color 1 +base_color1 = "#bbbbbb" + +# Base Color 2 +base_color2 = "#dddddd" + +# Font Color 1 +font_color1 = "#222222" + +# Font Color 2 +font_color2 = "#000000" + +# Font Color 3 +font_color3 = "#ffffff" + +# Row Color 1 +row_color1 = "#cccccc" + +# Row Color 2 +row_color2 = "#bbbbbb" + +# Row Color 3 +row_color3 = "#dddddd" + +# Error Color +error_color = "#990033" + diff --git a/themes/greyblock/images/ampache-dark-bg.gif b/themes/greyblock/images/ampache-dark-bg.gif Binary files differnew file mode 100644 index 00000000..648a87c3 --- /dev/null +++ b/themes/greyblock/images/ampache-dark-bg.gif diff --git a/themes/greyblock/images/ampache-light-bg.gif b/themes/greyblock/images/ampache-light-bg.gif Binary files differnew file mode 100644 index 00000000..d74a2db7 --- /dev/null +++ b/themes/greyblock/images/ampache-light-bg.gif diff --git a/themes/greyblock/images/ampache-mid.gif b/themes/greyblock/images/ampache-mid.gif Binary files differnew file mode 100644 index 00000000..2a986d25 --- /dev/null +++ b/themes/greyblock/images/ampache-mid.gif diff --git a/themes/greyblock/images/ampache.gif b/themes/greyblock/images/ampache.gif Binary files differnew file mode 100644 index 00000000..3e54f9ce --- /dev/null +++ b/themes/greyblock/images/ampache.gif diff --git a/themes/greyblock/images/blank-pixel.gif b/themes/greyblock/images/blank-pixel.gif Binary files differnew file mode 100644 index 00000000..17d43908 --- /dev/null +++ b/themes/greyblock/images/blank-pixel.gif diff --git a/themes/greyblock/images/blankalbum.gif b/themes/greyblock/images/blankalbum.gif Binary files differnew file mode 100644 index 00000000..a1d25b40 --- /dev/null +++ b/themes/greyblock/images/blankalbum.gif diff --git a/themes/greyblock/images/blankalbum.jpg b/themes/greyblock/images/blankalbum.jpg Binary files differnew file mode 100644 index 00000000..f3a49a2d --- /dev/null +++ b/themes/greyblock/images/blankalbum.jpg diff --git a/themes/greyblock/images/headphone.gif b/themes/greyblock/images/headphone.gif Binary files differnew file mode 100644 index 00000000..74a66e11 --- /dev/null +++ b/themes/greyblock/images/headphone.gif diff --git a/themes/greyblock/images/table.gif b/themes/greyblock/images/table.gif Binary files differnew file mode 100644 index 00000000..89761b38 --- /dev/null +++ b/themes/greyblock/images/table.gif diff --git a/themes/greyblock/templates/style.inc b/themes/greyblock/templates/style.inc new file mode 100644 index 00000000..6cc83d1f --- /dev/null +++ b/themes/greyblock/templates/style.inc @@ -0,0 +1,270 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +/*! + @header Style File + @discussion this is the css that is included on every page of + ampache, mod this to change the look and feel of the site +*/ +?> +<style type="text/css"> +<!-- + body + { + background: <?php echo conf('bg_color1'); ?>; + font-family: <?php echo conf('font') ?>; + font-size: <?php echo conf('font_size'); ?>px; + color: <?php echo conf('font_color2'); ?>; + } + + p + { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font')?>; + font-size: <?php echo conf('font_size'); ?>px; + } + a + { + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + } + a:visited + { + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + } + a:active + { + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + } + .text-box + { + padding-left:5px; + padding-top:5px; + margin-bottom:10px; + background-color: <?php echo conf('base_color1'); ?>; + border-right:2px solid <?php echo conf('bg_color2'); ?>; + border-bottom:2px solid <?php echo conf('bg_color2'); ?>; + border-left:2px solid <?php echo conf('bg_color2'); ?>; + border-top:2px solid <?php echo conf('bg_color2'); ?>; + } + table.tabledata + { + } + + td + { + padding: 0px 8px 0px 8px; + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + font-size: <?php echo conf('font_size'); ?>px; + } + th + { + padding-right: 10px; + } + input + { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font'); ?>; + font-size: <?php echo conf('font_size'); ?>px; + font-weight: bold; + background-color: <?php echo conf('base_color2') ?>; + border-style: solid; + border-width: 1px; + border-color: <?php echo conf('bg_color2'); ?>; + margin: 2px 2px 2px 2px; + } + select { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font')?>; + font-size: <?php echo conf('font_size'); ?>px; + background-color: <?php echo conf('base_color2') ?>; + } + textarea + { + color: <?php echo conf('font_color2') ?>; + font-family: <?php echo conf('font')?>; + font-size: <?php echo conf('font_size'); ?>px; + } + .table-header + { + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2')?> repeat-x; + vertical-align: top; + } +/*************** Main Menu *****************/ + #mainmenu { + float: left; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + border-top: 1px solid #000; + border-bottom: 1px solid #000; + border-right: 1px solid #000; + border-left: 1px solid #000; + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2');?> repeat-x; + } + #mainmenu li { + float: left; + margin: 0; + padding: 0 10px 0 10px; + border-right: 1px solid #000; + display: inline; + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2');?> repeat-x; +peat-x; + } + #mainmenu li.active { + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-dark-bg.gif) <?php echo conf('base_color2');?> rep +eat-x; + } + #mainmenu a { + text-decoration: none; + } + #mainmenu a:hover { + color: #000; + } + #mainmenu a:active { + color: #00a; + } +/*************** END Main Menu *************/ +/*************** Admin Menu *************/ + #adminmenu { + float: left; + width: 100%; + margin: 0; + padding: 0; + list-style: none; + border-bottom: 1px solid #000; + border-right: 1px solid #000; + border-left: 1px solid #000; + border-top: 1px solid #000; + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2')?> repeat; + } + #adminmenu li { + float: left; + margin: 0; + padding: 0 20px 0 20px; + border-right: 1px solid #000; + display: inline; + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2');?> repeat-x; + } + #adminmenu li.active { + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-dark-bg.gif) <?php echo conf('base_color2');?> repeat-x; + } + #adminmenu a { + text-decoration: none; + } + #adminmenu a:hover { + color: #000; + } + #adminmenu a:active { + color: #000; + } +/*************** END Main Menu *************/ +/*************** Page Header *********************/ + #pageheader { + background: <?php echo conf('bg_color1');?>; + } +/*************** END Page Header *****************/ + .navitem + { + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-light-bg.gif) <?php echo conf('base_color2')?> repeat-x; + vertical-align: top; + text-align: center; + } + .header1 + { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font'); ?>; + font-size: <?php echo conf('font_size') + 6; ?>px; + font-weight: 900; + } + .header2 + { + color: <?php echo conf('font_color2'); ?>; + font-family: <?php echo conf('font'); ?>; + font-size: <?php echo conf('font_size') + 2; ?>px; + font-weight: 900; + } + .active_navitem + { + background: url(<?php echo conf('web_path'); ?><?php echo conf('theme_path'); ?>/images/ampache-dark-bg.gif) <?php echo conf('bg_color1')?> repeat-x; + vertical-align: top; + text-align: center; + } + .headrow + { + background:<?php echo conf('row_color1'); ?>; + font-size: <?php echo conf('font_size'); ?>px; + } + .odd + { + background:<?php echo conf('row_color2'); ?>; + font-size: <?php echo conf('font_size'); ?>px; + } + .even + { + background:<?php echo conf('row_color3'); ?>; + font-size: <?php echo conf('font_size'); ?>px; + } + .blank + { + background: #fff; + } + .border + { + background:<?php echo conf('bg_color2'); ?>; + } + .header + { + font-size: <?php echo conf('font_size'); ?>px; + } + .error + { + color: <?php echo conf('error_color'); ?>; + } + .fatalerror + { + padding-top: 3px; + padding-bottom: 3px; + color: <?php echo conf('error_color'); ?>; + border-right:4px solid <?php echo conf('error_color'); ?>; + border-bottom:4px solid <?php echo conf('error_color'); ?>; + border-left:4px solid <?php echo conf('error_color'); ?>; + border-top:4px solid <?php echo conf('error_color'); ?>; + font-size: <?php echo conf('font_size')+2; ?>px; + font-weight: 900; + text-align: center; + } + .disabled + { + text-decoration: line-through; + } + .alphabet + { + font-size: <?php echo conf('font_size'); ?>px; + font-weight: normal; + } +--> +</style> diff --git a/themes/greyblock/theme.cfg.php b/themes/greyblock/theme.cfg.php new file mode 100644 index 00000000..b1515551 --- /dev/null +++ b/themes/greyblock/theme.cfg.php @@ -0,0 +1,68 @@ +################## +#<?php exit(); ?># +################## + +########################### +# Classic Ampache Theme +########################### + +# Theme Name +# This is the actual name of the theme that +# will be displayed in the preferences screen +# DEFAULT: ampache-theme +name = "Greyblock" + +# Theme Author +# This is just a way of giving credit to the +# person who actually created this theme +# DEFAULT: N/A +#author = "Ben Shields" + +# Theme Maintainer +# This is just a way of listing who is responsible for +# maintaining this theme incase it's not working right +# please include an e-mail address so you can be contacted +# DEFAULT: N/A +#maintainer = "Ben Shields <shieldb@ampache.org>" + +# Theme Colors +################### +[color] +################### +# Below is a list of the default colors for this theme, upon +# applying this theme the users color preferences will be reset +# to what is listed below... + +# Background Color 1 +bg_color1 = "#8B8B8B" + +# Background Color 2 +bg_color2 = "#000000" + +# Base Color 1 +base_color1 = "#C8C8C8" + +# Base Color 2 +base_color2 = "#D4D4D4" + +# Font Color 1 +font_color1 = "#C0C0C0" + +# Font Color 2 +font_color2 = "#292929" + +# Font Color 3 +font_color3 = "#F2F2F2" + +# Row Color 1 +row_color1 = "#CCCCCC" + +# Row Color 2 +row_color2 = "#7A7A7A" + +# Row Color 3 +row_color3 = "#A2A2A2" + +# Error Color +error_color = "#990033" + diff --git a/update.php b/update.php new file mode 100644 index 00000000..72b71e69 --- /dev/null +++ b/update.php @@ -0,0 +1,80 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All rights reserved. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +/*! + @header Update Document + This document handles updating from one version of Maintain to the next + if this doc is readable you can't login. stop.update and gone.fishing + must also be in place for this doc to work. + +*/ + +/* Start House Keeping */ + + // We need this stuff + $no_session = 1; + require("modules/init.php"); + + // Make a blank update object + $update = new Update(0); + + // Get the version and format it + $version = $update->get_version(); + + $conf['script'] = 'update.php'; + $prefs['font_size'] = 12; + $prefs['bg_color1'] = "#c0c0c0"; + $prefs['font'] = "Verdana"; + conf($prefs,1); + + +/* End House Keeping */ + +if ($_REQUEST['action'] == 'update') { + + /* Run the Update Mojo Here */ + $update->run_update(); + + /* Get the New Version */ + $version = $update->get_version(); + +} +?> +<html> +<head> +<title>Ampache - Update</title> +</head> +<body> +<?php show_template('style'); ?> +<p class="header1">Ampache - Update</p> +<p> +This page handles all database updates to Ampache starting with 3.2. According to your database your current version is: <?php echo $update->format_version($version); ; ?>. +the following updates need to be performed<br /><br /> +<?php $update->display_update(); ?> +</p> +<p> +<form method="post" enctype="multipart/form-data" action="<?php echo conf('web_path'); ; ?>/update.php?action=update"> +<?php if ($update->need_update()) { ?><input type="submit" value="Update Now!" /> <?php } ?> +</form> +</body> +</html> + + diff --git a/upload.php b/upload.php new file mode 100644 index 00000000..493880a6 --- /dev/null +++ b/upload.php @@ -0,0 +1,319 @@ +<?php
+/*
+
+ 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+/*
+
+ Copyright (c) 2003 Lamar
+ All rights reserved.
+
+ **Revised by Vollmerk**
+
+ upload.php script.
+ saves all uploaded files to the temp/ directory
+ then processes the files and moves them to the
+ proper directory.
+
+ There are two basic modes of operation. HTML
+ mode and GUI mode. If GUI mode is enabled a response
+ with header 200 will be sent to the GUI.
+
+*/
+
+require_once ("modules/init.php");
+
+
+
+// Set page header
+show_template('header');
+show_menu_items('Upload');
+
+// Access Control
+if(!$user->prefs['upload'] || conf('demo_mode')) {
+ access_denied();
+}
+
+/* Action Settings */
+$action = scrub_in($_REQUEST['action']);
+
+
+/*
+ FILE UPLOAD SECTION
+ This section handles file uploads. File types should
+ be declared in the $types hash. This will provide
+ an easy lookup mechanism.
+*/
+$types = array(
+ 'mp3'=>'music',
+ 'MP3'=>'music',
+ 'ogg'=>'music',
+ 'OGG'=>'music',
+ 'WMA'=>'music',
+ 'FLAC'=>'music',
+ 'flac'=>'music',
+ 'm4a' =>'music',
+ 'aac' =>'music',
+ '.gz'=>'compressed',
+ 'tar'=>'compressed',
+ 'zip'=>'compressed',
+ 'ZIP'=>'compressed',
+ );
+
+/* Upload Section Which Processes All Files Sent As Post */
+$audio_info = new Audioinfo();
+
+switch ($action) {
+ case 'upload_now':
+ // Verify the needed settings are in place
+ if (!@chdir($user->prefs['upload_dir']) || strlen($user->prefs['upload_dir']) < 1) {
+ break;
+ }
+
+ //FIXME: Set which catalog it goes into somewhere....
+ $sql = "SELECT * FROM catalog LIMIT 1";
+ $db_results = mysql_query($sql, dbh());
+
+ $results = mysql_fetch_object($db_results);
+
+ $catalog = new Catalog($results->id);
+
+ // Create arrays
+ $filelist = array();
+
+ foreach($_FILES as $tagname=>$file){
+ /* Skip blank file names */
+
+ if( strlen($file['name'] ) ){
+
+ // Determine tempfile name
+ $tempfile = $file['tmp_name'];
+
+ // Determine real file name
+ $realname = $user->prefs['upload_dir'] . "/" . $file['name'];
+
+ /* Determine Extension */
+ $ext = substr( $file['name'], -3 );
+
+ /* Prevent Unauthorized file types */
+ if( $types[$ext] == 'compressed' ){
+ // This section is currently disabled
+ }
+ elseif( $types[$ext] == 'music' ){
+ $error = @move_uploaded_file($tempfile, $realname );
+ if( $error )
+ {
+ $filelist = array( $realname => $file['name'] );
+ }
+ else{
+ switch ($file['error']) {
+ case '1':
+ $error_text = _("The uploaded file exceeds the upload_max_filesize directive in php.ini");
+ break;
+ case '2':
+ $error_text = _("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.");
+ break;
+ case '3':
+ $error_text = _("The uploaded file was only partially uploaded.");
+ break;
+ case '4':
+ $error_text = _("No file was uploaded.");
+ break;
+ default:
+ $error_text = _("An Unknown Error has occured.");
+ break;
+ } // end switch
+ if (conf('debug')) {
+ log_event($_SESSION['userdata']['username'],'upload',$error_text);
+ }
+ $message[$file['name']] .= "Error: $error_text";
+ $errorenum[$file['name']]=true;
+ }
+ } // end if known
+ // If unknown filetype
+ else{
+ $message[$file['name']] .= "Error: Unsupported File Type- $ext<br>";
+ $errorenum[$file['name']]=true;
+ }
+ // foreach through files uploaded
+ foreach( $filelist as $fullpath => $music ) {
+
+ // If we are quarantining the file
+ if ($user->prefs['quarantine']) {
+ // Log the upload but don't do anything
+ $message[$music] .= _("Successfully-Quarantined");
+ /* Log the upload */
+ $sql = "INSERT INTO upload (`user`,`file`,`addition_time`)" .
+ " VALUES ('$user->id','" . sql_escape($fullpath) . "','" . time() . "')";
+ $db_results = mysql_query($sql, dbh());
+ } // if quarantine
+
+ // Go ahead and insert the file
+ else {
+ $catalog->insert_local_song($fullpath,filesize($fullpath));
+ $message[$music] .= _("Successfully-Cataloged");
+ } // end foreach
+ flush();
+ }
+ }
+
+ } // end foreach
+
+ flush();
+ /* Display Upload results */
+ if( $message ){
+ print( "<table align='center'>\n" );
+ print( "<th>Filename</th><th>Result</th>\n" );
+
+ foreach ( $message as $key => $value ){
+ if( $errorenum[$key] ){
+ $color="color='red'";
+ }
+ else{
+ $color="color='green'";
+ }
+ print( "<tr>\n");
+ print( "<td><font $color>$key</font></td><td><font $color>$value</font></td>\n");
+ print( "</tr>\n");
+ }
+ print( "</table>\n" );
+ }
+ require_once(conf('prefix') . "/templates/show_upload.inc");
+ break;
+ case 'add':
+ case 'delete':
+ default:
+ require_once(conf('prefix') . "/templates/show_upload.inc");
+ break;
+} // end of switch on action
+
+echo "\n<br /><br />\n";
+
+/*
+ SHOW QUARANTINE SONGS
+ This Section Displays Quarantined Songs
+ Always process all files in quarantine directory.
+ Make a list (and check it twice)
+*/
+$songs = array();
+
+if ( $handle = @opendir($user->prefs['upload_dir'] ) ){
+ /* Loop Through the directory */
+ while( false !== ($file = readdir( $handle ))){
+
+ /* Find extension */
+ $ext = substr( $file, -4 );
+
+ if(( $ext == '.mp3' )||( $ext == '.ogg' )){
+ $songs[$file]=$user->prefs['upload_dir'] . "/" . "$file";
+ }
+ }
+} // end if upload_dir
+?>
+
+<table class="tabledata" cellspacing="0" cellpadding="0" border="1" align="center">
+ <tr class="table-header">
+ <td><?php echo _("Action"); ?></td>
+ <td><?php echo _("Song"); ?></td>
+ <td><?php echo _("Artist"); ?></td>
+ <td><?php echo _("Album"); ?></td>
+ <td><?php echo _("Genre"); ?></td>
+ <td><?php echo _("Time"); ?></td>
+ <td><?php echo _("Bitrate"); ?></td>
+ <td><?php echo _("Size"); ?></td>
+ <td><?php echo _("Filename"); ?></td>
+ <td><?php echo _("User"); ?></td>
+ <td><?php echo _("Date"); ?></td>
+ </tr>
+
+<?
+ /* Only populate table if valid songs exist */
+ if( sizeof($songs) ) {
+
+ /* Get file info */
+ $audio_info = new Audioinfo();
+ $order = conf('id3tag_order');
+
+ foreach( $songs as $file=>$song ){
+
+ if( $class == "odd" ){
+ $class = "even";
+ }
+ else{
+ $class = "odd";
+ }
+
+ $sql = "SELECT user,addition_time FROM upload WHERE file = '$song'";
+ $db_result = mysql_query($sql, dbh());
+
+ if( $r = mysql_fetch_object($db_result) ){
+ $temp_user = new User($r->user);
+ $uname = $temp_user->fullname;
+ }
+ else{
+ $uname = _("Unknown");
+ }
+
+ /* Get filesize */
+ $filesize = @filesize( $song );
+ $add_time = date( "r",filemtime( $song ) );
+
+ /* get audio information */
+ $results = $audio_info->Info($song);
+
+ $key = get_tag_type($results);
+
+ // Crappy Math time boys and girls!
+ //FIXME: Do this right
+ $min = floor($results['playing_time']/60);
+ $sec = floor($results['playing_time'] - ($min*60));
+ $time = $min . ":" . $sec;
+
+ echo " <tr class=\"".$class."\">\n";
+
+ if( $user->access === 'admin' ){
+ echo " <td>\n" .
+ " <a href=\"" . $web_path . "upload.php/?action=add&song=$file\">" . _("Add") . "</a><br />\n" .
+ " <a href=\"" . $web_path . "upload.php/?action=delete&song=$file\">" . _("Delete") . "</a><br />\n" .
+ " </td>\n";
+ }
+ else{
+ echo " <td>" . _("Quarantined") . "</td>\n";
+ }
+
+
+ echo " <td><a href='" . $web_path .
+ "/play/pupload.php?action=m3u&song=$file&uid=$user->id'>" .
+ $results[$key][title] . "</a></td>\n";
+
+
+ echo " <td>" . $results[$key]['artist'] . " </td>\n";
+ echo " <td>" . $results[$key]['album'] . " </td>\n";
+ echo " <td>" . $results[$key]['genre'] . "</td>\n";
+ echo " <td>" . $time . " </td>\n";
+ echo " <td>" . intval($results['avg_bit_rate']/1000) . "-" . $results['bitrate_mode'] . "</td>\n";
+ echo " <td>" . sprintf("%.2f",($filesize/1048576)) . "</td>\n";
+ echo " <td>$file </td>\n";
+ echo " <td>$uname</td>\n";
+ echo " <td>$add_time </td>\n";
+ echo " </tr>\n";
+ }
+ }
+
+
+?>
+
+ </table>
diff --git a/user.php b/user.php new file mode 100644 index 00000000..ae5e677a --- /dev/null +++ b/user.php @@ -0,0 +1,85 @@ +<?php +/* + + Copyright (c) 2001 - 2005 Ampache.org + All Rights Reserved + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +require_once("modules/init.php"); + +show_template('header'); + +show_menu_items('Profile'); + +$action = scrub_in($_REQUEST['action']); +$username = $user->username; +$password = $_REQUEST['password']; +$confirm_password = scrub_in($_REQUEST['confirm_password']); +$fullname = scrub_in($_REQUEST['fullname']); +$email = scrub_in($_REQUEST['email']); +$offset = scrub_in($_REQUEST['offset_limit']); +$user_id = scrub_in($_REQUEST['user_id']); + + +switch ($action) { + + case 'Change Password': + case 'change_password': + /* Make sure the passwords match */ + if ($confirm_password !== $password || empty($password) ) { + $error->add_error('password',_("Error: Password Does Not Match or Empty")); + show_edit_profile($username); + break; + } + /* Make sure they have the rights */ + if (!$user->has_access(25) || conf('demo_mode')) { + $error->add_error('password',_("Error: Insufficient Rights")); + show_edit_profile($username); + break; + } + $this_user = new User($user_id); + $this_user->update_password($password); + show_confirmation("User Updated","Password updated for " . $this_user->username,"user.php?action=show_edit_profile"); + break; + case 'Update Profile': + case 'update_user': + if (!$user->has_access(25) || conf('demo_mode')) { + $error->add_error('general',_("Error: Insufficient Rights")); + show_edit_profile($username); + break; + } // no rights! + $this_user = new User($user_id); + $this_user->update_fullname($fullname); + $this_user->update_email($email); + $this_user->update_offset($offset); + show_confirmation("User Updated","User Information for " . $this_user->username . " has been updated","user.php?action=show_edit_profile"); + break; + case 'Clear Stats': + case 'clear_stats': + $this_user = new User($user_id); + $this_user->delete_stats(); + show_confirmation("Statistics Cleared","Your Personal Statistics have been cleared","user.php?action=show_edit_profile"); + break; + case 'show_edit_profile': + default: + show_edit_profile($username); + break; +} // end action switch + +show_menu_items('Profile'); +?> |