<?php
/**
* Wrapper for {@link http://bitzi.com/bitcollider/ bitcollider}
* and related standalone helper functions to create
* {@link http://magnet-uri.sourceforge.net/ MAGNET} links and
* perform {@link http://www.faqs.org/rfcs/rfc3548.html Base32} encoding.
*/

/**
* Wrapper for the {@link http://bitzi.com/bitcollider/ bitcollider}
* file metadata extraction tool.
*
* Currently is not able to interact with Bitzi web services.
*
* <code>
* $b = new Bitcollider();
*
* # Normally bitcollider doesn't calculate md5
* $b->set_calculate_md5(true);
* $b->analyze_file('/path/to/my/test.txt');
* echo($b->get_sha1());
* echo($b->get_md5());
*
* # There are many format-specific getters.  Because
* # we're looking at a text file, the following will
* # print nothing:
* echo($b->getmp3bitrate());
*
* # These utility functions generate MAGNET links
* #
* #   http://magnet-uri.sourceforge.net/
* #
* echo($b->getmagnetlink());
*
* # A magnet link can optionally include a direct
* # download file URL in addition to file hashes
* echo($b->getmagnetlink('http://example.com/test.txt));
* </code>
*/
class Bitcollider {
  var
$BITCOLLIDER = '/usr/local/bin/bitcollider';
  function
set_program_location($bitcollider_full_path) {
    
$this->BITCOLLIDER = $bitcollider_full_path;
  }
  var
$file_metadata;
  var
$calculate_md5 = false;
  function
set_calculate_md5($boolean) { $this->calculate_md5 = $boolean; }
  var
$calculate_crc32 = false;
  function
set_calculate_crc32($boolean) { $this->calculate_crc32 = $boolean; }
  
/**
   * Invokes the bitcollider program.
   * @return bool false if the program exists with a nonzero error code.
   */
  
function analyze_file($filename) {
    
$filename = escapeshellarg($filename);
    
$cmd = "$this->BITCOLLIDER -p";
    if (
$this->calculate_md5) { $cmd .= " --md5"; }
    if (
$this->calculate_crc32) { $cmd .= " --crc32"; }
    
$cmd .= " $filename";
    
exec($cmd, $out, $return_code);
    if (
$return_code != 0) {
      return
false;
    }
    
$this->file_metadata = array();
    foreach(
$out as $line) {
      if (
preg_match('/^(\S+?)=(.*)$/',$line,$matches)) {
        
$key = $matches[1];
        
$value = $matches[2];
        
$this->file_metadata[$key] = $value;
      }
    }
    return
true;
  }
  function
get_magnetlink($file_url='') {
    return
magnetlink($this->get_sha1(), $this->get_filename(), $file_url, $this->get_treetiger(), $this->get_kzhash());
  }
  function
get_sha1() {
    
$a = $this->get_sha1_treetiger();
    return
$a[0];
  }
  function
get_treetiger() {
    
$a = $this->get_sha1_treetiger();
    return
$a[1];
  }
  function
get_bitprint() { return $this->file_metadata['bitprint']; }
  function
get_md5() { return $this->file_metadata['tag.md5.md5']; }
  function
get_ed2k() { return $this->file_metadata['tag.ed2k.ed2khash']; }
  function
get_kzhash() { return $this->file_metadata['tag.kzhash.kzhash']; }
  function
get_crc32() { return $this->file_metadata['tag.crc32.crc32']; }
  function
get_filename() { return $this->file_metadata['tag.filename.filename']; }
  function
get_filelength() { return $this->file_metadata['tag.file.length']; }
  function
get_first20bytes() { return $this->file_metadata['tag.file.first20']; }
  function
get_audiotracktitle() { return $this->file_metadata['tag.audiotrack.title']; }
  function
get_audiotrackartist() { return $this->file_metadata['tag.audiotrack.artist']; }
  function
get_audiotrackalbum() { return $this->file_metadata['tag.audiotrack.album']; }
  function
get_audiotracknumber() { return $this->file_metadata['tag.audiotrack.tracknumber']; }
  function
get_audiotrackyear() { return $this->file_metadata['tag.audiotrack.year']; }
  function
get_vorbisbitrate() { return $this->file_metadata['tag.vorbis.bitrate']; }
  function
get_vorbisduration() { return $this->file_metadata['tag.vorbis.duration']; }
  function
get_vorbissamplerate() { return $this->file_metadata['tag.vorbis.samplerate']; }
  function
get_vorbischannels() { return $this->file_metadata['tag.vorbis.channels']; }
  function
get_vorbisencoder() { return $this->file_metadata['tag.vorbis.encoder']; }
  function
get_vorbisaudiosha1() { return $this->file_metadata['tag.vorbis.audio_sha1']; }
  function
get_mp3bitrate() { return $this->file_metadata['tag.mp3.bitrate']; }
  function
get_mp3vbr() { return $this->file_metadata['tag.mp3.vbr']; }
  function
get_mp3duration() { return $this->file_metadata['tag.mp3.duration']; }
  function
get_mp3stereo() { return $this->file_metadata['tag.mp3.stereo']; }
  function
get_mp3encoder() { return $this->file_metadata['tag.mp3.encoder']; }
  function
get_mp3audiosha1() { return $this->file_metadata['tag.mp3.audio_sha1']; }
  function
get_imageformat() { return $this->file_metadata['tag.image.format']; }
  function
get_imagewidth() { return $this->file_metadata['tag.image.width']; }
  function
get_imageheight() { return $this->file_metadata['tag.image.height']; }
  function
get_imagebpp() { return $this->file_metadata['tag.image.bpp']; }
  function
get_id3genre() { return $this->file_metadata['tag.id3genre.genre']; }
  function
get_wavsamplesize() { return $this->file_metadata['tag.wav.samplesize']; }
  function
get_wavduration() { return $this->file_metadata['tag.wav.duration']; }
  function
get_wavsamplerate() { return $this->file_metadata['tag.wav.samplerate']; }
  function
get_wavchannels() { return $this->file_metadata['tag.wav.channels']; }
  function
get_wavaudiosha1() { return $this->file_metadata['tag.wav.audio_sha1']; }
  function
get_videoformat() { return $this->file_metadata['tag.video.format']; }
  function
get_videowidth() { return $this->file_metadata['tag.video.width']; }
  function
get_videoheight() { return $this->file_metadata['tag.video.height']; }
  function
get_videofps() { return $this->file_metadata['tag.video.fps']; }
  function
get_videoduration() { return $this->file_metadata['tag.video.duration']; }
  function
get_videobitrate() { return $this->file_metadata['tag.video.bitrate']; }
  function
get_videocodec() { return $this->file_metadata['tag.video.codec']; }
  function
get_sha1_treetiger() {
    if (
preg_match('/([A-Za-z2-7]{32})\.([A-Za-z2-7]{39})/',$this->get_bitprint(),$matches)) {
      return array(
$matches[1],$matches[2]);
    }
    return array(
'','');
  }
}

