PowerPoint ファイルを PDF に自動変換する AWS Lambda 関数をつくる -Lambda関数編-

こんにちは、広野です。

まとまった数の PowerPoint 資料 (PPTX ファイル) を PDF に変換したくて、AWS Lambda 関数をつくってみました。

記事を環境構築編と Lambda 関数編 (本記事) に分けて説明します。

やりたかったこと

多くの PowerPoint 資料 (PPTX) があり、それを RAG (Amazon Bedrock Knowledge Bases) に食わせたい。のですが、RAG が PPTX をソースデータファイルとしてサポートしておらず、一度 PDF に変換しないといけない事情がありました。簡単に変換できるよう、Amazon S3 バケットに置いたら自動変換してくれる処理をつくりました。PPTX – PDF 変換には LibreOffice を使用します。

  • Amazon S3 バケットにファイルを置くと、イベント通知が発行されます。ファイルは input フォルダに置きます。
  • Amazon EventBridge ルールで、input フォルダ内の .pptx ファイルであれば AWS Lambda 関数を呼び出します。
  • Lambda 関数は、EventBridge から当該 PPTX ファイルのメタデータを受け取っているので、それをもとに PPTX ファイルを取得します。
  • Lambda 関数内で、LibreOffice をヘッドレスで (No GUI で) 実行し、PPTX を PDF に変換します。
  • 作成された PDF ファイルを Amazon S3 バケットの output フォルダに保存します。名前は元ファイル名の拡張子が .pdf に変わっただけのものです。

LibreOffice について

LibreOffice はオープンソースの Office ソフトウェアです。Word, Excel, PowerPoint などの Microsoft 製品と互換性があります。そのため、PowerPoint のファイルを扱うことができます。

この LibreOffice はヘッドレス、つまりコマンドで操作することができ、PowerPoint を PDF 変換する機能を利用します。

環境について

環境については、以下の記事をご覧ください。

環境をご理解いただいた上で、本記事の Lambda 関数の説明をお読みいただくことをお勧めします。

 

Lambda 関数について

Lambda 関数はコンテナ化するので、コンテナイメージの中に Python スクリプト (.py) が格納されています。LibreOffice 含む必要なモジュールがインストールされたコンテナイメージに。

必要なソースコードは以下の構成になっています。

  • Dockerfile
    ビルドフェーズで実行するコマンドが書かれています。主にコンテナイメージをビルドし、Amazon ECR に保存するのが目的です。
  • buildspec.yml
    ビルドフェーズでコンテナのベースイメージにモジュールをインストールしたり配置したりするコマンドが書かれています。
  • lambda_function.py
    Lambda 関数です。今回は Python で書かれており、Amazon EventBridge ルールから Amazon S3 オブジェクトのメタデータを受け取り、Amazon S3 へのファイル読み書きや LibreOffice の PDF 変換コマンドを実行します。
  • cfn_container_lambda.yml
    ビルドフェーズでデプロイされた Amazon ECR 内のコンテナイメージと、Lambda 関数を関連付けます。また、Amazon S3 バケットから発行されたイベント通知を受け取るための Amazon EventBridge ルールをデプロイします。

以下のように、AWS CodeCommit リポジトリにはこれらソースコードを特にフォルダー分けせず放り込んでいます。AWS CodeCommit リポジトリ内の main ブランチのソースコードが更新されると、環境構築編で構築した CI/CD パイプラインが動き出しコンテナ Lambda 関数が自動でデプロイされる仕組みです。

中のコードを紹介します。ところどころインラインでコメントします。

Dockerfile

FROM public.ecr.aws/shelf/lambda-libreoffice-base:25.8-python3.14-x86_64
COPY lambda_function.py ${LAMBDA_TASK_ROOT}
CMD [ "lambda_function.handler" ]

ものすごくシンプルです。

元々は自分で Amazon Linux 2023 や Python 用のベースイメージを使用していろいろインストールして動くものを作ったのですが、後から有志の方が LibreOffice 用のベースイメージを公開してくれていることに気付きました。ほんとよく出来ているので、それを使わせてもらっています。

