I also came across this problem of conversion of Word document (doc/docx) to PDF or any other document type. I solved this problem through LibreOffice with Python 3.8 (will work with python 3.6 and 3.7 also) using subprocess in AWS Lambda.
Basically, this setup will pick your file from S3 through input event and convert file to PDF and put converted file into same S3 location. Let's walk through setup guide.
For this setup, we need LibreOffice executable accessible through Lambda. To achieve this, we will make use of a Lambda Layer.
Now, you have two options:
- You can create your own AWS Lambda layer and upload layer.tar.br.zip (you can download this archive from shelfio GitHub repository)
- or, you can use Layer ARN directly in your Lambda.
2.1. Layer ARN for python3.6 and 3.7
2.2. Layer ARN for python 3.8
It's time to create Lambda (dependency package).
- Create
fonts/fonts.conf at root of your lambda folder with following content (assuming libreoffice will be extracted under /tmp/instdir dir):
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<dir>/tmp/instdir/share/fonts/truetype</dir>
<cachedir>/tmp/fonts-cache/</cachedir>
<config></config>
</fontconfig>
- Paste following code in your
lambda_function.py file:
import os
from io import BytesIO
import tarfile
import boto3
import subprocess
import brotli
libre_office_install_dir = '/tmp/instdir'
def load_libre_office():
if os.path.exists(libre_office_install_dir) and os.path.isdir(libre_office_install_dir):
print('We have a cached copy of LibreOffice, skipping extraction')
else:
print('No cached copy of LibreOffice exists, extracting tar stream from Brotli file.')
buffer = BytesIO()
with open('/opt/lo.tar.br', 'rb') as brotli_file:
decompressor = brotli.Decompressor()
while True:
chunk = brotli_file.read(1024)
buffer.write(decompressor.decompress(chunk))
if len(chunk) < 1024:
break
buffer.seek(0)
print('Extracting tar stream to /tmp for caching.')
with tarfile.open(fileobj=buffer) as tar:
tar.extractall('/tmp')
print('Done caching LibreOffice!')
return f'{libre_office_install_dir}/program/soffice.bin'
def download_from_s3(bucket, key, download_path):
s3 = boto3.client("s3")
s3.download_file(bucket, key, download_path)
def upload_to_s3(file_path, bucket, key):
s3 = boto3.client("s3")
s3.upload_file(file_path, bucket, key)
def convert_word_to_pdf(soffice_path, word_file_path, output_dir):
conv_cmd = f"{soffice_path} --headless --norestore --invisible --nodefault --nofirststartwizard --nolockcheck --nologo --convert-to pdf:writer_pdf_Export --outdir {output_dir} {word_file_path}"
response = subprocess.run(conv_cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if response.returncode != 0:
response = subprocess.run(conv_cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if response.returncode != 0:
return False
return True
def lambda_handler(event, context):
bucket = event["document_bucket"]
key = event["document_key"]
key_prefix, base_name = os.path.split(key)
download_path = f"/tmp/{base_name}"
output_dir = "/tmp"
download_from_s3(bucket, key, download_path)
soffice_path = load_libre_office()
is_converted = convert_word_to_pdf(soffice_path, download_path, output_dir)
if is_converted:
file_name, _ = os.path.splitext(base_name)
upload_to_s3(f"{output_dir}/{file_name}.pdf", bucket, f"{key_prefix}/{file_name}.pdf")
return {"response": "file converted to PDF and available at same S3 location of input key"}
else:
return {"response": "cannot convert this document to PDF"}
- Build (and copy from
site-packages/brotli from your Linux Environment after installing) brotlipy dependency from Linux environment, as targeted Lambda runtime is AmazonLinux.
At the end, directory structure of your lambda (dependency package) should like this:
.
+-- brotli/*
+-- fonts
| +-- fonts.conf
+-- lambda_function.py
You can use following input event to invoke this Lambda handler, if your file s3 URI is s3://my-bucket-name/dir/file.docx:
{
"document_bucket: "my-bucket-name"
"document_key": "dir/file.docx"
}
Cheers! and let me know if you face any issue, would be happy to assist :)