/**
* Create {@link http://magnet-uri.sourceforge.net MAGNET link}.
*
* Standalone function, useful if you already know file hashes.
* If using a Bitcollider object call $b->magnet_link() instead.
*
* Any parameter may be an empty string, in which case it will
* be ignored.  Typically $sha1 and $filename are always used.
*
* @param string $sha1 Base32-encoded full file SHA1 hash
* @param string $filename
* @param string $fileurl Direct link for file, typically http
* @param string $treetiger Base32-encoded tiger tree hash
* @param string $kzhash Hex-encoded kazaa hash
*
* @return string A {@link http://magnet-uri.sourceforge.net MAGNET link}
*/
function magnetlink($sha1, $filename, $fileurl, $treetiger, $kzhash) {
  
$m = "";
  if (
$sha1 && $treetiger) {
    
$m .= "xt=urn:bitprint:$sha1.$treetiger";
  } else {
    if (
$sha1) {
      
$m .= "xt=urn:sha1:$sha1";
    } else if (
$treetiger) {
      
$m .= "xt=urn:tree:tiger:$treetiger";
    }
  }
  if (
$kzhash) {
    if (
$m) { $m .= "&"; }
    
$m .= "xt=urn:kzhash:$kzhash";
  }
  if (
$filename) {
    if (
$m) { $m .= "&"; }
    
$filename = urlencode($filename);
    
$m .= "dn=$filename";
  }
  if (
$fileurl) {
    if (
$m) { $m .= "&"; }
    
$fileurl = urlencode($fileurl);
    
$m .= "xs=$fileurl";
  }
  return
"magnet:?$m";
}


/**
* Convert hex (base16) encoding to
* {@link http://www.faqs.org/rfcs/rfc3548.html Base32} encoding.
*
* Probably horribly inefficient. Quick hack to work around initial
* failure to get solution using pack() to work and length limitations
* of base_convert().
*
* Example use: obtain base32 encoded full-file SHA1 hash.
* <code>
* $sha1_32 = hex_to_base32(sha1_file($filename));
* </code>
*
* If you're using the Bitcollider object you do not need to use this
* function.
*
* @param string $hex Hex (Base16) encoded string
*
* @return string Base32 encoded string
*/
function hex_to_base32($hex) {
  
$b32_alpha_to_rfc3548_chars = array(
    
'0' => 'A',
    
'1' => 'B',
    
'2' => 'C',
    
'3' => 'D',
    
'4' => 'E',
    
'5' => 'F',
    
'6' => 'G',
    
'7' => 'H',
    
'8' => 'I',
    
'9' => 'J',
    
'a' => 'K',
    
'b' => 'L',
    
'c' => 'M',
    
'd' => 'N',
    
'e' => 'O',
    
'f' => 'P',
    
'g' => 'Q',
    
'h' => 'R',
    
'i' => 'S',
    
'j' => 'T',
    
'k' => 'U',
    
'l' => 'V',
    
'm' => 'W',
    
'n' => 'X',
    
'o' => 'Y',
    
'p' => 'Z',
    
'q' => '2',
    
'r' => '3',
    
's' => '4',
    
't' => '5',
    
'u' => '6',
    
'v' => '7'
  
);
  for (
$pos = 0; $pos < strlen($hex); $pos += 10) {
    
$hs = substr($hex,$pos,10);
    
$b32_alpha_part = base_convert($hs,16,32);
    
$expected_b32_len = strlen($hs) * 0.8;
    
$actual_b32_len = strlen($b32_alpha_part);
    
$b32_padding_needed = $expected_b32_len - $actual_b32_len;
    for (
$i = $b32_padding_needed; $i > 0; $i--) {
      
$b32_alpha_part = '0' . $b32_alpha_part;
    }
    
$b32_alpha .= $b32_alpha_part;
  }
  for (
$i = 0; $i < strlen($b32_alpha); $i++) {
    
$b32_rfc3548 .= $b32_alpha_to_rfc3548_chars[$b32_alpha[$i]];
  }
  return
$b32_rfc3548;
}

?>