ZeroScript

ゼロからわかるスクリプト

【コピペ】6秒でアイキャッチ画像を自動生成する方法

f:id:tabemi:20210527211739p:plain
これも自動生成した

どうも、たべみです。

突然ですが、アイキャッチ作成って大変ですよね。。

そこで、、

///////////////////////////////////////

全ブロガーに告ぐ!!!
アイキャッチ作成時間を放棄せよ!

\\\\\\\\\\\\\\\\\\//////////////////////



ふう。
言ってみたかった。。


今日は、画像に自動でテキストを乗せるスクリプトをご紹介します。

簡単なアイキャッチ画像を自動で作って時短していきましょう。

完全コピペでOKです。


できること


素材の画像にテキストを挿入し、png形式で保存する

これが今回のツールでできることとなります。

f:id:tabemi:20210527223946p:plain
こんな感じに表を埋めると、
f:id:tabemi:20210527223940p:plain
画面右側でアイキャッチ画像が生成されていきます


挿入したい文字は、

  • 改行OK
  • 文字の大きさ自動調整

できます。

なーーーんも考えずにボタンを押すだけで、アイキャッチ画像を作成できます!


準備するもの

自動化するためにGoogleドライブとGoogleスプレッドシートを使用します。

それらを用意し、
コードをコピペする準備をします。

Googleスプレッドシートを新規で作成する

それではスプレッドシートを準備しましょう。

  1. Googleドライブを開く
  2. 画面左上の 新規+ ボタンをクリック
  3. スプレッドシートを選択
f:id:tabemi:20210523162245p:plain:w500
新規+ ボタンをクリック
f:id:tabemi:20210523162249p:plain:w350
スプレッドシートを選択


これで新しいスプレッドシートが作成できました

あとは、コードをコピペをする場所を整えていきます!

そんなに難しくないので安心してください

スクリプトエディタを開く

コードをコピペする場所であるスクリプトエディタというものを開きます。

  1. 画面上部「ツール」をクリック
  2. 「<> スクリプトエディタ」をクリック
f:id:tabemi:20210523162415p:plain
スクリプトエディタを開く方法

それっぽい画面が出てくればOKです!

ファイルを準備する

今回コードをコピペするには2つの場所が必要となります。
それが、

  • index.html
  • コード.gs

です!

このうち、index.html が存在していないため作成をします。

  1. 「ファイル +」のプラスをクリック
  2. 「HTML」をクリック
  3. 「無題」を「index」とする
f:id:tabemi:20210527222136p:plain:w350
プラスマークをクリックしてHTMLを選択
f:id:tabemi:20210527222140p:plain:w250
index と入力


これで、コピペをする準備の終了です!!


コピペする

では実際にソースコードを貼り付けていきましょう。

今回は、

  • index.js
  • コード.gs

の2つの場所にそれぞれ貼り付けていくので、間違わないようにお気を付けください!


index.html に貼り付け

まずはindex.htmlです。


削除)

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    
  </body>
</html>

既に書いてあるものをすべて削除してください!

そして、下のコードをそのままペーストしてください。

貼付)

<head>
	<base target="_top">


