🎞️

YouTubeの動画サムネイルをWordPressの投稿サムネイルとして自動挿入するカスタムフィールドを作成するコード

2023/08/07に公開

前置き

僕がこんなもの作らなくてもこういうプラグインがある。
(今回は要件に合わなかった)

Featured Video Plus
https://ja.wordpress.org/plugins/videographywp/

Video Thumbnails Reloaded
https://wordpress.org/plugins/video-thumbnails-reloaded/

コード

functions.php
<?php
// -----------------------------------
// YouTubeの動画サムネイルをWordPressの投稿サムネイルとして自動挿入する
// -----------------------------------

// カスタムフィールドを作る
// [参考]https://camel-press.com/media/customfield/
add_action('admin_menu', 'create_custom_fields');
function create_custom_fields()
{
    add_meta_box(
        'youtube-settings', //編集画面セクションID
        'YouTubeのリンク', //編集画面セクションのタイトル
        'insert_custom_fields', //編集画面セクションにHTML出力する関数
        'post', //投稿タイプ名
        'normal' //編集画面セクションが表示される部分
    );
}

// 入力フォームの作成と、スクリプトの挿入
function insert_custom_fields()
{
    global $post;
    // 登録してある値を取得(無ければ空文字を代入)
    $youtube_url = get_post_meta($post->ID, 'youtube-url', true) ?: "";
    $youtube_thumbnail = get_the_post_thumbnail_url($post -> ID) ?: "";
    ?>

<form method="post" action="admin.php?page=site_settings">
    <table class="form-table cptui-table">
      <tbody>
        <tr>
          <th scope="row"><label for="set-url">URL</label> <span class="required">*</span></th>
          <td>
            <input aria-required="true" id="set-url" name="youtube-url" required="true" type="text" value="<?php echo $youtube_url;?>">
          </td>
        </tr>
        <tr>
          <th scope="row"><label for="preview-thumbnail">サムネイル</label></th>
          <td>
            <div id="preview-thumbnail">
              <?php if($youtube_thumbnail): ?>
                <img src="<?php echo $youtube_thumbnail?>" alt="" width="500">
              <?php else:?>
                <p>未取得</p>
              <?php endif;?>
            </div>
            <input type="hidden" id="set-thumbnail" name="youtube-thumbnail" value="<?php echo $youtube_thumbnail;?>">
          </td>
        </tr>
      </tbody>
    </table>
  <script>
    // サムネイルを取得
    // [出典]https://zenn.dev/attt/articles/get-yt-thumbnail
    window.addEventListener("DOMContentLoaded", () => {
      const input = document.getElementById("set-url");
      let _box = document.getElementById("preview-thumbnail");
      let _send_box = document.getElementById("set-thumbnail");
      // 正規表現で動画idを切り出す
      let regex = /(https?:\/\/)?(((m|www)\.)?(youtube(-nocookie)?|youtube.googleapis)\.com.*(v\/|v=|vi=|vi\/|e\/|embed\/|user\/.*\/u\/\d+\/)|youtu\.be\/)([_0-9a-z-]+)/i;
      input.addEventListener("input", ()=> {
        let _videoUrl = input.value;
        if(_videoUrl) {
          let _videoId = _videoUrl.match(regex)[8];
  
          (async () => {
            const thumbnail = await getYtThumbnail(_videoId);
            _send_box.value = thumbnail;
            let _img = document.createElement("img");
            _img.setAttribute("src", thumbnail);
            _img.setAttribute("width", "500");
            _box.innerHTML = "";
            _box.appendChild(_img);
          })();
        }
      });
    });
    const THUMB_TYPES = ['maxresdefault.jpg','sddefault.jpg','hqdefault.jpg','mqdefault.jpg','default.jpg'];
    const getYtThumbnail = async (videoId) => {
      // 画像をロードする処理
      const loadImage = (src) => {
        return new Promise((resolve, reject) => {
          const img = new Image();
          img.onload = (e) => resolve(img);
          img.src = src;
        });
      };

      for (let i = 0; i < THUMB_TYPES.length; i++) {
        const fileName = `https://img.youtube.com/vi/${videoId}/${THUMB_TYPES[i]}`;
        const res = await loadImage(fileName);
        // ダミー画像じゃなかったら(横幅が121px以上だったら)
        // もしくは、これ以上小さい解像度が無かった場合は、このURLで決定
        if (
          !THUMB_TYPES[i + 1]
          || (res).width > 120
        ) {
          return fileName;
        }
      }
    };
  </script>
</form>
<?php
}

// 保存する
function save_custom_fields($post_id)
{
  if($_POST['youtube-thumbnail'] && $_POST['youtube-url']) {  
    // サムネイルを保存
    $media_url = $_POST['youtube-thumbnail'];
    $tmp = download_url($media_url);// 画像をDLし、一時ファイルに保存 

    // 手動でサムネイルを差し替えたときのエラーハンドリング
    if (!is_wp_error($tmp)) {
      // ファイルをアップロードディレクトリに移動
      $upload_dir = wp_upload_dir();
      $file_extension = pathinfo($media_url, PATHINFO_EXTENSION);
      $file_name = str_replace('.', '', microtime(true)) . '.' . $file_extension; // ユニークなファイル名にする(キャッシュ対策)
      $uploaded_file = $upload_dir['path'] . '/' . $file_name;
      rename($tmp, $uploaded_file);

      // 添付ファイルの情報を設定
      $media_title = 'thumbnail';
      $media_description = 'from : youtube';
      $file_type = wp_check_filetype($file_name, null);
      $attachment = array(
          'post_mime_type' => $file_type['type'],
          'post_title' => $media_title,
          'post_content' => $media_description,
          'post_status' => 'inherit',
          'timestamp' => time()
      );

      // メディアを挿入し、添付ファイルとして投稿に関連付ける
      $attach_id = wp_insert_attachment($attachment, $uploaded_file, $post_id);
  
      // 添付ファイルのメタデータを生成
      require_once(ABSPATH . 'wp-admin/includes/image.php');
      $attach_data = wp_generate_attachment_metadata($attach_id, $uploaded_file);
      wp_update_attachment_metadata($attach_id, $attach_data);

      // 投稿のサムネイルにアップロードした画像を設定
      set_post_thumbnail($post_id, $attach_id);
    }


    // カスタムフィールドを保存
    update_post_meta($post_id, 'youtube-url', $_POST['youtube-url']);
  }
}
add_action('save_post', 'save_custom_fields');
?>

参考コード

コード内にもあるけどあらためて書いておく。

カスタムフィールドを自作する方法【プラグインなし】
https://camel-press.com/media/customfield/
ブクマしていつも見てる

【JS】YouTubeの動画IDからなるべく大きい解像度のサムネイルを取得する方法(API使わずに)
https://zenn.dev/attt/articles/get-yt-thumbnail
面倒な部分を考えずに済んで助かった

ちなみに正規表現でYouTubeのURLから動画idを切り出すコードは
youtube video id regexで調べるといっぱい出てくる。

考慮ポイント

  • 手動でサムネイルを差し替えても大丈夫
  • ファイル名を重複しないよう(UNIXTIME)にすることでサーバーキャッシュを回避(同じファイル名で画像を上げると、消した画像が表示される現象が起こる)

残念ポイント

  • 動画のサムネが変わっても投稿には反映されない(その代わりYouTubeでエラーが起きたり動画が消えたりしてもサムネが消えないし、ページロードも早い)

Discussion