ffmpeg_bin = get_option( 'av_ffmpeg_path' ); $this->ffprobe_bin = get_option( 'av_ffprobe_path' ); if ( empty( $this->ffmpeg_bin ) || empty( $this->ffprobe_bin ) ) { add_action( 'admin_notices', array( $this, 'no_ffmpeg_notice' ) ); return; } add_filter( 'wp_generate_attachment_metadata', array( $this, 'wp_generate_attachment_metadata' ), 10, 2 ); } /** * Admin-specific actions and filters */ function admin_init() { $this->encodes = $this->get_transient( $this->encode_key, array() ); $this->queue = $this->get_transient( $this->queue_key, array() ); $this->failed = $this->get_transient( $this->failed_key, array() ); add_action( 'wp_ajax_av-read-queue', array( $this, 'av_read_queue_json' ) ); add_action( 'admin_footer', array( $this, 'admin_footer' ) ); wp_enqueue_style( 'av-transcoding', plugins_url( 'transcoding.css', __FILE__ ) ); wp_enqueue_script( 'av-transcoding', plugins_url( 'transcoding.js', __FILE__ ), array( 'backbone', 'wp-util' ), '', true ); add_action( 'admin_notices', array( $this, 'ffmpeg_encoding_notice' ) ); foreach ( array( 'ffmpeg', 'ffprobe' ) as $bin ) { register_setting( 'media', 'av_' . $bin . '_path' ); add_settings_field( 'av-' . $bin, __( sprintf( 'Path to %s binary', $bin ) ), array( $this, 'field_' . $bin ), 'media', 'av-settings' ); } } function av_read_queue_json() { $queue = array(); $encodes = array(); if ( ! empty( $this->encodes ) ) { foreach ( $this->encodes as $key => $exists ) { list( $id, $type ) = explode( '_', $key ); $progress = $this->get_transient( $key . '_progress', 1 ); $encodes[] = array( 'pid' => $id, 'type' => strtoupper( $type ), 'progress' => $progress, 'title' => get_the_title( $id ) ); } } if ( ! empty( $this->queue ) ) { foreach ( $this->queue as $key => $file ) { if ( isset( $this->encodes[ $key ] ) ) { continue; } list( $id, $type ) = explode( '_', $key ); $queue[] = array( 'pid' => $id, 'type' => $type, 'file' => $file, 'path' => ltrim( str_replace( $_SERVER['DOCUMENT_ROOT'], '', $file ), '/' ) ); } } echo json_encode( array( 'encodes' => $encodes, 'queue' => $queue, 'failed' => $this->failed, ) ); if ( empty( $encodes ) && ! empty( $queue ) ) { $this->check_queue(); } exit(); } function admin_footer() { include_once( __DIR__ . '/templates.php' ); } /** * Remove items from the queue when the main attachment is deleted * * @param int $id */ function delete_post( $id ) { $post = get_post( $id ); if ( 'attachment' !== $post->post_type ) { return; } $queue = $this->get_transient( $this->queue_key, array() ); foreach ( $queue as $key => $file ) { if ( 0 === strpos( $key, $id . '_' ) ) { unset( $queue[ $key ] ); } } set_transient( $this->queue_key, $queue ); } /** * Fire a non-blocking request to pop an item off the queue if no file is in progress */ function check_queue() { $url = add_query_arg( array( 'action' => 'av_encode' ), home_url( '/' ) ); $nonced = html_entity_decode( wp_nonce_url( $url, 'av_encode' ) ); $this->log( $nonced ); wp_remote_get( $nonced, array( 'blocking' => false ) ); } /** * Add items to the queue * * @param int $id */ function add_to_queue( $id ) { if ( get_post_meta( $id, '_is_fallback', true ) ) { return; } $file = get_attached_file( $id ); $ext = wp_check_filetype( $file ); $base = basename( $file ); $front = rtrim( $file, $base ); $encodes = $this->get_transient( $this->encode_key, array() ); $queue = $this->get_transient( $this->queue_key, array() ); $fallbacks = array(); switch ( $ext['ext'] ) { case 'wma': case 'wav': case 'm4a': $fallbacks = array( 'mp3', 'ogg' ); break; case 'ogg': $fallbacks[] = 'mp3'; break; case 'mp3': $fallbacks[] = 'ogg'; break; case 'm4v': case 'mp4': $fallbacks = array( 'ogv', 'webm' ); break; case 'webm': $fallbacks[] = 'ogv'; break; case 'ogv': $fallbacks[] = 'webm'; break; case 'flv': case 'mov': case 'wmv': $fallbacks = array( 'webm', 'ogv' ); break; } foreach ( $fallbacks as $type ) { $key = '_' . $type . '_fallback'; $meta = get_post_meta( $id, $key, true ); $encode_key = $id . '_' . $type; $new_file = $front . rtrim( $base, $ext['ext'] ) . $type; if ( empty( $meta ) && ! in_array( $new_file, $queue ) && ! isset( $encodes[ $encode_key ] ) && ! isset( $queue[ $encode_key ] ) && ! file_exists( $new_file ) ) { $queue[ $encode_key ] = $new_file; } } set_transient( $this->queue_key, $queue ); $this->check_queue(); } /** * On init, check for a request to potentially hijack */ function check_for_activity() { if ( ! isset( $_GET['action'] ) || 0 !== strpos( $_GET['action'], 'av_' ) ) { return; } $check = wp_verify_nonce( $_GET['_wpnonce'], $_GET['action'] ); if ( ! $check ) { // the first nonce is always failing for some reason... $this->log( 'nonce failed' ); //return; } switch ( $_GET['action'] ) { case 'av_delete_queue': set_transient( $this->queue_key, array() ); wp_safe_redirect( wp_get_referer() ); exit(); case 'av_delete_failed': set_transient( $this->failed_key, array() ); wp_safe_redirect( wp_get_referer() ); exit(); case 'av_encode': $encodes = $this->get_transient( $this->encode_key, array() ); if ( empty( $encodes ) || count( $encodes ) < AV_ENCODE_MAX ) { $queue = $this->get_transient( $this->queue_key, array() ); if ( ! empty( $queue ) ) { foreach ( $queue as $key => $file ) { list( $id, $type ) = explode( '_', $key ); $encodes[ $key ] = 1; set_transient( $this->encode_key, $encodes ); unset( $queue[ $key ] ); set_transient( $this->queue_key, $queue ); ignore_user_abort( true ); set_time_limit( 0 ); $this->encode_media( $id, $type, $file ); exit(); } } } break; case 'av_queue': $id = (int) $_GET['media_id']; if ( empty( $id ) ) { return; } $this->add_to_queue( $id ); exit(); } } function remove_encode( $encode_key ) { $encodes = $this->get_transient( $this->encode_key, array() ); unset( $encodes[ $encode_key ] ); set_transient( $this->encode_key, $encodes ); delete_transient( $encode_key . '_progress' ); } function add_to_failed( $encode_key, $type_file ) { $failed = $this->get_transient( $this->failed_key, array() ); $failed[ $encode_key ] = $type_file; set_transient( $this->failed_key, $failed ); } /** * Process the passed item * * @param int $id * @param string $type * @param string $type_file */ function encode_media( $id, $type, $type_file ) { $fallback_types = array( 'mp3', 'ogg', 'ogv', 'webm', 'mp4' ); $encode_key = $id . '_' . $type; if ( ! in_array( $type, $fallback_types ) || file_exists( $type_file ) ) { $this->remove_encode( $encode_key ); return; } $file = get_attached_file( $id ); if ( 'm4a' === substr( $file, -3 ) && 'ogg' === $type ) { $fallback = get_post_meta( $id, '_mp3_fallback', true ); if ( ! empty( $fallback ) ) { $file = get_attached_file( $fallback ); } } // Autoload PHP-FFMpeg + dependencies require_once( AV_DIR . '/vendor/autoload.php' ); $ffmpeg = $this->get_ffmpeg(); $media = $ffmpeg->open( $file ); $attachment = get_post( $id, ARRAY_A ); $props = $attachment; unset( $props['post_modified_gmt'], $props['post_modified'], $props['post_mime_type'], $props['guid'], $props['post_name'], $props['ID'] ); $thumbnail_id = get_post_thumbnail_id( $id ); $original_meta = wp_get_attachment_metadata( $id ); $key = '_' . $type . '_fallback'; try { switch ( $type ) { case 'mp3': $format = new FFMpeg\Format\Audio\Mp3(); break; case 'ogg': $format = new FFMpeg\Format\Audio\Vorbis(); break; case 'ogv': $format = new FFMpeg\Format\Video\Ogg(); break; case 'webm': $format = new FFMpeg\Format\Video\WebM(); break; case 'mp4': $format = new FFMpeg\Format\Video\X264(); break; } $progress_key = $id . '_' . $type . '_' . 'progress'; $format->on( 'progress', function ( $media, $format, $percentage ) use ( $progress_key ) { static $last_progress = false; if ( ! $last_progress ) { $last_progress = $percentage; } if ( $percentage && $percentage !== $last_progress ) { $last_progress = $percentage; set_transient( $progress_key, $last_progress ); } } ); $media->save( $format, $type_file ); } catch ( Exception $e ) { $this->log( 'Caught exception: ' . $e->getMessage() ); unlink( $type_file ); $this->remove_encode( $encode_key ); $this->add_to_failed( $encode_key, $type_file ); $this->check_queue(); return; } $type_ext = wp_check_filetype( $type_file ); $props['post_mime_type'] = $type_ext['type']; $attachment_id = wp_insert_attachment( $props, $type_file ); update_post_meta( $id, $key, $attachment_id ); update_post_meta( $attachment_id, '_is_fallback', true ); if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) { require_once( ABSPATH . 'wp-admin/includes/media.php' ); require_once( ABSPATH . 'wp-admin/includes/image.php' ); } $attach_data = wp_generate_attachment_metadata( $attachment_id, $type_file ); foreach ( wp_get_attachment_id3_keys( $attachment, 'display' ) as $key ) { if ( ! empty( $original_meta[ $key ] ) ) { $attach_data[ $key ] = $original_meta[ $key ]; } } wp_update_attachment_metadata( $attachment_id, $attach_data ); if ( ! empty( $thumbnail_id ) ) { set_post_thumbnail( $attachment_id, $thumbnail_id ); } $this->remove_encode( $encode_key ); $this->check_queue(); } /** * Fire a non-blocking request when a new item is being added to queue its fallbacks * * @param array $data * @param int $post_id * @return array */ function wp_generate_attachment_metadata( $data, $post_id ) { $file = get_attached_file( $post_id ); $ext = wp_check_filetype( $file ); switch ( $ext['type'] ) { case 'audio/x-ms-wma': case 'audio/ogg': case 'audio/wav': case 'audio/m4a': case 'audio/mpeg': case 'video/ogg': case 'video/webm': case 'video/mpeg': case 'video/mp4': case 'video/m4v': case 'video/quicktime': case 'video/x-ms-wmv': $url = add_query_arg( array( 'media_id' => $post_id, 'action' => 'av_queue', ), home_url( '/' ) ); $nonced = html_entity_decode( wp_nonce_url( $url, 'av_queue' ) ); $this->log( $nonced ); wp_remote_get( $nonced, array( 'blocking' => false ) ); break; } return $data; } /** * Return an instance of ffmpeg wrapper that does not use threads * Give the process 30 minutes to run for large files * * @return FFMpeg\FFMpeg */ function get_ffmpeg() { return FFMpeg\FFMpeg::create( array( 'ffmpeg.binaries' => $this->ffmpeg_bin, 'ffprobe.binaries' => $this->ffprobe_bin, 'ffmpeg.threads' => 0, 'timeout' => HOUR_IN_SECONDS / 2 ) ); } /** * Output a message in the admin when path options are missing */ function no_ffmpeg_notice() { $plugin_file = plugin_basename( __FILE__ ); ?>

Audio / Video Bonus Pack isn't very useful without ffmpeg. Uninstall the plugin or update your settings.

Encoding media

Delete all queued items

failed ) ): ?>
failed as $key => $file ): ?>

Transcoding failed: %s', $path ); ?>

Delete all failed items