<script type="text/JavaScript">
	google.script.run.withSuccessHandler(main).withFailureHandler(missFunc).getAlldata();
  let limit = 0;
  let i = 0;//開始数
  let end = 0;//処理終了数
  function logger(){
    console.log(`${i} コの処理を開始済み\n${end} コの処理を完了\n残り ${limit-end} コ`);
  }
  function main(json){
    const orders = JSON.parse(json);
    limit = orders.length;
    const timeInterval = 300;
    const timer = setInterval (() =>{
      if( i -end < 25 ){//30でもいいけど、なにかしらほかのプロジェクトのトリガーとかonOpenが起動しないとまずい。
        const order = orders[i]//1つのimgDataを取得
        //GASにデータを渡し、更新スタート!!ルンルン
        google.script.run.withSuccessHandler(makeEyeCatch).withFailureHandler(missFunc).encode(order);
      }
      i++;
      logger();
      if(i===limit) clearInterval(timer);//処理終了
    },timeInterval);
  }

  function makeEyeCatch({row, imageEncode, title}) {
    const board = document.getElementById("board");  //getElementById()等でも可。オブジェクトが取れれば良い。
    const ctx = board.getContext("2d");
    const chara = new Image();
    chara.src = imageEncode;
    
    chara.onload = () => {
      // console.log(chara);
      const imgHeight = chara.naturalHeight;
      const imgWidth = chara.naturalWidth;
      board.height = imgHeight;
      board.width = imgWidth;
      ctx.drawImage(chara, 0, 0);//画像サイズのままcanvasの生成

      /**
       * @type {Array}
       */
      const titles = title.split('\n');
      const moji = new Moji(titles, imgHeight, imgWidth);
      const size = moji.getSize();
      titles.forEach( (text,  i) => {
        const {x, y} = moji.getXYPotision(i);
        ctx.font = size +'px メイリオ';
        ctx.textAlign = "left";
        ctx.fillText(text,x, y);
      });

      const fileName = titles.join('-');
      const imageType = 'image/png';
      const base64 = board.toDataURL(imageType);
      const data = { fileName, imageType, base64, row };
      google.script.run.withSuccessHandler(setState).withFailureHandler(missFunc).saveEyeCatchImg2Folder(data);
    };
  }
  function setState(text){
    const p = document.createElement('p');//テキストで表示する
    p.textContent = "finish: " +text;
    const box = document.getElementById('box');
    box.appendChild( p );//Box要素に入れる
    end++;//処理完了を一つ増やす
    logger();
  }
  
  class Moji {
    constructor(titles, imgHeight, imgWidth) {
      this.titles = titles;
      // const posibleHeight = imgHeight *0.9;//90%の縦幅で文字を配置
      const posibleWidth = imgWidth *0.9;//90%の横幅で文字を配置する

      const getMojiSize = () => {
        // console.log(imgWidth + ":width")
        const maxSize = posibleWidth *0.085;//最大文字サイズ //1200pxなら 1200*0.9*0.08 = 約92px
        const maxMojiLength = this.titles.reduce((number, x) => {
          if (x.length > number) return x.length;
          return number;
        }, 0);
        const size = Math.floor(posibleWidth /maxMojiLength)  //幅から文字数を割って1文字当たりの大きさを出す。
        // console.log(maxMojiLength +"moji => " +size +"px" +", ※max:" +maxSize );
        return maxSize < size ? maxSize: size;//90Px(仮)より大きかったら90px。小さければsize;  
      }
      this.imgWidth = imgWidth;
      this.mojiSize = getMojiSize();
      this.gyo_kan = this.mojiSize *0.4;

      const getInitY = () => {
        const numOfGyo = this.titles.length;
        const mojisHeight = numOfGyo *this.mojiSize;//すべての行の合計高さ
        const gyo_kansHeight = (numOfGyo-1) *this.gyo_kan;//すべての行間の合計高さ
        const mojiBoxesHeight =  mojisHeight +gyo_kansHeight;
        return ( imgHeight -mojiBoxesHeight) /2;
      }

      this.initY = getInitY();
    }
    getSize() {
      return this.mojiSize;
    }
    getXYPotision(i){
      const titleLength = this.titles[i].length;
      const boxWidth = titleLength *this.mojiSize;
      // console.log(boxWidth + "px .boxWidth")
      const x = (this.imgWidth -boxWidth)/2;
      const boxHeight = this.mojiSize +this.gyo_kan;
      const y = (i *boxHeight) +this.initY;
      return {x, y};
    }
    
  }


  function missFunc(){
    const p = document.createElement('p');//完成した料理をテキストで表示する
    p.textContent = 'error';
    p.style.color = 'red';
    const box = document.getElementById('box');
    box.appendChild( p );//Box要素に入れる
  }

</script>
</head>
<div id="box"></div>
<canvas id="board"></canvas>
<canvas id="canvas"> </canvas>
<img id="img" />


すべてもれなくコピペしてください!

コード.gs に貼り付け

続けてコード.gsです。

削除)

function myFunction() {

}


こちらをすべて削除し
下のコードをこちらももれなく貼り付けてください

貼付)

