多くの人(?)が通る道、保育園のアルバム委員の作業をGASで効率化した話


背景

保育園の役員や謝恩会の委員、アルバム制作など、多くの人が経験することとと思います。
その中で、アルバム制作の写真集めを、GASを使って効率化した話を書きます。
エンジニアの仕事の効率化ではないことを最初に謝っておきます。ごめんなさい。
長女(9)の時に苦労して、長男(7)の時に効率化した話です。
GASのコードは、初めてGASを書いた状態なので燦々たる状態ですが、現状リファクタリングニーズがないのでこのまま供養します。

課題

子供達の保育園では、卒園アルバム制作が保護者のタスクとなっています。
例年、下記の内容でアルバム制作を進めています。
1. 業者選定
2. 分担(デザイン、撮影、データ)
3. 分担に分かれて作業
4. 納品
5. 配布

この中で、データ担当になり、写真データを集めて管理していた話です。
年にもよりますが、長女の時は、3000枚程度の写真を集めていました
何時間もかけて集めた写真をスキャンしていました、、、、

「各自、スキャンして送ってもらうのが良いが、メールで送ってもらったのを分類するのも大変そう、、、、どうせなら色々と自動でできないかしら。」
と思ったということで、長男の時には、いろんな条件で特定のドライブに移すものを作りました

使うもの

  • Googleメールアドレス
    • 画像メールの受け子
  • Googleドライブ
    • 画像の格納先
  • GoogleAppScript
    • いろいろ(後述)
  • GoogleSpreadsheet
    • いろいろの設定(後述)

GAS + SpreadSheetで行ったこと

  • GAS
    • 新着メールからの複数画像ファイルをSpreadSheetで指定したファイルへの移動
      • メールタイトルとフォルダを対応付け
      • ファイル→メールのトレーサビリティを確保するため、メールの日時をファイル名に付与(ファイルサイズが小さい等の返信をすることがあるので結構重要)
    • SpreadSheetで指定したファイル名への変更
      • 連番_〇〇ちゃん、というprefixを付けて、ソートできるようにした
新着メールからの複数画像ファイルをSpreadSheetで指定したファイルへの移動.gs

function gmailFileUploader(){

  //スプレッドシートを読み込む
  var spreadsheets = SpreadsheetApp.openById(SPREADSHEET_ID);
  var spreadsheet = spreadsheets.getSheetByName("フォルダ");

  //二次元配列に
  var data = spreadsheet.getRange(1,1,spreadsheet.getLastRow(),2).getValues();

  //タイトル名の配列と、ファイル名の配列にする
  folderArray = [];
  titleArray = [];
  for(var i = 0; i < data.length ; i++){
    titleArray.push(data[i][0]);
    folderArray.push(data[i][1]);
  }

  var Threads = GmailApp.getInboxThreads(); 
  for(var i=0;i<100;i++){ 
    if (!Threads[i]){return;}//メールがなければ抜ける
    var status = Threads[i].isUnread();
    var status_save = false;
    if(status==true){
      var subject = Threads[i].getMessages()[0].getSubject();
      for (var k = 0;k < titleArray.length;k++){
        if(data[k] != null && subject==titleArray[k]){//タイトルは0列目に
          for(var i_msg = 0; i_msg < Threads[i].getMessageCount(); i_msg++){
            var msg = Threads[i].getMessages()[i_msg];
            //既読だったら処理を飛ばして次のmsgへ
            if(msg.isUnread() == false){ continue; }
            var attachments = Threads[i].getMessages()[i_msg].getAttachments({includeInlineImages:true});
            var mailfrom = remove_dot(Threads[i].getMessages()[i_msg].getFrom()); 
            var folders = DriveApp.getFoldersByName(folderArray[k]);//フォルダは1列目に
            while(folders.hasNext()){
              var folder = folders.next();
              for(var j = 0;j<attachments.length;j++){
                var data = DriveApp.createFile(attachments[j]);
                //ファイル名に時間を入れる
                var new_file_name = Utilities.formatDate(msg.getDate(),"Asia/Tokyo","yyyyMMdd-HHmmss") + mailfrom + "_" + attachments[j].getName();
                data.setName(new_file_name);
                folder.addFile(data); 
                status_save=true;
              }
            }
            if(status_save){
              msg.markRead();//保存終了したら既読にする
            }
          }
          continue;//一致するタイトルが見つかったら次のループへ
        }
      }
    }
  }  
} 
//あとでメールアドレスから名前に変換するため、メールアドレスの@以前のドットを抜いた状態でファイル名につける
function remove_dot(mail_from){

  var reg = /<.*@.*>/g;
  var ret = "";

  if(mail_from.length > 0 && mail_from.match(reg) != null){
    ret = mail_from.match(reg)[0];
  }else{
    ret = mail_from; 
  }
  ret = ret.split("@")[0].replace(/@*/g,"").split('<').join('').split('.').join('');

  return ret;
}
SpreadSheetで指定したファイル名への変更.gs
function myFunction() {
  //スプレッドシートを読み込む
  var spreadsheets = SpreadsheetApp.openById(SPREADSHEET_ID);
  var spreadsheet = spreadsheets.getSheetByName("よみかえ");

  //エリアのサイズを取得
  var rowSize = spreadsheet.getDataRange().getHeight();
  var colSize = spreadsheet.getDataRange().getWidth();

  //jpegのみ収集
  var files = DriveApp.getFilesByType(MimeType.JPEG);

  var froms = spreadsheet.getRange(1, 1, rowSize, 1).getValues();
  var tos = spreadsheet.getRange(1, 2, rowSize, 1).getValues();
  //ファイル全てに対してループ

  var count = 0;
  while (files.hasNext()) {
    var file = files.next();

    for(var i = 1; i < rowSize ; i++){
      var from = froms[i]
      var name = tos[i]

      //ファイル名に指定文字列が含まれる、かつ、読み替え後文字列が存在すれば読み替える
      if(file.getName().indexOf(from) > 0 && name.length > 0){
        var fname = file.getName();
        file.setName(name + fname.replace(from,""));
        Logger.log(fname);
        count++;
        //読み替えたら次ループへ
        continue;
      }
    }
  }
  Logger.log(count);
}
  • SpreadSheet※個人情報満載なのでつけられません
    • タイトルによる格納先一覧
      • フォルダと、メールタイトルの対応付け
    • メールアドレスによる自動ファイル名付け一覧
      • 送信元メールアドレス(@以前のみ、ドットは削除)と、つけるprefixの対応付け

