liberty
[ class tree: liberty ] [ index: liberty ] [ all elements ]

Source for file mime.audio.php

Documentation is available at mime.audio.php

  1. <?php
  2. /**
  3.  * @version        $Header$
  4.  *
  5.  * @author        xing  <xing@synapse.plus.com>
  6.  * @version        $Revision$
  7.  *  created        Thursday May 08, 2008
  8.  * @package        liberty
  9.  * @subpackage    liberty_mime_handler
  10.  ***/
  11.  
  12. /**
  13.  * setup
  14.  */
  15. global $gLibertySystem;
  16.  
  17. /**
  18.  *  This is the name of the plugin - max char length is 16
  19.  * As a naming convention, the liberty mime handler definition should start with:
  20.  * PLUGIN_MIME_GUID_
  21.  */
  22. define( 'PLUGIN_MIME_GUID_AUDIO', 'mimeaudio' );
  23.  
  24. $pluginParams = array (
  25.     // Set of functions and what they are called in this paricular plugin
  26.     // Use the GUID as your namespace
  27.     'preload_function'    => 'mime_audio_preload',
  28.     'verify_function'     => 'mime_default_verify',
  29.     'store_function'      => 'mime_audio_store',
  30.     'update_function'     => 'mime_audio_update',
  31.     'load_function'       => 'mime_audio_load',
  32.     'download_function'   => 'mime_default_download',
  33.     'expunge_function'    => 'mime_default_expunge',
  34.     // Brief description of what the plugin does
  35.     'title'               => 'Listen to uploaded Audio files',
  36.     'description'         => 'This plugin will extract as much information about an uploaded audio file as possible and allow you to listen to it on the website using a streaming player.',
  37.     // Templates to display the files
  38.     'view_tpl'            => 'bitpackage:liberty/mime/audio/view.tpl',
  39.     'inline_tpl'          => 'bitpackage:liberty/mime/audio/inline.tpl',
  40.     'storage_tpl'         => 'bitpackage:liberty/mime/audio/storage.tpl',
  41.     'attachment_tpl'      => 'bitpackage:liberty/mime/audio/attachment.tpl',
  42.     'edit_tpl'            => 'bitpackage:liberty/mime/audio/edit.tpl',
  43.     // url to page with options for this plugin
  44.     'plugin_settings_url' => LIBERTY_PKG_URL.'admin/plugins/mime_audio.php',
  45.     // This should be the same for all mime plugins
  46.     'plugin_type'         => MIME_PLUGIN,
  47.     // Set this to TRUE if you want the plugin active right after installation
  48.     'auto_activate'       => FALSE,
  49.     // Help page on bitweaver.org
  50.     //'help_page'           => 'LibertyMime+Audio+Plugin',
  51.     // this should pick up all audio
  52.     'mimetypes'           => array(
  53.         '#audio/.*#i',
  54.     ),
  55. );
  56. $gLibertySystem->registerPlugin( PLUGIN_MIME_GUID_AUDIO, $pluginParams );
  57.  
  58. /**
  59.  * mime_audio_preload This function is loaded on every page load before anything happens and is used to load required scripts.
  60.  * 
  61.  * @access public
  62.  * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
  63.  */
  64. function mime_audio_preload() {
  65.     global $gBitThemes;
  66.     $gBitThemes->loadJavascript( UTIL_PKG_PATH."javascript/flv_player/swfobject.js", FALSE, 25 );
  67. }
  68.  
  69. /**
  70.  * Store the data in the database
  71.  * 
  72.  * @param array $pStoreRow File data needed to store details in the database - sanitised and generated in the verify function
  73.  * @access public
  74.  * @return TRUE on success, FALSE on failure - $pStoreRow['errors'] will contain reason
  75.  */
  76. function mime_audio_store( &$pStoreRow ) {
  77.     // this will set the correct pluign guid, even if we let default handle the store process
  78.     $pStoreRow['attachment_plugin_guid'] = PLUGIN_MIME_GUID_AUDIO;
  79.     $pStoreRow['log'] = array();
  80.  
  81.     // if storing works, we process the audio
  82.     if( $ret = mime_default_store( $pStoreRow )) {
  83.         if( !mime_audio_converter( $pStoreRow )) {
  84.             // if it all goes tits up, we'll know why
  85.             $pStoreRow['errors'] = $pStoreRow['log'];
  86.             $ret = FALSE;
  87.         }
  88.     }
  89.     return $ret;
  90. }
  91.  
  92. /**
  93.  * mime_audio_update
  94.  * 
  95.  * @param array $pStoreRow 
  96.  * @access public
  97.  * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
  98.  */
  99. function mime_audio_update( &$pStoreRow, $pParams = NULL ) {
  100.     $ret = FALSE;
  101.     if( BitBase::verifyId( $pStoreRow['attachment_id'] )) {
  102.         $pStoreRow['log'] = array();
  103.  
  104.         // set the correct pluign guid, even if we let default handle the store process
  105.         $pStoreRow['attachment_plugin_guid'] = PLUGIN_MIME_GUID_AUDIO;
  106.         // remove the entire directory
  107.         $pStoreRow['unlink_dir'] = TRUE;
  108.  
  109.         // if storing works, we process the audio
  110.         if( !empty( $pStoreRow['upload'] ) && $ret = mime_default_update( $pStoreRow )) {
  111.             if( !mime_audio_converter( $pStoreRow )) {
  112.                 // if it all goes tits up, we'll know why
  113.                 $pStoreRow['errors'] = $pStoreRow['log'];
  114.                 $ret = FALSE;
  115.             }
  116.         }
  117.  
  118.         // if there was no upload we'll process the file parameters
  119.         if( empty( $pStoreRow['upload'] ) && !empty( $pParams['meta'] )) {
  120.             // update our local version of the file
  121.             $file = STORAGE_PKG_PATH.$pStoreRow['dest_branch'];
  122.             if( is_file( dirname( $file ).'/bitverted.mp3' )) {
  123.                 $verted = dirname( $file ).'/bitverted.mp3';
  124.             } elseif( is_file( dirname( $file ).'/bitverted.m4a' )) {
  125.                 $verted = dirname( $file ).'/bitverted.m4a';
  126.             }
  127.  
  128.             // update audio tags of converted and original file (ignore errors since these might be m4a)
  129.             mime_audio_update_tags( $verted, $pParams['meta'] );
  130.             mime_audio_update_tags( $file, $pParams['meta'] );
  131.  
  132.             // finally we update the meta table data
  133.             if( !LibertyMime::storeMetaData( $pStoreRow['attachment_id'], 'ID3', $pParams['meta'] )) {
  134.                 $log['store_meta'] = "There was a problem storing the meta data in the database";
  135.             }
  136.  
  137.             if( empty( $log )) {
  138.                 $ret = TRUE;
  139.             } else {
  140.                 $pStoreRow['errors'] = $log;
  141.             }
  142.         }
  143.     }
  144.     return $ret;
  145. }
  146.  
  147. /**
  148.  * Load file data from the database
  149.  * 
  150.  * @param array $pFileHash Contains all file information
  151.  * @param array $pPrefs Attachment preferences taken liberty_attachment_prefs
  152.  * @param array $pParams Parameters for loading the plugin - e.g.: might contain values from the view page
  153.  * @access public
  154.  * @return TRUE on success, FALSE on failure - ['errors'] will contain reason for failure
  155.  */
  156. function mime_audio_load( &$pFileHash, &$pPrefs, $pParams = NULL ) {
  157.     global $gLibertySystem, $gBitThemes;
  158.  
  159.     // don't load a mime image if we don't have an image for this file
  160.     if( $ret = mime_default_load( $pFileHash, $pPrefs, $pParams )) {
  161.         // fetch meta data from the db
  162.         $ret['meta'] = LibertyMime::getMetaData( $pFileHash['attachment_id'], "ID3" );
  163.  
  164.         if( !empty( $ret['storage_path'] )) {
  165.             if( is_file( dirname( STORAGE_PKG_PATH.$ret['storage_path'] ).'/bitverted.mp3' )) {
  166.                 $ret['media_url'] = storage_path_to_url( dirname( $ret['storage_path'] )).'/bitverted.mp3';
  167.                 // we need some javascript for the player:
  168.             } elseif( is_file( dirname( STORAGE_PKG_PATH.$ret['storage_path'] ).'/bitverted.m4a' )) {
  169.                 $ret['media_url'] = storage_path_to_url( dirname( $ret['storage_path'] )).'/bitverted.m4a';
  170.             }
  171.         }
  172.     }
  173.     return $ret;
  174. }
  175.  
  176. /**
  177.  * mime_audio_converter
  178.  * 
  179.  * @param array $pParamHash 
  180.  * @access public
  181.  * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
  182.  */
  183. function mime_audio_converter( &$pParamHash ) {
  184.     global $gBitSystem;
  185.  
  186.     // audio conversion can take a while
  187.     ini_set( "max_execution_time", "1800" );
  188.  
  189.     $ret = FALSE;
  190.     $log = array();
  191.  
  192.     $source = STORAGE_PKG_PATH.$pParamHash['upload']['dest_branch'].$pParamHash['upload']['name'];
  193.     $destPath = dirname( $source );
  194.  
  195.     if( @BitBase::verifyId( $pParamHash['attachment_id'] )) {
  196.         $pattern = "#.*\.(mp3|m4a)$#i";
  197.         if( !$gBitSystem->isFeatureActive( 'mime_audio_force_encode' ) && preg_match( $pattern, $pParamHash['upload']['name'] )) {
  198.             // make a copy of the original maintaining the original extension
  199.             $dest_file = $destPath.'/bitverted.'.preg_replace( $pattern, "$1", strtolower( $pParamHash['upload']['name'] ));
  200.             if( !is_file( $dest_file ) && !link( $source, $dest_file )) {
  201.                 copy( $source, $dest_file );
  202.             }
  203.             $ret = TRUE;
  204.         } else {
  205.             // TODO: have a better mechanism of converting audio to mp3. ffmpeg works well as long as the source is 'perfect'
  206.             //       there are many audiofiles that can't be read by ffmpeg but by other tools like flac, faac, oggenc
  207.             //       mplayer is very good, but has a lot of dependencies and not many servers have it installed
  208.             //       also, using mplayer is a 2 step process: decoding and encoding
  209.  
  210.             // if we convert audio, we always make an mp3
  211.             $dest_file = $destPath.'/bitverted.mp3';
  212.             if( !( $ret = mime_audio_converter_ffmpeg( $pParamHash, $source, $dest_file ))) {
  213.                 // fall back to using slower mplayer / lame combo
  214.                 $ret = mime_audio_converter_mplayer_lame( $pParamHash, $source, $dest_file );
  215.             }
  216.         }
  217.  
  218.         // if the conversion was successful, we'll copy the tags to the new mp3 file and import data to meta tables
  219.         if( $ret == TRUE ) {
  220.             $log['success'] = 'Successfully converted to mp3 audio';
  221.  
  222.             // now that we have a new mp3 file, we might as well copy the tags accross in case someone downloads it
  223.             require_once( UTIL_PKG_PATH.'getid3/getid3/getid3.php' );
  224.             $getID3 = new getID3;
  225.             // we silence this since this will spew lots of ugly errors when using UTF-8 and some odd character in the file ID
  226.             $meta = @$getID3->analyze( $source );
  227.             getid3_lib::CopyTagsToComments( $meta );
  228.  
  229.             // write tags to new mp3 file
  230.             if( $errors = mime_audio_update_tags( $dest_file, $meta['comments'] )) {
  231.                 $log['tagging'] = $errors;
  232.             }
  233.  
  234.             // getID3 returns everything in subarrays - we want to store everything in [0]
  235.             foreach( $meta['comments'] as $key => $comment ) {
  236.                 $store[$key] = $comment[0];
  237.             }
  238.             $store['playtimeseconds'] = $meta['playtime_seconds'];
  239.             $store['playtimestring']  = $meta['playtime_string'];
  240.  
  241.             // make sure we remove previous entries first
  242.             LibertyMime::expungeMetaData( $pParamHash['attachment_id'] );
  243.             if( !LibertyMime::storeMetaData( $pParamHash['attachment_id'], 'ID3', $store )) {
  244.                 $log['store_meta'] = "There was a problem storing the meta data in the database";
  245.             }
  246.  
  247.             // if we have an image in the id3v2 tag, we might as well do something with it
  248.             // we'll simply use the first image we can find in the file
  249.             if( !empty( $meta['id3v2']['APIC'][0]['data'] )) {
  250.                 $image = $meta['id3v2']['APIC'][0];
  251.             } elseif( !empty( $meta['id3v2']['PIC'][0]['data'] )) {
  252.                 $image = $meta['id3v2']['PIC'][0];
  253.             }
  254.  
  255.             if ( !empty( $image )) {
  256.                 // write the image to temp file for us to process
  257.                 $tmpfile = str_replace( "//", "/", tempnam( TEMP_PKG_PATH, LIBERTY_PKG_NAME ));
  258.  
  259.                 if( $fp = fopen( $tmpfile, 'w' )) {
  260.                     fwrite( $fp, $image['data'] );
  261.                     fclose( $fp );
  262.  
  263.                     $fileHash['type']            = $image['mime'];
  264.                     $fileHash['source_file']     = $tmpfile;
  265.                     $fileHash['dest_branch']     = $pParamHash['upload']['dest_branch'];
  266.                     liberty_generate_thumbnails( $fileHash );
  267.  
  268.                     // remove temp file
  269.                     if( !empty( $tmpfile ) && is_file( $tmpfile )) {
  270.                         unlink( $tmpfile );
  271.                     }
  272.                 }
  273.             }
  274.  
  275.             // TODO: when tags package is enabled add an option to add tags
  276.             //       recommended tags might be artist and album
  277.  
  278.             // TODO: fetch album cover from amazon.com or musicbrainz.org
  279.             //       fetch lyrics from lyricwiki.org
  280.  
  281.             //$item->mLogs['audio_converter'] = "Audio file was successfully converted to MP3.";
  282.         }
  283.     }
  284.  
  285.     // update log
  286.     $pParamHash['log'] = array_merge( $pParamHash['log'], $log );
  287.  
  288.     return $ret;
  289. }
  290.  
  291. /**
  292.  * mime_audio_converter_mplayer_lame will decode the audio to wav using mplayer and then encode to mp3 using lame
  293.  * 
  294.  * @param array $pParamHash file information
  295.  * @param array $pSource source file
  296.  * @param array $pDest destination file
  297.  * @access public
  298.  * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
  299.  */
  300. function mime_audio_converter_mplayer_lame( &$pParamHash, $pSource, $pDest ) {
  301.     global $gBitSystem;
  302.     $ret = FALSE;
  303.     $log = array();
  304.  
  305.     if( !empty( $pParamHash ) && !empty( $pSource ) && is_file( $pSource ) && !empty( $pDest )) {
  306.         $mplayer = trim( $gBitSystem->getConfig( 'mplayer_path', shell_exec( 'which mplayer' )));
  307.         $lame    = trim( $gBitSystem->getConfig( 'lame_path', shell_exec( 'which lame' )));
  308.  
  309.         // confirm that both applications are available
  310.         if( $mm = shell_exec( "$mplayer 2>&1" ) && $ll = shell_exec( "$lame 2>&1" )) {
  311.             // we will decode the audio file using mplayer and encode using lame
  312.             $mplayer_params = " -quiet -vo null -vc dummy -af volume=0,resample=44100:0:1 -ao pcm:waveheader:file='$pSource.wav' '$pSource' ";
  313.             $lame_params    = $gBitSystem->getConfig( "mime_audio_lame_options", " -b ".( $gBitSystem->getConfig( 'mime_audio_bitrate', 64000 ) / 1000 ))." '$pSource.wav' '$pDest' ";
  314.             $command        = "$mplayer $mplayer_params && $lame $lame_params";
  315.             $debug          = shell_exec( "$command 2>&1" );
  316.  
  317.             // remove the temporary wav file again
  318.             @unlink( "$pSource.wav" );
  319.  
  320.             // make sure the conversion was successfull
  321.             if( is_file( $pDest ) && filesize( $pDest ) > 1 ) {
  322.                 $ret = TRUE;
  323.             } else {
  324.                 // remove unsuccessfully converted file
  325.                 @unlink( $pDest );
  326.                 $log['message'] = 'ERROR: The audio you uploaded could not be converted by mplayer and lame. DEBUG OUTPUT: '.nl2br( $debug );
  327.  
  328.                 // write error message to error file
  329.                 $h = fopen( dirname( $pDest )."/error", 'w' );
  330.                 fwrite( $h, "$command\n\n$mm\n\n$ll\n\n$debug" );
  331.                 fclose( $h );
  332.             }
  333.         }
  334.     }
  335.  
  336.     // update log
  337.     $pParamHash['log'] = array_merge( $pParamHash['log'], $log );
  338.  
  339.     return $ret;
  340. }
  341.  
  342. /**
  343.  * mime_audio_converter_ffmpeg
  344.  * 
  345.  * @param array $pParamHash file information
  346.  * @param array $pSource source file
  347.  * @param array $pDest destination file
  348.  * @access public
  349.  * @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
  350.  */
  351. function mime_audio_converter_ffmpeg( &$pParamHash, $pSource, $pDest ) {
  352.     global $gBitSystem;
  353.     $ret = FALSE;
  354.     $log = array();
  355.  
  356.     if( !empty( $pParamHash ) && !empty( $pSource ) && is_file( $pSource ) && !empty( $pDest )) {
  357.         // these are set in the liberty plugin admin screen
  358.         $ffmpeg = trim( $gBitSystem->getConfig( 'ffmpeg_path', shell_exec( 'which ffmpeg' )));
  359.  
  360.         if( $ff = shell_exec( "$ffmpeg 2>&1" )) {
  361.             // set up parameters to convert audio
  362.             $params =
  363.                 " -i '$pSource'".
  364.                 " -acodec ".$gBitSystem->getConfig( 'ffmpeg_mp3_lib', 'libmp3lame' ).
  365.                 " -ab ".trim( $gBitSystem->getConfig( 'mime_audio_bitrate', 64000 ).'b' ).
  366.                 " -ar ".trim( $gBitSystem->getConfig( 'mime_audio_samplerate', 22050 )).
  367.                 " -y '$pDest'";
  368.             $debug = shell_exec( "$ffmpeg $params 2>&1" );
  369.  
  370.             // make sure the conversion was successfull
  371.             if( is_file( $pDest ) && filesize( $pDest ) > 1 ) {
  372.                 $ret = TRUE;
  373.             } else {
  374.                 // remove unsuccessfully converted file
  375.                 @unlink( $pDest );
  376.                 $log['message'] = 'ERROR: The audio you uploaded could not be converted by ffmpeg. DEBUG OUTPUT: '.nl2br( $debug );
  377.  
  378.                 // write error message to error file
  379.                 $h = fopen( dirname( $pDest )."/error", 'w' );
  380.                 fwrite( $h, "$ffmpeg $params\n\n$ff\n\n$debug" );
  381.                 fclose( $h );
  382.             }
  383.         }
  384.     }
  385.  
  386.     // update log
  387.     $pParamHash['log'] = array_merge( $pParamHash['log'], $log );
  388.  
  389.     return $ret;
  390. }
  391.  
  392. /**
  393.  * mime_audio_update_tags will update the tags of a given audio file
  394.  * 
  395.  * @param array $pFile absolute path to file
  396.  * @param array $pMetaData Hash of data that should be passed to the file.
  397.  * @access public
  398.  * @return NULL on success, String of errors on failure
  399.  */
  400. function mime_audio_update_tags( $pFile, $pMetaData ) {
  401.     $ret = NULL;
  402.     if( !empty( $pFile ) && is_file( $pFile ) && is_array( $pMetaData )) {
  403.         // we need to initiate getID3 for the writer to work
  404.         require_once( UTIL_PKG_PATH.'getid3/getid3/getid3.php' );
  405.         $getID3 = new getID3;
  406.  
  407.         require_once( UTIL_PKG_PATH.'getid3/getid3/write.php' );
  408.         // Initialize getID3 tag-writing module
  409.         $tagwriter = new getid3_writetags();
  410.         $tagwriter->filename       = $pFile;
  411.         $tagwriter->tagformats     = array( 'id3v1', 'id3v2.3' );
  412.  
  413.         // set various options
  414.         $tagwriter->overwrite_tags = TRUE;
  415.         $tagwriter->tag_encoding   = "UTF-8";
  416.  
  417.         // prepare meta data for storing
  418.         foreach( $pMetaData as $key => $data ) {
  419.             if( !is_array( $data )) {
  420.                 $data = array( $data );
  421.             }
  422.             $write[$key] = $data;
  423.         }
  424.  
  425.         // store the tags
  426.         if( !empty( $write )) {
  427.             $tagwriter->tag_data = $write;
  428.             if( !$tagwriter->WriteTags() ) {
  429.                 $ret = 'Failed to write tags!<br />'.implode( '<br /><br />', $tagwriter->errors );
  430.             }
  431.         }
  432.     }
  433.     return $ret;
  434. }
  435. ?>

Documentation generated on Wed, 29 Jul 2015 13:57:05 +0000 by phpDocumentor 1.5.0-lsces