const SHEET_NAME = 'アイキャッチ画像';
const SPREAD_SHEET = SpreadsheetApp.getActiveSpreadsheet();
function initFunction() {
  const sheet = new Sheet(SHEET_NAME);
  const header = [['画像URL', 'アイキャッチタイトル', 'アイキャッチURL']];
  sheet.setValues(header);
  const folderId = DriveApp.createFolder('アイキャッチ画像').getId();
  PropertiesService.getDocumentProperties().setProperty('folderId', folderId);
  ScriptApp.newTrigger("onOpen").forSpreadsheet(SPREAD_SHEET).onOpen().create();//起動時にonOpen関数を実行するトリガーを設置する
}
/**
 * SSにメニューを表示
 */
function onOpen() {
  
  const myMenu = [
    { name: 'アイキャッチ画像生成', functionName: 'openSidebar_' }
  ];
  SPREAD_SHEET.addMenu('自動化ツール', myMenu); //メニューを追加
}

function openSidebar_() {
  const htmlOutput = HtmlService.createTemplateFromFile('index').evaluate().setTitle('ゼロから使える/ZeroScript');
  SpreadsheetApp.getUi().showSidebar(htmlOutput);
}

/**
 * スプレッドシート上の未生成アイキャッチ画像データを返す
 */
function getAlldata() {
  const [header, ...v] = new Sheet(SHEET_NAME).getDataRangeValues();
  if (!v) return [];
  const allData = v.reduce((array, x, i) => {
    if (x[2]) return array;//すでにあれば
    const row = i + 2;//sheetのIndex(starting1)
    const imageUrl = x[0];
    const title = x[1];
    return [ ...array, {row, imageUrl, title} ];
  }, []);
  console.log(allData);
  return JSON.stringify(allData);
}
/**
 * share用Urlからidに変換する
 */
function replaceUrl2id_ (url) {
  const start = url.indexOf('/d/') + 3;
  const end = url.indexOf('/view?usp=sharing');
  return url.substring(start, end);
  // return "https://drive.google.com/uc?id=" +  id;
}
function encode({row, imageUrl, title}){
  const id = replaceUrl2id_(imageUrl);
  var f = DriveApp.getFileById(id);//画像
  var b = f.getBlob();
  var imageEncode = 'data:image/jpeg;base64,' +Utilities.base64Encode(b.getBytes());
  return {row, imageEncode, title};
//  console.log(text);
}
/**
 * @param {blob}
 */
function saveEyeCatchImg2Folder(data) {
  const folderId = PropertiesService.getDocumentProperties().getProperty('folderId');
  const folder = DriveApp.getFolderById(folderId);
  const { fileName, imageType, row } = data;
  const base64 = data["base64"].replace('data:image/png;base64,', '');
  const decoded = Utilities.base64Decode(base64);
  const blob = Utilities.newBlob(decoded, imageType, fileName);
  const url = folder.createFile(blob).getUrl();
  const column = 3;
  new Sheet(SHEET_NAME).setValue(url, row, column);
  return fileName;
}


class Sheet {
  constructor(sheetName) {
    const SS = SpreadsheetApp.getActiveSpreadsheet();
    let sheet = SS.getSheetByName(sheetName);
    if (!sheet) sheet = SS.insertSheet().setName(sheetName);
    this.sheet = sheet;
  }
  /**
   * @return {Array[]}
   */
  getDataRangeValues() {
    return this.sheet.getDataRange().getValues();
  }
  /**
   * @param {Array[]} values
   */
  setValues(values) {
    const row = 1;
    const column = 1;
    const rowNums = values.length;
    const numColumns = values[0].length;
    this.sheet.getRange(row, column, rowNums, numColumns).setValues(values);
  }

  /**
   * @param {string || number} value
   * @param {number} row
   * @param {number} column
   */
  setValue(value, row, column) {
    this.sheet.getRange(row, column).setValue(value);
  }
}


コードのコピペは以上となります。

保存する

コードを削除して貼り付けると、ファイル名のところに赤いポチが付いたのに気が付きましたでしょうか?

これは、未保存ということなので、保存してあげます

  1. Windows=>「Ctrl+s」、Macの方「Comd+s」

これで赤いポチがなくなったかと思います。


▶ボタンを押してみる

  1. コード.gs を開く
  2. ▶ボタンを押す


ペーストして物々しい雰囲気となったエディタ画面の上部に注目してみましょう。

再生ボタンのようなアイコンがあるかと思います。押してみます。

f:id:tabemi:20210523162409p:plain
実行ボタンをクリック

すると、、ウィンドウが出てきますね。。

