執筆: S.G.

はじめに

株式会社ORENDAのS.G.です。

以前はアニメ制作会社に務めていまして、『撮影』という職種でテレビシリーズの2Dアニメの制作に関わっていました。ORENDAでも主にアニメの『撮影』として、CMやテレビシリーズのエンディングなどの短いアニメーション映像の制作に携わっております。

アニメ制作における『撮影』とは、キャラクターの画像(いわゆる『セル』)と背景の画像とを組み合わせて1つの画面を作る作業のことで、業務で扱うのは主に Adobe After Effects(AE)です。

一般的にアニメは1カット単位で制作されます。撮影の工程でもそれは同じです。カットごとの背景とキャラクターの画像の素材を受け取り、それらの素材を画像合成(コンポジット)して、1カットずつレンダリングして書き出した動画ファイルを納品する。これが撮影の業務になります。

ふつうのテレビアニメ1話分のカット数はだいたい300カット弱で、つまり納品する動画ファイルも300ぐらいになります。これが漏れなく揃っているかどうかを確認する作業、それ自体が大変です。世の中には Shotgun などの管理ツールがあるわけですが、アニメ業界ではそれら管理ツールの普及率が高くなく、Microsoft Excel でチェック表を付けているところも未だ珍しくありません。

そんな中で数年前から私は個人的に Googleスプレッドシート + Google Apps Script(以下GAS)によって管理を効率化する試みをしています。プログラマーとして専門的な訓練を受けたわけではなく、また制作の管理それ自体がメインの業務というわけではないので、未だ「試み」程度の段階ではありますが、読んでいただいた方の、なにかしらのとっかかりになればと思い、今回GASによるファイルの集計の例をご紹介いたします。

GASを使った管理ツールのメリットは、以下のようになるかと思います。

・Googleアカウントさえ持っていれば無料で作れる
・実行環境のサーバやデータベースを別途用意する必要がない

デメリットは以下です。

・実行速度が遅く、実行時間に制限がある
・スクリプトの実行の権限を限定するのが難しい(Google Workspace(GSuite)のアカウントであれば、同じ企業ドメインのアカウントにのみ、公開する形をとることはできます)

また、GASは Javascript をベースにしたプログラム言語なので、開発には Javascript の知識が必要となります。この点に関しては、私は同じくJavascriptをベースにしたAdobe ExtendScript(Adobe 製品の自動化ツール)に触ったことがあったので、比較的ハードルが低かったと思います。Web 制作の経験がある方であれば、同じように、とっつきやすく感じられるのではないかと思います。

前提条件

GASを利用しようと思った理由の一つに、もともと Google スプレッドシートでチーム内部向けの作業管理表を付けていたことがあります。管理表の書式はチームによって、作品によって様々だと思いますが、最近関わった作品では、次のような書式にしていました。

これらの項目を埋めるためにGASを活用したいというわけですが、今回は右端の2行を自動的に記入していくことを考えます。take はアップロードした動画ファイルのバージョン、rollはどのフォルダに動画ファイルが入っているかの情報です。

動画ファイル自体は次のような命名規則に従っているとします。

ORE_00_C000_t0.mp4

アンダーバー(_)に仕切られた部分のうち、1番めの部分は作品ごとに付けられた3~4文字程度の区分コードです。2番めが話数もしくはパート表示ですが、今回は関係ありません。3番めがカット番号で、全体の長さが30分の作品を想定すると、300±50ぐらいのカット数に収まるので、0埋めされた3桁の数字列ということにします。制作上の都合で数字列の後ろに英字が付く場合があります。4番めがバージョン表記で、数字の前につく接頭辞が何を意味するのかは、やはり作品によって様々なのですが、今回は「t」のみを考えてそのまま管理表に記入することにします。

上記の規則に従った動画ファイルを、何個かずつフォルダにまとめ、Googleドライブ上の特定のフォルダにアップロードするとしましょう。

Googleスプレッドシート上にカット番号の列とtake、rollの記入列を作り、今回の管理表ということにします。

Googleドライブにアップロードされている動画ファイルのテイクとフォルダ名を、GASを使って管理表に記入していく、というのが今回のアジェンダになります。

プロジェクトを作り、スプレッドシートを読み書きしてみる