この GitHub の中を覗くと、このベースイメージを作成するためのコマンドも書いてあり、もしフォントを追加したいなどあれば自分で加工したものを作れると思います。※フォント追加の必要性については後述します。

buildspec.yml

version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
  build:
    commands:
      - echo Building the Docker image...
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  post_build:
    commands:
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
artifacts:
  files:
    - cfn_container_lambda.yml

環境変数多めです。どうしてもコマンド実行の際に環境特有の情報が必要になるので。それ以外は特別なことはしていません。

lambda_function.py

import json
import os
import subprocess
import boto3

s3 = boto3.client("s3")
WORKDIR = "/tmp"

def handler(event, context):
  print("Event:", json.dumps(event))
  # EventBridge - S3 情報取得
  bucket = event["detail"]["bucket"]["name"]
  key = event["detail"]["object"]["key"]
  filename = os.path.basename(key)
  local_pptx = f"{WORKDIR}/{filename}"

  # S3 - /tmp にダウンロード
  s3.download_file(bucket, key, local_pptx)

  # LibreOffice変換
  subprocess.run([
    'libreoffice25.8',
    '--headless',
    '--invisible',
    '--nodefault',
    '--view',
    '--nolockcheck',
    '--nologo',
    '--norestore',
    '--convert-to',
    'pdf',
    '--outdir',
    WORKDIR,
    local_pptx
  ], check=True)

  pdf_name = filename.replace(".pptx", ".pdf")
  local_pdf = f"{WORKDIR}/{pdf_name}"

  # 出力 S3 の output フォルダへ
  output_key = f"output/{pdf_name}"
  s3.upload_file(local_pdf, bucket, output_key)
  return {
    "status": "ok",
    "input": key,
    "output": output_key
  }

こちらも特段特別なことはしていません。

Amazon EventBridge ルールから Amazon S3 の PPTX ファイルのメタデータを受け取り、それを元にファイルを取得。LibreOffice をヘッドレス実行して PDF 変換します。PDF を Amazon S3 バケットに戻す処理だけです。

cfn_container_lambda.yml

インラインでコメントします。Lambda 関数の箱の設定です。中身はコンテナイメージになるのでここには記述されません。

AWSTemplateFormatVersion: 2010-09-09
Description: The CloudFormation template that creates a Lambda function on container and a relevant IAM role.
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
# 以下のパラメータは自動で AWS CodePipeline から環境変数を受け取ります。
# デフォルト値は気にする必要はありませんが、定義を削除するとエラーになります。
Parameters:
  SystemName:
    Type: String
    Description: System name. use lower case only. (e.g. example)
    Default: example
    MaxLength: 10
    MinLength: 1
  SubName:
    Type: String
    Description: System sub name. use lower case only. (e.g. prod or dev)
    Default: dev
    MaxLength: 10
    MinLength: 1
  ImageTag:
    Type: String
    Default: xxxxxxxxxxxxxxxxxxxx
    MaxLength: 100
    MinLength: 1
  ImgRepoName:
    Type: String
    Default: xxxxxxxxxxxxxxxxxxxx
    MaxLength: 100
    MinLength: 1
  S3BucketDocs:
    Type: String
    Default: xxxxxxxxxxxxxxxxxxxx
    MaxLength: 200
    MinLength: 1

Resources:
# ------------------------------------------------------------#
# Lambda
# ------------------------------------------------------------#
  Lambda:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${SystemName}-${SubName}-pptx-pdf-conv
      Description: !Sub Lambda Function to convert pptx to pdf for ${SystemName}-${SubName}
      PackageType: Image
      Timeout: 60
      # メモリは 1024 MB にしました。670 MB ほど使用していましたので。512 MB だと処理に時間がかかりました。
      # 1024 MB で、1 MB の PPTX の処理が 20 秒ほどかかりました。
      # 10 MB を超える PPTX ファイルだと 1024 MB メモリをフルに消費し、時間も 60 秒タイムアウトを超過してしまいました。
      # 取り扱うファイルサイズによってメモリサイズとタイムアウトは調整する必要があります。
      MemorySize: 1024
      EphemeralStorage:
        Size: 512
      Architectures:
        - x86_64
      # 環境変数として HOME を /tmp として設定しないと LibreOffice の実行が失敗します。
      Environment:
        Variables:
          HOME: "/tmp"
      Role: !GetAtt LambdaRole.Arn
      Tags:
        - Key: Cost
          Value: !Sub ${SystemName}-${SubName}
      # ここで、コンテナイメージを Lambda 関数にするように関連付けています。
      Code:
        ImageUri: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ImgRepoName}:${ImageTag}
    DependsOn:
      - LambdaRole