f:id:tabemi:20210523162329p:plain
ウィンドウが出てくる。「権限を確認」をクリック。キャンセルは押しません。

承認画面を承認する

これは、「あなたに代わってプログラムが色々するけど、大丈夫かい?」という許可をGoogleが求めてきています。許可されない場合、このプログラムは動きませんので、承認しましょう

なにか情報を抜き取るコードなんじゃないの?!

大丈夫です笑

以前私は本気で心配していたんですよね。

だから何度でも言います。大丈夫です。



この認証画面をクリアする手順は、こちらです。

  1. Googleアカウントを選択
  2. 左下「詳細」>「 安全でないページに移動」をクリック
  3. 下にスクロール「許可」をクリック
f:id:tabemi:20210523162340p:plain:w200
Googleアカウントを選択
f:id:tabemi:20210523162346p:plain:w350
詳細をクリック
f:id:tabemi:20210523162354p:plain:w350
焦りますが、「安全ではないページに移動」をクリック
f:id:tabemi:20210523162404p:plain:w350
スクロールして「許可」を選択


そしたら、コードが実行できるようになっているので、
もう一度「再生ボタン」を押してみましょう

エディタ画面
出てくる表示は少し違います。

黄色く「実行完了」が出ていればOKです!
でない、という方は、もう一度▶ボタンを押してみましょう。


以上で、自動生成ができるようになっています!
お疲れ様でした!!

つぎは、使い方を見ていきましょう。

アイキャッチ自動生成ツールの使い方

先ほど作成した、スプレッドシートを見てみましょう。


すると、「アイキャッチ画像」というシートが追加されています。

そして、上部「アドオン ヘルプ」の並びに「自動化ツール」と表示されています。ないよという方、リロードしてみてください。

f:id:tabemi:20210527223946p:plain
「自動化ツール」があればOKです!

使い方概要


使い方の手順をざっと説明すると以下のようになります!

  1. A列にGoogleドライブにアップロードした画像のURLを貼り付ける
  2. B列に画像に入れたい文字を入力する(改行は Ctrl+Enter)
  3. 「自動化ツール」>「アイキャッチ画像生成」をクリック
  4. サイドバー出現
  5. 自動生成開始

Googleドライブへのアップロード方法とUrlの取得方法は、下記にて解説しています。


自動生成されたアイキャッチ画像はGoogleドライブの「アイキャッチ画像」というフォルダの中に保存されています!

f:id:tabemi:20210527223940p:plain
せっまいサイドバーで画像を作っています。。

Googleドライブに画像をアップロードする方法

素材となる画像は、あらかじめGoogleドライブにアップロードする必要があります。

アップロード方法はとっても簡単です。

  1. Googleドライブを開く
  2. 画面左上の 新規+ ボタンをクリック
  3. ファイルをアップロード を選択
  4. アップロードしたいファイルを選択する
f:id:tabemi:20210523162245p:plain:w500
新規+ ボタンをクリック
f:id:tabemi:20210527211853p:plain:w350
ファイルをアップロードを選択します

以上でアップロードができました!

ちなみにアップロードする場所(フォルダ)はどこでも大丈夫ですよ

画像のURLを取得する

スプレッドシートA列には素材となる画像のULRを貼り付ける必要がありました。
そのURLを取得する方法をご紹介します!

  1. アップロードした画像ファイルを右クリック
  2. 「リンクを取得」をクリック
  3. 「リンクをコピー」をクリック
f:id:tabemi:20210527211858p:plain:w400
対象の画像ファイルを右クリックして「リンクを取得」をクリック
f:id:tabemi:20210527211848p:plain:w350
「リンクをコピー」でコピーします

ここで取得したURLをA列に貼り付けましょう!!


解説は以上となります!

最後に

実は、画像に描写して保存するって作業がムリゲー(死語?)だったりするんですが、GASでエンコードして投げることで回避できました。

詳しい方は、例の「汚染」を回避する方法といえば伝わるでしょうか、、

.toDataURL()ができない、
tainted canvases may not be exported です。

私もあまり詳しくはない、いや全然よく分からないのですが、とにかくなんとかなりました。。


というわけで、今日は「6秒でアイキャッチ画像を自動生成する方法」でした。

よいブログライフをお送りください。

以上、たべみでした

ではでは~