GASのプロジェクトを作成します。GASのホーム画面(https://script.google.com/home)からプロジェクトを作ることも可能ですが、スプレッドシート上の「ツール」→「スクリプトエディタ」を選ぶことでプロジェクトを作成することもできます。このように作成したプロジェクトは Container Bound Script と呼ばれ、あらかじめスプレッドシートと紐ついた状態になっています。

作成したばかりのGASのプロジェクトの画面です。最近エディタがアップデートされ、以前のエディタに比べてだいぶ画面がすっきりしました。入力補助もかなりわかりやすくなっています。

まずは、手動で入力したカット番号が何カットあるのか数えてみます。myFunction関数を次のように書き換えます。

function myFunction() {
  //紐ついているスプレッドシートの1番めのシートを参照
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
  //カット番号が記入されている1番めの列(column)を
 //ヘッダー部分を抜いた2番めの行(row)からシートの最後の行まで指定して選択している
  const cutNumRange = sheet.getRange(2, 1, sheet.getLastRow()-1);
  //指定した範囲からデータを取り出す
  const cutNumValues = cutNumRange.getValues();
  //取り出されたデータは配列型なので、lengthのプロパティで数がわかる
  const cutNum = cutNumValues.length;
  //実行ログに書き出す
  Logger.log(cutNum);
}

書き換えたあとで保存し(エディタ上ではCtrl+Sのショートカットが効きます)、myFunction関数を選択して実行ボタンを押すと、スクリプトが実行されます。

プロジェクト上で初めて Google のサービスの API を利用する際は、下のような画面で許可を求められます。スクリプトに許可する権限とアカウントを確認して、許可してください。

実行が成功すると実行ログが次のような結果になります。

A列には300カット分の番号が入力されています。

さらにmyFunction関数を書き換えてみます。

function myFunction() {
  //紐ついているスプレッドシートの1番めのシートを参照
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
  //カット番号が記入されている1番めの列(column)を
  //ヘッダー部分を抜いた2番めの行(row)からシートの最後の行まで指定して選択している
  const cutNumRange = sheet.getRange(2, 1, sheet.getLastRow()-1);
  //指定した範囲からデータを取り出す  
  const cutNumValues = cutNumRange.getValues();
  //取り出されたデータは配列型なので、lengthのプロパティで数がわかる
  const cutNum = cutNumValues.length;
  //A1表記でも範囲の指定が可能
  const setRange = sheet.getRange("D1");
  //文字列型のデータを指定していた範囲に書き込む
  setRange.setValue(cutNum + "カット");
}

上記のmyFunction関数を実行してみると以下のようにD1のセルに記入されています。

以上がGASによるスプレッドシートの操作の基本になります。

Googleドライブ上のフォルダの中のファイルを数える

次にGASからGoogleドライブ上のフォルダを参照してみます。

myFunction関数はさておいて、checkFol関数を作ってみます。

function checkFol(){
  //idによってGoogleドライブ上のフォルダを参照
  const folder = DriveApp.getFolderById("folderid");
  //フォルダ内のファイルのイテレータを取得
  const files = folder.getFiles();
  //一時保存用の配列
  const fileCount = [];
  //次のファイルがあるかぎり実行
  while(files.hasNext()){
    //ファイルを取得
    const file = files.next();
    //ファイルの名前を取得
    const fileName = file.getName();
    //配列に入れる
    fileCount.push(fileName);
  }
  //配列の中身を実行ログに書き出す
  Logger.log(fileCount);
  //配列の中身の数を実行ログに書き出す
  Logger.log(fileCount.length);
}

1行めが一番重要です。プロジェクトで初めて DriveApp を使った場合、スプレッドシートを操作した際と同じように許可が求められます。

idとはGoogleドライブのフォルダをブラウザで開いたとき、ブラウザのアドレス欄に表示される「https://drive.google.com/drive/u/0/folders/***」の末尾の「***」部分です。ここでは前提条件の節で作った「20210125_R1」のフォルダのidを指定しています(共有の設定をしていないのでこのidのフォルダをブラウザで開くことはできません)。

getFiles() メソッドでフォルダの中身のファイルを取得できますが、このとき返ってくるのは配列ではなくてファイルのイテレータです。中身の取り出し方にやや工夫が必要になります。

というわけで、「20210125_R1」フォルダを参照したときのcheckFol関数の実行結果はこうなります。

フォルダの中のファイル名を分解してスプレッドシートに書き込む

さて準備はここまでとなり、いよいよ今回の目標に取り組みます。新しく作るcheckOut関数は、内容が次のようになっています。

function checkOut(){
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
  const cutNumRange = sheet.getRange(2, 1, sheet.getLastRow()-1);
  const cutNumValues = cutNumRange.getValues();
  //B列、C列を2行めから最後の行まで指定
  const setRange = sheet.getRange(2, 2, sheet.getLastRow()-1, 2);
  //書き換え用の配列
  const setArray = setRange.getValues();
  const folder = DriveApp.getFolderById("folderid");
  const files = folder.getFiles();
  const roll = folder.getName();
  //保存用の連想配列
  const numTake = {};
  while(files.hasNext()){
    const file = files.next();
    //拡張子以外のファイル名を取り出す
    const fileName = file.getName().split(".")[0];
    //ファイル名をアンダーバーで区切った3番めの部分をカット番号として取り出す
    const num = fileName.split("_")[2].substring(1);
    ファイル名の4番めの部分をテイクとして取り出す
    const take = fileName.split("_")[3];
    //取り出したカット番号とテイクを保存
    numTake[num] = take;
  }
  //カット番号を1個ずつチェック
  cutNumValues.forEach((row, index)=>{
    const cutNum = row[0];
    const take = numTake[cutNum];
    //保存用のnumTakeにカット番号とテイクがあれば、それを書き換え用の配列に入れる
    if(take !== undefined){
      setArray[index] = [[take],[roll]];
    }
  })
  //書き換え用の配列によってシート上のデータを書き換える
  setRange.setValues(setArray);
}

GASによるスプレッドシートの操作は、パフォーマンス上の理由から、一度データを取り出してデータをGAS上で書き換えた後、まとめてスプレッドシートに書き込むというのが一般的です。特に、今回のようにチェックしたいカット数が300ぐらいになると、取り出したデータを書き換えたり照合したりする処理にも時間がかかります。そして、GASの実行時間には制限があり、実行開始から6分過ぎた時点で処理は中断されてしまいます。

そのため、上記のcheckOut関数では、一度アップロードしたフォルダの中身から、カット番号とテイクを組み合わせた連想配列を作っておいて、カット番号の配列を1つずつ処理するforEachの中で連想配列を呼び出し、連想配列にカット番号のキーがあれば(undefined でなければ)データを書き換えるという処理にしています。スプレッドシートから取り出したデータを走査する処理に一番時間がかかるため、それを一度で済ませたいというわけです。

「20210125_R1」フォルダに対してcheckOut関数を実行した結果、OREカットリストには次のように記入されることになります。

終わりに

GASによる納品ファイルの集計のご説明は以上となります。

これだけだと、アップロードするフォルダがスクリプト上で決め打ちされてしまっているので、フォルダをアップロードするたびにスクリプトを書き換えなくてはなりません。また、フォルダ内に命名規則に従わないファイルがあった場合エラーが発生してすべての処理が止まります。

さらに、古い(テイクの番号が若い)ファイルをあとからアップロードしてスクリプトでチェックした場合、スプレッドシートに記入されるのは古いファイルの方になりますが、それは避けたいという場合もあります。つまり、t2のファイルのあとにt1のファイルがアップされたとして、シートに記録されるのはt2のファイルの情報であってほしいというわけです。その場合は、シートに記入済みデータと新しくチェックしたデータを比較する機能が必要になります。

あと、テイクとフォルダ名だけではなく、アップロードの日付や作業者、アップロードした担当者の名前などの情報も管理表に記入する必要があるかもしれません。

要件は様々で、関わる作品やチームによってそれらの機能を実装したり、スクリプトで達成することを諦めて運用でカバーしたりしているのですが、ともあれ今回のご紹介が、読んでいただいた方に多少なりとも有益であってくれれば幸いです。今後ともよろしくお願いいたします。

関連リンク

GoogleAppsScript(ORENDA技術ブログ)