annotate e2gallerypro/e2upload/Backend/Assets/getid3/module.audio-video.flv.php @ 28:118cb614cf18 judyates tip

judy bio.
author Robert McIntyre <rlm@mit.edu>
date Tue, 29 Dec 2015 16:26:18 -0800
parents 3f6b44aa6b35
children
rev   line source
rlm@3 1 <?php
rlm@3 2 // +----------------------------------------------------------------------+
rlm@3 3 // | PHP version 5 |
rlm@3 4 // +----------------------------------------------------------------------+
rlm@3 5 // | Copyright (c) 2002-2006 James Heinrich, Allan Hansen |
rlm@3 6 // +----------------------------------------------------------------------+
rlm@3 7 // | This source file is subject to version 2 of the GPL license, |
rlm@3 8 // | that is bundled with this package in the file license.txt and is |
rlm@3 9 // | available through the world-wide-web at the following url: |
rlm@3 10 // | http://www.gnu.org/copyleft/gpl.html |
rlm@3 11 // +----------------------------------------------------------------------+
rlm@3 12 // | getID3() - http://getid3.sourceforge.net or http://www.getid3.org |
rlm@3 13 // +----------------------------------------------------------------------+
rlm@3 14 // | Authors: James Heinrich <infoØgetid3*org> |
rlm@3 15 // | Allan Hansen <ahØartemis*dk> |
rlm@3 16 // +----------------------------------------------------------------------+
rlm@3 17 // | module.archive.gzip.php |
rlm@3 18 // | module for analyzing GZIP files |
rlm@3 19 // | dependencies: NONE |
rlm@3 20 // +----------------------------------------------------------------------+
rlm@3 21 // | FLV module by Seth Kaufman <sethØwhirl-i.gig*com> |
rlm@3 22 // | |
rlm@3 23 // | * version 0.1 (26 June 2005) |
rlm@3 24 // | |
rlm@3 25 // | minor modifications by James Heinrich <infoØgetid3*org> |
rlm@3 26 // | * version 0.1.1 (15 July 2005) |
rlm@3 27 // | |
rlm@3 28 // | Support for On2 VP6 codec and meta information by |
rlm@3 29 // | Steve Webster <steve.websterØfeaturecreep*com> |
rlm@3 30 // | * version 0.2 (22 February 2006) |
rlm@3 31 // | |
rlm@3 32 // | Modified to not read entire file into memory |
rlm@3 33 // | by James Heinrich <infoØgetid3*org> |
rlm@3 34 // | * version 0.3 (15 June 2006) |
rlm@3 35 // | |
rlm@3 36 // | Modifications by Allan Hansen <ahØartemis*dk> |
rlm@3 37 // | Adapted module for PHP5 and getID3 2.0.0. |
rlm@3 38 // +----------------------------------------------------------------------+
rlm@3 39 //
rlm@3 40 // $Id: module.audio-video.flv.php,v 1.7 2006/11/10 11:20:12 ah Exp $
rlm@3 41
rlm@3 42
rlm@3 43
rlm@3 44 class getid3_flv extends getid3_handler
rlm@3 45 {
rlm@3 46
rlm@3 47 const TAG_AUDIO = 8;
rlm@3 48 const TAG_VIDEO = 9;
rlm@3 49 const TAG_META = 18;
rlm@3 50
rlm@3 51 const VIDEO_H263 = 2;
rlm@3 52 const VIDEO_SCREEN = 3;
rlm@3 53 const VIDEO_VP6 = 4;
rlm@3 54
rlm@3 55
rlm@3 56 public function Analyze()
rlm@3 57 {
rlm@3 58 $info = &$this->getid3->info;
rlm@3 59
rlm@3 60 $info['flv'] = array ();
rlm@3 61 $info_flv = &$info['flv'];
rlm@3 62
rlm@3 63 fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
rlm@3 64
rlm@3 65 $flv_data_length = $info['avdataend'] - $info['avdataoffset'];
rlm@3 66 $flv_header = fread($this->getid3->fp, 5);
rlm@3 67
rlm@3 68 $info['fileformat'] = 'flv';
rlm@3 69 $info_flv['header']['signature'] = substr($flv_header, 0, 3);
rlm@3 70 $info_flv['header']['version'] = getid3_lib::BigEndian2Int(substr($flv_header, 3, 1));
rlm@3 71 $type_flags = getid3_lib::BigEndian2Int(substr($flv_header, 4, 1));
rlm@3 72
rlm@3 73 $info_flv['header']['hasAudio'] = (bool) ($type_flags & 0x04);
rlm@3 74 $info_flv['header']['hasVideo'] = (bool) ($type_flags & 0x01);
rlm@3 75
rlm@3 76 $frame_size_data_length = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 4));
rlm@3 77 $flv_header_frame_length = 9;
rlm@3 78 if ($frame_size_data_length > $flv_header_frame_length) {
rlm@3 79 fseek($this->getid3->fp, $frame_size_data_length - $flv_header_frame_length, SEEK_CUR);
rlm@3 80 }
rlm@3 81
rlm@3 82 $duration = 0;
rlm@3 83 while ((ftell($this->getid3->fp) + 1) < $info['avdataend']) {
rlm@3 84
rlm@3 85 $this_tag_header = fread($this->getid3->fp, 16);
rlm@3 86
rlm@3 87 $previous_tag_length = getid3_lib::BigEndian2Int(substr($this_tag_header, 0, 4));
rlm@3 88 $tag_type = getid3_lib::BigEndian2Int(substr($this_tag_header, 4, 1));
rlm@3 89 $data_length = getid3_lib::BigEndian2Int(substr($this_tag_header, 5, 3));
rlm@3 90 $timestamp = getid3_lib::BigEndian2Int(substr($this_tag_header, 8, 3));
rlm@3 91 $last_header_byte = getid3_lib::BigEndian2Int(substr($this_tag_header, 15, 1));
rlm@3 92 $next_offset = ftell($this->getid3->fp) - 1 + $data_length;
rlm@3 93
rlm@3 94 switch ($tag_type) {
rlm@3 95
rlm@3 96 case getid3_flv::TAG_AUDIO:
rlm@3 97 if (!isset($info_flv['audio']['audioFormat'])) {
rlm@3 98 $info_flv['audio']['audioFormat'] = $last_header_byte & 0x07;
rlm@3 99 $info_flv['audio']['audioRate'] = ($last_header_byte & 0x30) / 0x10;
rlm@3 100 $info_flv['audio']['audioSampleSize'] = ($last_header_byte & 0x40) / 0x40;
rlm@3 101 $info_flv['audio']['audioType'] = ($last_header_byte & 0x80) / 0x80;
rlm@3 102 }
rlm@3 103 break;
rlm@3 104
rlm@3 105
rlm@3 106 case getid3_flv::TAG_VIDEO:
rlm@3 107 if (!isset($info_flv['video']['videoCodec'])) {
rlm@3 108 $info_flv['video']['videoCodec'] = $last_header_byte & 0x07;
rlm@3 109
rlm@3 110 $flv_video_header = fread($this->getid3->fp, 11);
rlm@3 111
rlm@3 112 if ($info_flv['video']['videoCodec'] != getid3_flv::VIDEO_VP6) {
rlm@3 113
rlm@3 114 $picture_size_type = (getid3_lib::BigEndian2Int(substr($flv_video_header, 3, 2))) >> 7;
rlm@3 115 $picture_size_type = $picture_size_type & 0x0007;
rlm@3 116 $info_flv['header']['videoSizeType'] = $picture_size_type;
rlm@3 117
rlm@3 118 switch ($picture_size_type) {
rlm@3 119 case 0:
rlm@3 120 $picture_size_enc = getid3_lib::BigEndian2Int(substr($flv_video_header, 5, 2));
rlm@3 121 $picture_size_enc <<= 1;
rlm@3 122 $info['video']['resolution_x'] = ($picture_size_enc & 0xFF00) >> 8;
rlm@3 123 $picture_size_enc = getid3_lib::BigEndian2Int(substr($flv_video_header, 6, 2));
rlm@3 124 $picture_size_enc <<= 1;
rlm@3 125 $info['video']['resolution_y'] = ($picture_size_enc & 0xFF00) >> 8;
rlm@3 126 break;
rlm@3 127
rlm@3 128 case 1:
rlm@3 129 $picture_size_enc = getid3_lib::BigEndian2Int(substr($flv_video_header, 5, 4));
rlm@3 130 $picture_size_enc <<= 1;
rlm@3 131 $info['video']['resolution_x'] = ($picture_size_enc & 0xFFFF0000) >> 16;
rlm@3 132
rlm@3 133 $picture_size_enc = getid3_lib::BigEndian2Int(substr($flv_video_header, 7, 4));
rlm@3 134 $picture_size_enc <<= 1;
rlm@3 135 $info['video']['resolution_y'] = ($picture_size_enc & 0xFFFF0000) >> 16;
rlm@3 136 break;
rlm@3 137
rlm@3 138 case 2:
rlm@3 139 $info['video']['resolution_x'] = 352;
rlm@3 140 $info['video']['resolution_y'] = 288;
rlm@3 141 break;
rlm@3 142
rlm@3 143 case 3:
rlm@3 144 $info['video']['resolution_x'] = 176;
rlm@3 145 $info['video']['resolution_y'] = 144;
rlm@3 146 break;
rlm@3 147
rlm@3 148 case 4:
rlm@3 149 $info['video']['resolution_x'] = 128;
rlm@3 150 $info['video']['resolution_y'] = 96;
rlm@3 151 break;
rlm@3 152
rlm@3 153 case 5:
rlm@3 154 $info['video']['resolution_x'] = 320;
rlm@3 155 $info['video']['resolution_y'] = 240;
rlm@3 156 break;
rlm@3 157
rlm@3 158 case 6:
rlm@3 159 $info['video']['resolution_x'] = 160;
rlm@3 160 $info['video']['resolution_y'] = 120;
rlm@3 161 break;
rlm@3 162
rlm@3 163 default:
rlm@3 164 $info['video']['resolution_x'] = 0;
rlm@3 165 $info['video']['resolution_y'] = 0;
rlm@3 166 break;
rlm@3 167 }
rlm@3 168 }
rlm@3 169 }
rlm@3 170 break;
rlm@3 171
rlm@3 172
rlm@3 173 // Meta tag
rlm@3 174 case getid3_flv::TAG_META:
rlm@3 175
rlm@3 176 fseek($this->getid3->fp, -1, SEEK_CUR);
rlm@3 177 $reader = new AMFReader(new AMFStream(fread($this->getid3->fp, $data_length)));
rlm@3 178 $event_name = $reader->readData();
rlm@3 179 $info['meta'][$event_name] = $reader->readData();
rlm@3 180 unset($reader);
rlm@3 181
rlm@3 182 $info['video']['frame_rate'] = @$info['meta']['onMetaData']['framerate'];
rlm@3 183 $info['video']['resolution_x'] = @$info['meta']['onMetaData']['width'];
rlm@3 184 $info['video']['resolution_y'] = @$info['meta']['onMetaData']['height'];
rlm@3 185 break;
rlm@3 186
rlm@3 187 default:
rlm@3 188 // noop
rlm@3 189 break;
rlm@3 190 }
rlm@3 191
rlm@3 192 if ($timestamp > $duration) {
rlm@3 193 $duration = $timestamp;
rlm@3 194 }
rlm@3 195
rlm@3 196 fseek($this->getid3->fp, $next_offset, SEEK_SET);
rlm@3 197 }
rlm@3 198
rlm@3 199 if ($info['playtime_seconds'] = $duration / 1000) {
rlm@3 200 $info['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds'];
rlm@3 201 }
rlm@3 202
rlm@3 203 if ($info_flv['header']['hasAudio']) {
rlm@3 204 $info['audio']['codec'] = $this->FLVaudioFormat($info_flv['audio']['audioFormat']);
rlm@3 205 $info['audio']['sample_rate'] = $this->FLVaudioRate($info_flv['audio']['audioRate']);
rlm@3 206 $info['audio']['bits_per_sample'] = $this->FLVaudioBitDepth($info_flv['audio']['audioSampleSize']);
rlm@3 207
rlm@3 208 $info['audio']['channels'] = $info_flv['audio']['audioType'] + 1; // 0=mono,1=stereo
rlm@3 209 $info['audio']['lossless'] = ($info_flv['audio']['audioFormat'] ? false : true); // 0=uncompressed
rlm@3 210 $info['audio']['dataformat'] = 'flv';
rlm@3 211 }
rlm@3 212 if (@$info_flv['header']['hasVideo']) {
rlm@3 213 $info['video']['codec'] = $this->FLVvideoCodec($info_flv['video']['videoCodec']);
rlm@3 214 $info['video']['dataformat'] = 'flv';
rlm@3 215 $info['video']['lossless'] = false;
rlm@3 216 }
rlm@3 217
rlm@3 218 return true;
rlm@3 219 }
rlm@3 220
rlm@3 221
rlm@3 222 public static function FLVaudioFormat($id) {
rlm@3 223
rlm@3 224 static $lookup = array(
rlm@3 225 0 => 'uncompressed',
rlm@3 226 1 => 'ADPCM',
rlm@3 227 2 => 'mp3',
rlm@3 228 5 => 'Nellymoser 8kHz mono',
rlm@3 229 6 => 'Nellymoser',
rlm@3 230 );
rlm@3 231 return (@$lookup[$id] ? @$lookup[$id] : false);
rlm@3 232 }
rlm@3 233
rlm@3 234
rlm@3 235 public static function FLVaudioRate($id) {
rlm@3 236
rlm@3 237 static $lookup = array(
rlm@3 238 0 => 5500,
rlm@3 239 1 => 11025,
rlm@3 240 2 => 22050,
rlm@3 241 3 => 44100,
rlm@3 242 );
rlm@3 243 return (@$lookup[$id] ? @$lookup[$id] : false);
rlm@3 244 }
rlm@3 245
rlm@3 246
rlm@3 247 public static function FLVaudioBitDepth($id) {
rlm@3 248
rlm@3 249 static $lookup = array(
rlm@3 250 0 => 8,
rlm@3 251 1 => 16,
rlm@3 252 );
rlm@3 253 return (@$lookup[$id] ? @$lookup[$id] : false);
rlm@3 254 }
rlm@3 255
rlm@3 256
rlm@3 257 public static function FLVvideoCodec($id) {
rlm@3 258
rlm@3 259 static $lookup = array(
rlm@3 260 getid3_flv::VIDEO_H263 => 'Sorenson H.263',
rlm@3 261 getid3_flv::VIDEO_SCREEN => 'Screen video',
rlm@3 262 getid3_flv::VIDEO_VP6 => 'On2 VP6',
rlm@3 263 );
rlm@3 264 return (@$lookup[$id] ? @$lookup[$id] : false);
rlm@3 265 }
rlm@3 266 }
rlm@3 267
rlm@3 268
rlm@3 269
rlm@3 270 class AMFStream
rlm@3 271 {
rlm@3 272 public $bytes;
rlm@3 273 public $pos;
rlm@3 274
rlm@3 275
rlm@3 276 public function AMFStream($bytes) {
rlm@3 277
rlm@3 278 $this->bytes = $bytes;
rlm@3 279 $this->pos = 0;
rlm@3 280 }
rlm@3 281
rlm@3 282
rlm@3 283 public function readByte() {
rlm@3 284
rlm@3 285 return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1));
rlm@3 286 }
rlm@3 287
rlm@3 288
rlm@3 289 public function readInt() {
rlm@3 290
rlm@3 291 return ($this->readByte() << 8) + $this->readByte();
rlm@3 292 }
rlm@3 293
rlm@3 294
rlm@3 295 public function readLong() {
rlm@3 296
rlm@3 297 return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
rlm@3 298 }
rlm@3 299
rlm@3 300
rlm@3 301 public function readDouble() {
rlm@3 302
rlm@3 303 return getid3_lib::BigEndian2Float($this->read(8));
rlm@3 304 }
rlm@3 305
rlm@3 306
rlm@3 307 public function readUTF() {
rlm@3 308
rlm@3 309 $length = $this->readInt();
rlm@3 310 return $this->read($length);
rlm@3 311 }
rlm@3 312
rlm@3 313
rlm@3 314 public function readLongUTF() {
rlm@3 315
rlm@3 316 $length = $this->readLong();
rlm@3 317 return $this->read($length);
rlm@3 318 }
rlm@3 319
rlm@3 320
rlm@3 321 public function read($length) {
rlm@3 322
rlm@3 323 $val = substr($this->bytes, $this->pos, $length);
rlm@3 324 $this->pos += $length;
rlm@3 325 return $val;
rlm@3 326 }
rlm@3 327
rlm@3 328
rlm@3 329 public function peekByte() {
rlm@3 330
rlm@3 331 $pos = $this->pos;
rlm@3 332 $val = $this->readByte();
rlm@3 333 $this->pos = $pos;
rlm@3 334 return $val;
rlm@3 335 }
rlm@3 336
rlm@3 337
rlm@3 338 public function peekInt() {
rlm@3 339
rlm@3 340 $pos = $this->pos;
rlm@3 341 $val = $this->readInt();
rlm@3 342 $this->pos = $pos;
rlm@3 343 return $val;
rlm@3 344 }
rlm@3 345
rlm@3 346
rlm@3 347 public function peekLong() {
rlm@3 348
rlm@3 349 $pos = $this->pos;
rlm@3 350 $val = $this->readLong();
rlm@3 351 $this->pos = $pos;
rlm@3 352 return $val;
rlm@3 353 }
rlm@3 354
rlm@3 355
rlm@3 356 public function peekDouble() {
rlm@3 357
rlm@3 358 $pos = $this->pos;
rlm@3 359 $val = $this->readDouble();
rlm@3 360 $this->pos = $pos;
rlm@3 361 return $val;
rlm@3 362 }
rlm@3 363
rlm@3 364
rlm@3 365 public function peekUTF() {
rlm@3 366
rlm@3 367 $pos = $this->pos;
rlm@3 368 $val = $this->readUTF();
rlm@3 369 $this->pos = $pos;
rlm@3 370 return $val;
rlm@3 371 }
rlm@3 372
rlm@3 373
rlm@3 374 public function peekLongUTF() {
rlm@3 375
rlm@3 376 $pos = $this->pos;
rlm@3 377 $val = $this->readLongUTF();
rlm@3 378 $this->pos = $pos;
rlm@3 379 return $val;
rlm@3 380 }
rlm@3 381 }
rlm@3 382
rlm@3 383
rlm@3 384
rlm@3 385 class AMFReader
rlm@3 386 {
rlm@3 387 public $stream;
rlm@3 388
rlm@3 389 public function __construct($stream) {
rlm@3 390
rlm@3 391 $this->stream = $stream;
rlm@3 392 }
rlm@3 393
rlm@3 394
rlm@3 395 public function readData() {
rlm@3 396
rlm@3 397 $value = null;
rlm@3 398
rlm@3 399 $type = $this->stream->readByte();
rlm@3 400
rlm@3 401 switch($type) {
rlm@3 402 // Double
rlm@3 403 case 0:
rlm@3 404 $value = $this->readDouble();
rlm@3 405 break;
rlm@3 406
rlm@3 407 // Boolean
rlm@3 408 case 1:
rlm@3 409 $value = $this->readBoolean();
rlm@3 410 break;
rlm@3 411
rlm@3 412 // String
rlm@3 413 case 2:
rlm@3 414 $value = $this->readString();
rlm@3 415 break;
rlm@3 416
rlm@3 417 // Object
rlm@3 418 case 3:
rlm@3 419 $value = $this->readObject();
rlm@3 420 break;
rlm@3 421
rlm@3 422 // null
rlm@3 423 case 6:
rlm@3 424 return null;
rlm@3 425 break;
rlm@3 426
rlm@3 427 // Mixed array
rlm@3 428 case 8:
rlm@3 429 $value = $this->readMixedArray();
rlm@3 430 break;
rlm@3 431
rlm@3 432 // Array
rlm@3 433 case 10:
rlm@3 434 $value = $this->readArray();
rlm@3 435 break;
rlm@3 436
rlm@3 437 // Date
rlm@3 438 case 11:
rlm@3 439 $value = $this->readDate();
rlm@3 440 break;
rlm@3 441
rlm@3 442 // Long string
rlm@3 443 case 13:
rlm@3 444 $value = $this->readLongString();
rlm@3 445 break;
rlm@3 446
rlm@3 447 // XML (handled as string)
rlm@3 448 case 15:
rlm@3 449 $value = $this->readXML();
rlm@3 450 break;
rlm@3 451
rlm@3 452 // Typed object (handled as object)
rlm@3 453 case 16:
rlm@3 454 $value = $this->readTypedObject();
rlm@3 455 break;
rlm@3 456
rlm@3 457 // Long string
rlm@3 458 default:
rlm@3 459 $value = '(unknown or unsupported data type)';
rlm@3 460 break;
rlm@3 461 }
rlm@3 462
rlm@3 463 return $value;
rlm@3 464 }
rlm@3 465
rlm@3 466
rlm@3 467 public function readDouble() {
rlm@3 468
rlm@3 469 return $this->stream->readDouble();
rlm@3 470 }
rlm@3 471
rlm@3 472
rlm@3 473 public function readBoolean() {
rlm@3 474
rlm@3 475 return $this->stream->readByte() == 1;
rlm@3 476 }
rlm@3 477
rlm@3 478
rlm@3 479 public function readString() {
rlm@3 480
rlm@3 481 return $this->stream->readUTF();
rlm@3 482 }
rlm@3 483
rlm@3 484
rlm@3 485 public function readObject() {
rlm@3 486
rlm@3 487 // Get highest numerical index - ignored
rlm@3 488 $highestIndex = $this->stream->readLong();
rlm@3 489
rlm@3 490 $data = array();
rlm@3 491
rlm@3 492 while ($key = $this->stream->readUTF()) {
rlm@3 493 // Mixed array record ends with empty string (0x00 0x00) and 0x09
rlm@3 494 if (($key == '') && ($this->stream->peekByte() == 0x09)) {
rlm@3 495 // Consume byte
rlm@3 496 $this->stream->readByte();
rlm@3 497 break;
rlm@3 498 }
rlm@3 499
rlm@3 500 $data[$key] = $this->readData();
rlm@3 501 }
rlm@3 502
rlm@3 503 return $data;
rlm@3 504 }
rlm@3 505
rlm@3 506
rlm@3 507 public function readMixedArray() {
rlm@3 508
rlm@3 509 // Get highest numerical index - ignored
rlm@3 510 $highestIndex = $this->stream->readLong();
rlm@3 511
rlm@3 512 $data = array();
rlm@3 513
rlm@3 514 while ($key = $this->stream->readUTF()) {
rlm@3 515 // Mixed array record ends with empty string (0x00 0x00) and 0x09
rlm@3 516 if (($key == '') && ($this->stream->peekByte() == 0x09)) {
rlm@3 517 // Consume byte
rlm@3 518 $this->stream->readByte();
rlm@3 519 break;
rlm@3 520 }
rlm@3 521
rlm@3 522 if (is_numeric($key)) {
rlm@3 523 $key = (float) $key;
rlm@3 524 }
rlm@3 525
rlm@3 526 $data[$key] = $this->readData();
rlm@3 527 }
rlm@3 528
rlm@3 529 return $data;
rlm@3 530 }
rlm@3 531
rlm@3 532
rlm@3 533 public function readArray() {
rlm@3 534
rlm@3 535 $length = $this->stream->readLong();
rlm@3 536
rlm@3 537 $data = array();
rlm@3 538
rlm@3 539 for ($i = 0; $i < count($length); $i++) {
rlm@3 540 $data[] = $this->readData();
rlm@3 541 }
rlm@3 542
rlm@3 543 return $data;
rlm@3 544 }
rlm@3 545
rlm@3 546
rlm@3 547 public function readDate() {
rlm@3 548
rlm@3 549 $timestamp = $this->stream->readDouble();
rlm@3 550 $timezone = $this->stream->readInt();
rlm@3 551 return $timestamp;
rlm@3 552 }
rlm@3 553
rlm@3 554
rlm@3 555 public function readLongString() {
rlm@3 556
rlm@3 557 return $this->stream->readLongUTF();
rlm@3 558 }
rlm@3 559
rlm@3 560
rlm@3 561 public function readXML() {
rlm@3 562
rlm@3 563 return $this->stream->readLongUTF();
rlm@3 564 }
rlm@3 565
rlm@3 566
rlm@3 567 public function readTypedObject() {
rlm@3 568
rlm@3 569 $className = $this->stream->readUTF();
rlm@3 570 return $this->readObject();
rlm@3 571 }
rlm@3 572 }
rlm@3 573
rlm@3 574 ?>