Skip to content

Instantly share code, notes, and snippets.

@RadGH
Last active April 10, 2022 00:05
Show Gist options
  • Save RadGH/874017aae0d9dcd19114 to your computer and use it in GitHub Desktop.
Save RadGH/874017aae0d9dcd19114 to your computer and use it in GitHub Desktop.
WP Utility functions to get youtube/vimeo images and store their thumbnails as attachments
<?php
function video_get_service( $video_url ) {
if ( stristr( $video_url, 'vimeo.com') ) {
return 'vimeo';
}
if ( stristr( $video_url, 'youtube.com' ) || stristr( $video_url, 'youtu.be' ) ) {
return 'youtube';
}
return false;
}
function youtube_get_video_id( $video_url ) {
if ( preg_match( '/(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/', $video_url, $matches ) ) {
return $matches[1];
}
return false;
}
function vimeo_get_video_id( $video_url ) {
if ( preg_match( '/(https?:\/\/)?(www\.)?(player\.)?vimeo\.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/', $video_url, $matches ) ) {
return $matches[5];
}
return false;
}
function video_get_embed_code( $video_url, $user_options = null ) {
static $player_id_increment = 0;
$player_id_increment++;
$service = video_get_service( $video_url );
$default_options = array(
'width' => 1280,
'height' => null, // Calculates to proper aspect ratio, which would be 720.
'autoplay' => false,
'player_id' => 'video-player-' . $player_id_increment,
// Vimeo Specific
'vimeo_autopause' => false,
'vimeo_badge' => false,
'vimeo_byline' => false,
'vimeo_color' => '00adef',
'vimeo_portrait' => false,
'vimeo_title' => false,
// Youtube Specific
'youtube_autohide' => 2,
'youtube_color' => 'red',
'youtube_enablejsapi' => 1,
'youtube_modestbranding' => 1,
);
$options = wp_parse_args( $user_options, $default_options );
if ( $options['height'] === null ) {
$options['height'] = round( $options['width'] * (720/1280) );
}
if ( $service == 'vimeo' ) {
$video_id = vimeo_get_video_id( $video_url );
if ( !$video_id ) return false;
$iframe_args = array(
'autoplay' => $options['autoplay'] ? '1' : '0',
'autopause' => $options['vimeo_autopause'] ? '1' : '0',
'badge' => $options['vimeo_badge'] ? '1' : '0',
'byline' => $options['vimeo_byline'] ? '1' : '0',
'color' => substr(str_replace('#', '', $options['vimeo_color']), 0, 6),
'player_id' => $options['player_id'],
'portrait' => $options['vimeo_portrait'] ? '1' : '0',
'title' => $options['vimeo_title'] ? '1' : '0',
);
$player_url = add_query_arg( $iframe_args, '//player.vimeo.com/video/' . $video_id );
return sprintf(
'<iframe id="%s" src="%s" width="%s" height="%s" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>',
esc_attr( $options['player_id'] ),
esc_attr( $player_url ),
esc_attr( $options['width'] ),
esc_attr( $options['height'] )
);
}
if ( $service == 'youtube' ) {
$video_id = youtube_get_video_id( $video_url );
if ( !$video_id ) return false;
$iframe_args = array(
'autoplay' => $options['autoplay'] ? '1' : '0',
'origin' => site_url(),
'color' => $options['youtube_color'] == 'white' ? 'white' : 'red',
'autohide' => (int) $options['youtube_autohide'],
'enablejsapi' => $options['youtube_enablejsapi'] ? '1' : '0',
'playerapiid' => $options['player_id'],
'modestbranding' => $options['youtube_modestbranding'] ? '1' : '0',
);
$player_url = add_query_arg( $iframe_args, '//www.youtube.com/embed/' . $video_id );
return sprintf(
'<iframe id="%s" src="%s" width="%s" height="%s" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>',
esc_attr( $options['player_id'] ),
esc_attr( $player_url ),
esc_attr( $options['width'] ),
esc_attr( $options['height'] )
);
}
return false;
}
function video_get_image( $video_url ) {
$service = video_get_service( $video_url );
if ( $service == 'vimeo' ) {
$data = vimeo_get_image_attachment( $video_url );
}else if ( $service == 'youtube' ) {
$data = youtube_get_image_attachment( $video_url );
}else{
return false;
}
return empty($data['attachment_id']) ? false : $data['attachment_id'];
}
function youtube_get_image_attachment( $url ) {
$video_id = youtube_get_video_id( $url );
if ( !$video_id ) return false;
$video_image_option = 'youtube-image-' . $video_id;
// Return the image that has already been uploaded for this video ID
if ( $v = get_option( $video_image_option ) ) {
if ( isset($v['attachment_id']) && get_post( (int) $v['attachment_id'] ) ) {
return $v;
}
}
$image_url = 'http://img.youtube.com/vi/'. $video_id .'/0.jpg';
$attachment = ld_handle_upload_from_url( $image_url );
if ( !empty($attachment['attachment_id']) ) {
update_option( $video_image_option, $attachment, false );
return $attachment;
}
return false;
}
function vimeo_get_image_attachment( $url ) {
$video_id = vimeo_get_video_id( $url );
if ( !$video_id ) return false;
$video_image_option = 'vimeo-image-' . $video_id;
// Return the image that has already been uploaded for this video ID
if ( $v = get_option( $video_image_option ) ) {
if ( isset($v['attachment_id']) && get_post( (int) $v['attachment_id'] ) ) {
return $v;
}
}
$video_data = vimeo_get_info_by_url( $url );
if ( $video_data && !empty($video_data['thumbnail_url']) ) {
$image_url = $video_data['thumbnail_url'];
$attachment = ld_handle_upload_from_url( $image_url );
if ( !empty($attachment['attachment_id']) ) {
update_option( $video_image_option, $attachment, false );
return $attachment;
}
}
return false;
}
function vimeo_get_info_by_url( $url, $option_id = null ) {
/*
Here is the result on a successful request, and also what will be cached. The ellipsis are added by me, you'll get full responses:
{
type: "video",
version: "1.0",
provider_name: "Vimeo",
provider_url: "https://vimeo.com/",
title: "PORTRAIT",
author_name: "MILKYEYES - donato sansone",
author_url: "http://vimeo.com/milkyeyes",
is_plus: "0",
html: "<iframe src="//player.vimeo.com/video/84241262" width="1280" ...></iframe>",
width: 1280,
height: 600,
duration: 171,
description: "A slow and surreal video|slideshow of nightmareish, grotesque and a...",
thumbnail_url: "http://b.vimeocdn.com/ts/461/023/461023899_1280.jpg",
thumbnail_width: 1280,
thumbnail_height: 600,
video_id: 84241262
// WordPress imports the thumbnail, giving you a full size embed code as well as the attachment ID so you can obtain your own.
// Note that this image is attached to the post obtained by get_the_ID()
media: "<img src=\"http://dyscover.limelightmethod.com/wp-content/uploads/2013/10/461023899_128020.jpg\" alt=\"Vimeo: PORTRAIT\">",
media_id: 437,
}
*/
// If no specific option ID is provided, create our own using the URL as input.
if ( $option_id === null ) {
$option_id = 'vimeo-' . sanitize_title( preg_replace( '/(https?:\/\/(www\.)?vimeo\.com\/)?/', '', $url ) );
}
if ( $option_id == 'vimeo-' ) {
return false;
}
// Get the results from cache if we have it
$from_cache = get_option( $option_id );
if ( $from_cache ) {
return $from_cache;
}
// We don't have cache, perform an oembed API request
$oembed_url = sprintf( 'http://vimeo.com/api/oembed.json?url=%s', $url );
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $oembed_url );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, false );
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 10 );
$data_raw = curl_exec( $ch );
curl_close( $ch );
if ( !$data_raw ) {
return false;
}
$data = json_decode( $data_raw, true );
if ( !$data ) {
return false;
}
if ( $data['thumbnail_url'] ) {
// Sideload image using media_sideload_image: http://codex.wordpress.org/Function_Reference/media_sideload_image
require_once( ABSPATH . 'wp-admin/includes/media.php' );
require_once( ABSPATH . 'wp-admin/includes/file.php' );
require_once( ABSPATH . 'wp-admin/includes/image.php' );
$data['media'] = media_sideload_image( $data['thumbnail_url'], get_the_ID(), 'Vimeo: ' . $data['title'] );
$data['media_id'] = image_get_attachment_id( $data['media'] );
}
update_option( $option_id, $data, false );
return $data;
}
/**
* Retrives an image from a URL and uploads it using ld_handle_upload_from_path. See that function for more details.
*
* Note: This function should also work for local file paths as well, but the implementation is slightly different than ld_handle_upload_from_path.
*
* @param $image_url
* @param int $attach_to_post
* @param bool|true $add_to_media
* @return array
*/
function ld_handle_upload_from_url( $image_url, $attach_to_post = 0, $add_to_media = true ) {
$remote_image = fopen($image_url, 'r');
if ( !$remote_image ) return false;
$meta = stream_get_meta_data( $remote_image );
$image_meta = false;
$image_filetype = false;
if ( $meta && !empty($meta['wrapper_data']) ) {
foreach( $meta['wrapper_data'] as $v ) {
if ( preg_match('/Content\-Type: ?((image)\/?(jpe?g|png|gif|bmp))/i', $v, $matches ) ) {
$image_meta = $matches[1];
$image_filetype = $matches[3];
}
}
}
// Resource did not provide an image.
if ( !$image_meta ) return false;
$v = basename($image_url);
if ( $v && strlen($v) > 6 ) {
// Create a filename from the URL's file, if it is long enough
$path = $v;
}else{
// Short filenames should use the path from the URL (not domain)
$url_parsed = parse_url( $image_url );
$path = isset($url_parsed['path']) ? $url_parsed['path'] : $image_url;
}
$path = preg_replace('/(https?:|\/|www\.|\.[a-zA-Z]{2,4}$)/i', '', $path );
$filename_no_ext = sanitize_title_with_dashes( $path, '', 'save' );
$extension = $image_filetype;
$filename = $filename_no_ext . "." . $extension;
// Simulate uploading a file through $_FILES. We need a temporary file for this.
$stream_content = stream_get_contents( $remote_image );
$tmp = tmpfile();
$tmp_path = stream_get_meta_data( $tmp )['uri'];
fwrite( $tmp, $stream_content );
fseek( $tmp, 0 ); // If we don't do this, WordPress thinks the file is empty
$fake_FILE = array(
'name' => $filename,
'type' => 'image/' . $extension,
'tmp_name' => $tmp_path,
'error' => UPLOAD_ERR_OK,
'size' => strlen( $stream_content ),
);
// Trick is_uploaded_file() by adding it to the superglobal
$_FILES[basename( $tmp_path )] = $fake_FILE;
// For wp_handle_upload to work:
include_once ABSPATH . 'wp-admin/includes/media.php';
include_once ABSPATH . 'wp-admin/includes/file.php';
include_once ABSPATH . 'wp-admin/includes/image.php';
$result = wp_handle_upload( $fake_FILE, array(
'test_form' => false,
'action' => 'local',
) );
fclose( $tmp ); // Close tmp file
@unlink( $tmp_path ); // Delete the tmp file. Closing it should also delete it, so hide any warnings with @
unset( $_FILES[basename( $tmp_path )] ); // Clean up our $_FILES mess.
fclose( $remote_image ); // Close the opened image resource
$result['attachment_id'] = 0;
if ( empty( $result['error'] ) && $add_to_media ) {
$args = array(
'post_title' => $filename_no_ext,
'post_content' => '',
'post_status' => 'publish',
'post_mime_type' => $result['type'],
);
$result['attachment_id'] = wp_insert_attachment( $args, $result['file'], $attach_to_post );
$attach_data = wp_generate_attachment_metadata( $result['attachment_id'], $result['file'] );
wp_update_attachment_metadata( $result['attachment_id'], $attach_data );
if ( is_wp_error( $result['attachment_id'] ) ) {
$result['attachment_id'] = 0;
}
}
return $result;
}
/**
* Takes a path to a file, simulates an upload and passes it through wp_handle_upload. If $add_to_media
* is set to true (default), the file will appear under Media in the dashboard. Otherwise, it's hidden,
* but stored in the uploads folder.
*
* Return Values: Similar to wp_handle_upload, but with attachment_id:
* - Success: Returns an array including file, url, type, attachment_id.
* - Failure: Returns an array with the key "error" and a value including the error message.
*
* @param $path
* @param int $attach_to_post
* @param bool $add_to_media
*
* @return array
*/
function ld_handle_upload_from_path( $path, $attach_to_post = 0, $add_to_media = true ) {
if ( !file_exists( $path ) ) {
return array( 'error' => 'File does not exist.' );
}
$filename = basename( $path );
$filename_no_ext = pathinfo( $path, PATHINFO_FILENAME );
$extension = pathinfo( $path, PATHINFO_EXTENSION );
// Simulate uploading a file through $_FILES. We need a temporary file for this.
$tmp = tmpfile();
$tmp_path = stream_get_meta_data( $tmp )['uri'];
fwrite( $tmp, file_get_contents( $path ) );
fseek( $tmp, 0 ); // If we don't do this, WordPress thinks the file is empty
$fake_FILE = array(
'name' => $filename,
'type' => 'image/' . $extension,
'tmp_name' => $tmp_path,
'error' => UPLOAD_ERR_OK,
'size' => filesize( $path ),
);
// Trick is_uploaded_file() by adding it to the superglobal
$_FILES[basename( $tmp_path )] = $fake_FILE;
// For wp_handle_upload to work:
include_once ABSPATH . 'wp-admin/includes/media.php';
include_once ABSPATH . 'wp-admin/includes/file.php';
include_once ABSPATH . 'wp-admin/includes/image.php';
$result = wp_handle_upload( $fake_FILE, array(
'test_form' => false,
'action' => 'local',
) );
fclose( $tmp ); // Close tmp file
@unlink( $tmp_path ); // Delete the tmp file. Closing it should also delete it, so hide any warnings with @
unset( $_FILES[basename( $tmp_path )] ); // Clean up our $_FILES mess.
$result['attachment_id'] = 0;
if ( empty( $result['error'] ) && $add_to_media ) {
$args = array(
'post_title' => $filename_no_ext,
'post_content' => '',
'post_status' => 'publish',
'post_mime_type' => $result['type'],
);
$result['attachment_id'] = wp_insert_attachment( $args, $result['file'], $attach_to_post );
$attach_data = wp_generate_attachment_metadata( $result['attachment_id'], $result['file'] );
wp_update_attachment_metadata( $result['attachment_id'], $attach_data );
if ( is_wp_error( $result['attachment_id'] ) ) {
$result['attachment_id'] = 0;
}
}
return $result;
}
@shikkaba
Copy link

Are you supposed to use the entire thing? Sorry, I'm a little slow today.

@RadGH
Copy link
Author

RadGH commented Mar 11, 2022

Yeah but take a look at oembed, a standardized way to get video thumbnails and other data:

https://wordpress.org/support/article/embeds/

I would not recommend using my original code, it's quite old

@shikkaba
Copy link

I ended up using oembed using ACF's video field, and pulling images from there, and adding as a featured image on save. Thanks for the answer!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment