毎週、データを集めてレポートを作るのに、何時間くらい費やしていますか?
アニメ制作スタジオではKitsuで進捗を追跡していますが、それでもスーパーバイザーがそのデータをPDFにまとめる作業を何時間も手作業で行っているのをよく見かけます。プロデューサーやディレクターに状況を共有するためとはいえ、これは創造のエネルギーを大きく奪い、かつ、上級チームが対処しなくてはいけない“手作業ならではの致命点”になりがちです。データがすでに追跡ソフトウェア内に存在しているのであれば、共有することは格闘である必要はありません。
テクニカルリードとしての仕事は、アーティストがアートに集中できるように、退屈な作業を自動化することです。そしてGazuのPythonクライアントを使えば、Kitsuのデータベースと最終的な関係者向けレポートの間をつなぐことができます。
今日は、プロジェクトの指標をプログラムで取得して、独自のPDFを生成するスクリプトを作ります。2時間の手作業を5秒の自動化タスクへと変えるわけです。
なぜカスタムレポートなのか?
Kitsuは、制作の混乱を整理しておくための命綱です。標準搭載のダッシュボードは、マルチ制作の分析を含むあらゆるユースケースをカバーしています。ですが、時には「標準」だけでは足りません。

たとえばクライアントは、プレミアムなサービスに対価を払っていると感じたいことがあります。生のソフトウェア画面のスクリーンショットや、ただの汎用リンクを送ると、少し素人っぽく見えてしまいます。カスタムレポートを使えば、スタジオのブランディングで包んだ進捗更新を届けられ、提供するフレームと同じくらい洗練された見栄えにできます。
さらに悩ましいのが、プロデューサーにとって扱いやすい形式を探すことです。プロデューサーは、非常に特定のExcelのピボットテーブルや、社内の奇妙なロジック(本人しか理解していない)に従った過去資料用のレガシーPDFを要求してきます。「Sequence 02の“進行中(In Progress)”になっているすべてのショット」をフィルタして、そのうえで「延期(Overdue)になっているリテイク」と絡めたリストが必要だ」といった場合でも、カスタムレポートならそのデータを即座に得られます。手作業のコピペによる地獄から救われ、またアニメーションに戻れます。
また、先進的な追跡のために、カスタムビューが必要なスタジオもあります。たとえば、FXキャッシュの遅延によって照明チームが常に停滞しているようなとき、カスタムデータは部門のボトルネックを見つけるのに役立ちます。問題が“金曜夜の追い込み”へ変わる前に、摩擦を解消できます。
幸い、Kitsuは非常に拡張しやすいです。
このガイドで紹介している例の統合に関する完全なソースコードは、GitHubで確認できます:
🔗 https://github.com/cgwire/blog-tutorials/tree/main/custom-kitsu-reports
1. Kitsuのセットアップと認証
まず、Kitsuのインスタンスに接続する必要があります。
まだスタジオURLがない場合、そして自分のマシンでKitsuを動かしたい場合、Dockerが制作向けの実行環境を最速で立ち上げる方法です:
docker run --init -ti --rm -p 80:80 -p 1080:1080 --name cgwire cgwire/cgwire
スクリプト作成では、公式のKitsu Python SDKであるgazuを使います。
ユーザーの認証情報で認証できます(ローカルでのテストならこれで十分です):
import gazu
gazu.set_host("http://localhost/api") user = gazu.log_in("admin@example.com", "mysecretpassword")
2. 制作データの取得
コードを1行も書く前に、Kitsuが公開しているデータについて説明する必要があります。UI上で見えるなら、Gazuで取得できる可能性が高いです。
APIは意外なほど奥が深いです。しっかりした制作レポートを作るなら、通常は次のようなデータを取得することになります:
- 進捗指標(Progress Metrics): ステータスの変更(例:イベントを使って「WIP」から「Internal Review」に移るなど)。
- 時間管理(Time Tracking): あるショットが「In Progress」になっている期間と、元の見積もりの差。
- キャストリスト(Cast Lists): 特定のエピソードまたはシーケンスに紐づく、登場人物、環境、プロップのすべて。
- 作業量(Workload): ある特定のアーティストに現在割り当てられているフレーム数またはアセット数の正確な値。
- 予算(Budget): チームの割当(クォータ)が時間とともにどのように変化するか。
- その他にも、詳細な開発者ドキュメントで読めるリソースがたくさんあります。
よくあるシナリオを見てみましょう。特定のプロジェクトにおいて、チームメンバーに現在割り当てられているすべてのタスクを素早く把握したい、というケースです。これは「誰が何をしている?」系のレポートの土台になります。
projects = gazu.project.all_projects() project = projects0tasks = gazu.task.all_tasks_for_project(project)
report =
for task in tasks: assignees = gazu.person.get_person(p_id)"full_name" for p_id in task"assignees"
task_info = { "date": task["updated_at"], "entity": gazu.entity.get_entity(task["entity_id"])["name"], "type": gazu.task.get_task_type(task["task_type_id"])["name"], "status": gazu.task.get_task_status(task["task_status_id"])["name"] } for artist in assignees: report.append({**task_info, "artist": artist})
Gazuは辞書(dictionaries)を返します。all_tasks_for_project を取得しているときは、長編制作ではこれが膨大な量のデータになり得る点を覚えておいてください。必ずデータを絞り込むようにしましょう。たとえばtask_status や entity_typeでフィルタし、必要なのがアクティブなAnimationショットだけなら、そのように絞り込むべきです。
3. 再利用できるテンプレートの作成
次に、PDFをどのように描画(レンダリング)するかを決めます。主な選択肢は2つあります。
ReportLabを使う方法です。これは最小限の手法です。高速で、Python以外の外部依存を必要としません。社内向けのテックレポート、単純な明細テーブル、高速なバッチ自動化に最適です。
または、Jinja2(テンプレート)とWeasyPrintを使って、HTMLからPDFへ変換するレンダリングパイプラインを作ることもできます。この方法がよく好まれるのは、CSSでレポートの見た目を調整できるからです。Webページを作れるならレポートも作れます。クライアント向けの納品物、重めのブランディング、複雑なレイアウトに最適です。
設定とテンプレートを定義しましょう:
STUDIO_NAME = "My Animation Studio"
STUDIO_LOGO = "studio_logo.png" # ローカルのファイルパス
PROJECT_NAME = "My Project"
OUTPUT_PDF = "activity_report.pdf"
Jinja2の構文()を使って、Pythonのデータを標準的なHTMLへ差し込みます。
<!doctype html> <html> <head> <meta charset="utf-8" /> <style> body { font-family: Arial, sans-serif; margin: 40px; } header { display: flex; align-items: center; margin-bottom: 30px; } header img { height: 50px; margin-right: 20px; } h1 { color: #2a2a2a; } table { width: 100%; border-collapse: collapse; margin-top: 20px; } th { background: #222; color: white; padding: 8px; text-align: left; } td { padding: 8px; border-bottom: 1px solid #ccc; } .footer { margin-top: 40px; font-size: 10px; color: #777; text-align: center; } </style> </head><body> <header> <img src="{{ studio_logo }}" /> <h1>{{ studio_name }} – Activity Report</h1> </header> <p> <strong>Project:</strong> {{ project_name }}<br /> <strong>Report Date:</strong> {{ report_date }} </p> <table> <tr> <th>Date</th> <th>Artist</th> <th>Task</th> <th>Entity</th> <th>Status</th> </tr> {% for row in rows %} <tr> <td>{{ row.date }}</td> <td>{{ row.artist }}</td> <td>{{ row.entity }}</td> <td>{{ row.type }}</td> <td>{{ row.status }}</td> </tr> {% endfor %} </table> <div class="footer">Generated automatically by {{ studio_name }}</div> </body>
</html>
このHTMLファイルは、レポートの視覚構造とスタイルを定義するJinja2テンプレートとして機能し、ページレイアウト、フォント、色、そしてアクティビティデータを表示するためのテーブルを含みます。null の式は、スタジオ名、ロゴURL、プロジェクト名、レポート日付などの値のプレースホルダーを示し、埋め込まれたCSSにより、レンダリングまたはPDFへの変換時に文書がきちんと整った印刷用の見た目になります。
Pythonコードがこのテンプレートをレンダリングすると、Jinja2がスクリプトから渡された実際の値で、すべてのプレースホルダーを置き換えます。そして{% for row in rows %}のループを実行し、アクティビティ記録ごとに1行のテーブルを生成します。rowの辞書は、日付、アーティスト、タスク、エンティティ、ステータス、そして時間(hours)の値を提供し、hoursフィールドは小数点以下2桁で明示的にフォーマットされるため、テーブルが完全に埋められた完全なHTMLドキュメントが得られます。
レンダリングされたHTMLはWeasyPrintに渡されます。WeasyPrintは、HTML構造とインラインCSSの両方を解釈して、内容を印刷可能な文書としてレイアウトします。スタジオのロゴは、URLまたは相対パスを通じて読み込まれ、テーブルとテキストはテンプレートで定義された通りのスタイルになります。そしてすべてがPDFファイルにレンダリングされ、最後に「レポートは自動生成された」ことを示すフッターで締まります。
4. レンダリング
最後に、全部をつなぎ合わせます。jinja2を使ってHTML内のプレースホルダーにデータを流し込み、次にWeasyPrintでそのHTML文字列をPDFファイルに変換します:
from jinja2 import Environment, FileSystemLoader from weasyprint import HTML from datetime import dateenv = Environment(loader=FileSystemLoader(".")) template = env.get_template("report.html")
html = template.render( studio_name=STUDIO_NAME, studio_logo=STUDIO_LOGO, project_name=PROJECT_NAME, report_date=date.today().isoformat(), rows=report, )
HTML(string=html, base_url=".").write_pdf(OUTPUT_PDF)
print(f"PDF generated: {OUTPUT_PDF}")
コードの最初の部分は、Jinja2に対して、現在のディレクトリからHTMLテンプレートを読み込むよう設定し、そのうえで前述のreport.htmlを取得します。
次に、テンプレートは、実行時のデータをそれらのプレースホルダーに注入して、完全なHTMLドキュメントとしてレンダリングされます。スタジオとプロジェクトのメタデータが渡され、現在の日付はISO形式で生成されます。このステップの結果は、動的な値がすべて解決されたプレーンなHTML文字列です。
最後に、レンダリングされたHTMLはWeasyPrintに渡されます。WeasyPrintはHTMLと、関連するCSSやアセットを解析し、PDFファイルへ変換します。base_urlパラメータにより、画像やスタイルシートへの相対パスが正しく動作するようになります。完成したPDFは出力先パスへ書き込まれ、その後、確認メッセージが出力されます。
この最終結果は次の通りです:

対応するGithubリポジトリをクローンすれば、1分でこのスクリプトを自分で実行してみることができます。
5. 自動化のヒント
自動化こそが、このワークフローの本当の“最大のリターン”です。レポートスクリプトがローカルで動くようになったら、次は人の介入なしに確実に動き、そして出力が、人々がすでに見に行く場所に届くようにします。
スクリプトを手作業で実行するのではなく、サーバーにcronジョブを設定して、予測可能な時間に実行しましょう。たとえば、毎営業日18:00に実行すれば、PDFは夜のうちに生成され、プロデューサーが仕事を始めるタイミングで準備が整います。日次のバーンダウンやショットのステータス要約にも特に役立ちます。
PDFが生成されたら、gazuを使ってKitsu内の適切なエンティティ(Production、Episode、繰り返しタスクなど)に直接添付します。これにより、レポートは“正真正銘の制作成果物”になり、永続的な履歴として残ります。たとえば、毎日のレポートを「Daily Production Report(毎日の制作レポート)」というタスクにアップロードすれば、時間の経過による変更の監査や、過去の判断を参照するのが簡単になります。実用的なコツとして、日付をファイル名と添付コメントの両方に含めると、KitsuのUI上で各レポートをダウンロードせずにすばやくスキャンできます。
レポートを関係者へ直接プッシュするには、Pythonの標準機能であるsmtplib(またはトランザクションメールサービス)を使って、PDFを添付して送信します。これは一日中Kitsuの中にいないプロデューサーやクライアントに最適です。具体的には、本文に短い要約(例:「Shots blocked: 12, shots finaled: 3」)を送り、詳細は完全なPDFを添付するという形が現実的です。
単一のHTMLレイアウトに固定せず、同じKitsuデータから異なるレポートスタイルを生成できるように、複数のJinja2テンプレート(client_report.html や internal_audit.htmlなど)を保存しておくとよいでしょう。たとえば、クライアント向けにはクリーンでハイレベルなサマリー、社内の追跡にはより詳細なテーブル、といった使い分けができます。役立つ考え方として、ベースのテンプレートやマクロ(ヘッダー、テーブル、ステータスバッジ)を共有し、ブランディングやレイアウトの変更が、すべてのレポート種別へ波及するようにします。必要なら古いレポートを正確に再現できるように、これらのテンプレートもコードと一緒にバージョン管理しておきましょう。
結論
ここでのより大きなテーマは、PDFの話だけではありません。制作を前に進める“実際の作業”に向けて、時間と注意力を取り戻すことです!
GazuでKitsuから構造化されたデータを引き出し、Pythonで整形し、洗練された自動化レポートとしてレンダリングすることで、壊れやすい手作業の儀式を、静かにバックグラウンドで動く再現可能な仕組みに置き換えられます。これまで数時間かけて行っていたコピペ、整形、二重チェックは、正確なデータを、間に合うタイミングで、プロデューサーやクライアントが本当に読みたくなる形式で届ける、信頼できるパイプラインへと変わります。カスタムレポートによって、自信を持って進捗を伝えられ、危機(クランチ)になる前に問題を浮上させられ、そしてスタジオを“創造面で鋭いだけでなく技術的にも規律がある”存在として見せられます。
パイプラインが複雑になればなるほど、カスタムレポートを作る重要性は増します。インスピレーションとして、ぜひ私たちのスクリプトガイドももっと読んでみてください!