# ------------------------------------------------------------#
# Lambda Role (IAM)
# ------------------------------------------------------------#
  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub LambdaRole-pptx-pdf-conv-${SystemName}-${SubName}
      Description: This role allows Lambda functions to access S3 bucket.
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
      Policies:
        - PolicyName: !Sub LambdaPolicy-pptx-pdf-conv-${SystemName}-${SubName}
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "s3:PutObject"
                  - "s3:GetObject"
                Resource:
                  - !Sub "arn:aws:s3:::${S3BucketDocs}/*"

# ------------------------------------------------------------#
# EventBridge Rule for starting Lambda function
# ------------------------------------------------------------#
  EventBridgeRuleStartLambda:
    Type: AWS::Events::Rule
    Properties:
      Name: !Sub ${SystemName}-${SubName}-pptx-pdf-conv-start-lambda
      Description: !Sub This rule starts pptx pdf converter Lambda function for ${SystemName}-${SubName}. The trigger is the S3 event notifications.
      EventBusName: !Sub "arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/default"
      EventPattern:
        source:
          - "aws.s3"
        detail-type:
          - "Object Created"
        detail:
          bucket:
            name:
              - !Ref S3BucketDocs
          object:
            key:
              - wildcard: "input/*.pptx"
      State: ENABLED
      Targets:
        - Arn: !GetAtt Lambda.Arn
          Id: !Sub ${SystemName}-${SubName}-pptx-pdf-conv-start-lambda
          RoleArn: !GetAtt EventBridgeRuleLambdaRole.Arn
    DependsOn:
      - EventBridgeRuleLambdaRole

# ------------------------------------------------------------#
# EventBridge Rule Invoke Lambda Role (IAM)
# ------------------------------------------------------------#
  EventBridgeRuleLambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub EventBridgeLambdaRole-${SystemName}-${SubName}
      Description: !Sub This role allows EventBridge to invoke pptx pdf converter Lambda for ${SystemName}-${SubName}.
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
              - events.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: !Sub EventBridgeLambdaPolicy-${SystemName}-${SubName}
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "lambda:InvokeFunction"
                Resource:
                  - !GetAtt Lambda.Arn
    DependsOn:
      - Lambda

 

変換した PDF 

結局、この方法で PPTX を PDF に変換するとどうなるのか。PPTX と PDF のスクリーンショットを撮って比較してみました。

コンテナイメージに Noto Sans CJK フォントが入っていたので、日本語変換は問題ありません。しかし元々使用していたフォントと異なるので、ところどころにレイアウト崩れが起きてしまいました。見た目を気にする資料だとフォントを合わせないと実用的ではなさそうです。フォント以外は特段問題なさそうだと感じました。

PPTX PDF
Noto Sans フォントの Noto は、No Tofu の意味です。環境にフォントが無いと文字化けした文字が四角形 (=豆腐) で表示されてしまうことがありましたが、もう豆腐は無くしたいという思いから、No Tofu -> Noto というフォントが作られたそうです。こんなところで日本語が使われていて面白いですね。

 

まとめ

いかがでしたでしょうか。

本記事はコンテナ Lambda 関数の中身にフォーカスしていました。簡単でしたが LibreOffice の活用に触れられたと思います。アイデア次第で他の用途にも使えると思います。

本記事が皆様のお役に立てれば幸いです。

タイトルとURLをコピーしました