運用

  • 写真は、専用のメールアドレスにファイル添付で送ってもらうこととする
  • 「2歳児クラス」等の学年や、「5才遠足」等のイベントごとに、メールのタイトルと氏名等を含めたメールを作成するURL・QRコードを作成して一覧配布
    • 同時にLINEでも○歳は、タイトル「○才」で送ってね、という周知
    • (※ページの都合で、「5歳クラス」と「5歳遠足」というMECEじゃない分け方で困りそうな感じなのは私のせいじゃない)

振り返り等々

  • タイトル指定はなかなか難しい(半角全角の表記ゆれ等)
  • メールでデータを送る、が慣れていない人も
  • そもそもスキャンしたくない人も多くいた(そういう人は、スキャンしてまで送りたくない、となって枚数が減ってしまう。写真集めをタスクとしている身としては、目的を満たせないかなり大きな問題)
  • スキャン環境がない人の分は現物を受け取って実施した。以前と比べると現物受け取り枚数は減ったので、スキャンの負担をある程度分散できたように思う
  • この仕組みを使っても使わなくても、スキャン画像の荒さなどの問題がでる
  • 学年間違えて送ったりは目検で救済
  • 複数人から同じ写真が送られてくることがあり、皆が気に入って買っている写真があるのもあったが、人毎に分かれてしまうので、似た写真がわかるといいなと思った

感想

  • GASを当時初めて使ったけれど、便利だと思った、勉強になった!ここから私のGAS生活が始まった、、、!
  • 機械学習用の画像データを集めを複数人で行うときに便利かもと思ったり
  • 個人情報だらけのため、スプレッドシートが出せなくてすみません

今後

  • もう一人、次男(3)の分の仕事が残っています
  • この仕組みをまた使うかは分からないが、次回使うなら、画像サイズでの自動返信を入れたい、、、。「画像サイズが小さいので、担当まで現物を渡してください」のような感じで、、、、
  • GoogleFormの方が簡単かもしれないとちょっと思っています
  • LINEでのコミュニケーションを、Slackに出来たら、まだまだ色々と自動化できそうだな〜と思ったり。(進行スケジュールとか)
  • まあ細かい効率化はありますが、結局コミュニケーションを効率化することが一番、、、、
  • 4人目の予定はありません。たぶん。