.doc to pdf using python
Asked Answered
A

14

78

I'am tasked with converting tons of .doc files to .pdf. And the only way my supervisor wants me to do this is through MSWord 2010. I know I should be able to automate this with python COM automation. Only problem is I dont know how and where to start. I tried searching for some tutorials but was not able to find any (May be I might have, but I don't know what I'm looking for).

Right now I'm reading through this. Dont know how useful this is going to be.

Asthenosphere answered 15/5, 2011 at 20:42 Comment(0)
L
98

A simple example using comtypes, converting a single file, input and output filenames given as commandline arguments:

import sys
import os
import comtypes.client

wdFormatPDF = 17

in_file = os.path.abspath(sys.argv[1])
out_file = os.path.abspath(sys.argv[2])

word = comtypes.client.CreateObject('Word.Application')
doc = word.Documents.Open(in_file)
doc.SaveAs(out_file, FileFormat=wdFormatPDF)
doc.Close()
word.Quit()

You could also use pywin32, which would be the same except for:

import win32com.client

and then:

word = win32com.client.Dispatch('Word.Application')
Leap answered 16/5, 2011 at 13:19 Comment(10)
For many files, consider setting: word.Visible = False to save time and processing of the word files (MS word will not display this way, code will run in background essentially)Reformer
I've managed to get this working for powerpoint documents. Use Powerpoint.Application, Presentations.Open and FileFormat=32.Beehive
I am using a linux server and these libraries dont work in linux.. is there any other way to make it work in linuxAmar
8 years into development and it's this answer that made me feel bad that I am not on Windows!Ventura
when I run this, came an error File "test.py", line 7, in <module> in_file = os.path.abspath(sys.argv[1]) IndexError: list index out of rangeFlawed
@Flawed argv[1] and argv[2] will be the names of the input and output files. You get that error if you don't specify the files on the command line.Isis
When running the doc.SaveAs() command I got an error and had to drop the "FileFormat=" prefix, and then it worked fine.Potato
Do you have an example working with LibreOffice? word = comtypes.client.CreateObject('LibreWriter.Application') doesn't work.Yesseniayester
is there any way to just use file objects and avoid these file saves?Bakelite
Both solutions didn't work for me. For win32com, it gives: File "<COMObject Open>", line 5, in SaveAs pywintypes.com_error: (-2147352571, 'Type mismatch.', None, 2). For the former, it gives CoInitialize() has not been called.Erastus
H
53

You can use the docx2pdf python package to bulk convert docx to pdf. It can be used as both a CLI and a python library. It requires Microsoft Office to be installed and uses COM on Windows and AppleScript (JXA) on macOS.

from docx2pdf import convert

convert("input.docx")
convert("input.docx", "output.pdf")
convert("my_docx_folder/")
pip install docx2pdf
docx2pdf input.docx output.pdf

Disclaimer: I wrote the docx2pdf package. https://github.com/AlJohri/docx2pdf

Hampshire answered 24/12, 2019 at 8:4 Comment(4)
Unfortunately, it requires Microsoft Office to be installed and thus only works on Windows and macOS.Hampshire
@AlJohri take a look here michalzalecki.com/converting-docx-to-pdf-using-python this solution works on both windows and linux. runnig on linux it's a must bcause the most of deployement servers use linuxNorrie
The solution asked for doc, and docx2pdf does not work for doc...Ute
Lib is outdated and thus does not work.Erastus
N
24

I have tested many solutions but no one of them works efficiently on Linux distribution.

I recommend this solution :

import sys
import subprocess
import re


def convert_to(folder, source, timeout=None):
    args = [libreoffice_exec(), '--headless', '--convert-to', 'pdf', '--outdir', folder, source]

    process = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=timeout)
    filename = re.search('-> (.*?) using filter', process.stdout.decode())

    return filename.group(1)


def libreoffice_exec():
    # TODO: Provide support for more platforms
    if sys.platform == 'darwin':
        return '/Applications/LibreOffice.app/Contents/MacOS/soffice'
    return 'libreoffice'

and you call your function:

result = convert_to('TEMP Directory',  'Your File', timeout=15)

All resources:

https://michalzalecki.com/converting-docx-to-pdf-using-python/

Norrie answered 28/2, 2020 at 20:5 Comment(2)
This is not using Python, this is just running the libre office exe from a python script.Elegize
Thank you, sir, for this solution. It is actually even runnable through Google Colab so you can do this on the fly.Civics
S
17

I have worked on this problem for half a day, so I think I should share some of my experience on this matter. Steven's answer is right, but it will fail on my computer. There are two key points to fix it here:

(1). The first time when I created the 'Word.Application' object, I should make it (the word app) visible before open any documents. (Actually, even I myself cannot explain why this works. If I do not do this on my computer, the program will crash when I try to open a document in the invisible model, then the 'Word.Application' object will be deleted by OS. )

(2). After doing (1), the program will work well sometimes but may fail often. The crash error "COMError: (-2147418111, 'Call was rejected by callee.', (None, None, None, 0, None))" means that the COM Server may not be able to response so quickly. So I add a delay before I tried to open a document.

After doing these two steps, the program will work perfectly with no failure anymore. The demo code is as below. If you have encountered the same problems, try to follow these two steps. Hope it helps.

    import os
    import comtypes.client
    import time


    wdFormatPDF = 17


    # absolute path is needed
    # be careful about the slash '\', use '\\' or '/' or raw string r"..."
    in_file=r'absolute path of input docx file 1'
    out_file=r'absolute path of output pdf file 1'

    in_file2=r'absolute path of input docx file 2'
    out_file2=r'absolute path of outputpdf file 2'

    # print out filenames
    print in_file
    print out_file
    print in_file2
    print out_file2


    # create COM object
    word = comtypes.client.CreateObject('Word.Application')
    # key point 1: make word visible before open a new document
    word.Visible = True
    # key point 2: wait for the COM Server to prepare well.
    time.sleep(3)

    # convert docx file 1 to pdf file 1
    doc=word.Documents.Open(in_file) # open docx file 1
    doc.SaveAs(out_file, FileFormat=wdFormatPDF) # conversion
    doc.Close() # close docx file 1
    word.Visible = False
    # convert docx file 2 to pdf file 2
    doc = word.Documents.Open(in_file2) # open docx file 2
    doc.SaveAs(out_file2, FileFormat=wdFormatPDF) # conversion
    doc.Close() # close docx file 2   
    word.Quit() # close Word Application 
Spann answered 13/12, 2016 at 9:48 Comment(0)
P
8

unoconv (writen in Python) and OpenOffice running as a headless daemon.

https://github.com/unoconv/unoconv

http://dag.wiee.rs/home-made/unoconv/

Works very nicely for doc, docx, ppt, pptx, xls, xlsx.

Very useful if you need to convert docs or save/convert to certain formats on a server.

Paleozoic answered 15/10, 2014 at 22:15 Comment(3)
Can you include a sample code to show how to do it from a python script (import unoconv unoconv.dosomething(...))? The documentation only shows how to do it from command line.Yesseniayester
"Please note that there is a rewrite of Unoconv called "Unoserver": github.com/unoconv/unoserver We are running Unoserver successfully in production, and it’s now the recommended solution. Unoserver does not have all the features of Unoconv, which features it will get depends on a combination of what people want, and if someone wants to implement it. Until Unoserver has all the major features people need, Unoconv is in bugfix mode, there will be no major changes...." from github.com/unoconv/unoconv I'm think I'm placing my money on unoconv still.Lacagnia
Heads up for other uses, I had some issue making unoconv work. The approach I went for (which works okay on linux and within docker) was called libreoffice directly as described in this answer.Lacagnia
A
7

As an alternative to the SaveAs function, you could also use ExportAsFixedFormat which gives you access to the PDF options dialog you would normally see in Word. With this you can specify bookmarks and other document properties.

doc.ExportAsFixedFormat(OutputFileName=pdf_file,
    ExportFormat=17, #17 = PDF output, 18=XPS output
    OpenAfterExport=False,
    OptimizeFor=0,  #0=Print (higher res), 1=Screen (lower res)
    CreateBookmarks=1, #0=No bookmarks, 1=Heading bookmarks only, 2=bookmarks match word bookmarks
    DocStructureTags=True
    );

The full list of function arguments is: 'OutputFileName', 'ExportFormat', 'OpenAfterExport', 'OptimizeFor', 'Range', 'From', 'To', 'Item', 'IncludeDocProps', 'KeepIRM', 'CreateBookmarks', 'DocStructureTags', 'BitmapMissingFonts', 'UseISO19005_1', 'FixedFormatExtClassPtr'

Agleam answered 24/5, 2018 at 19:6 Comment(0)
P
4

It's worth noting that Stevens answer works, but make sure if using a for loop to export multiple files to place the ClientObject or Dispatch statements before the loop - it only needs to be created once - see my problem: Python win32com.client.Dispatch looping through Word documents and export to PDF; fails when next loop occurs

Precontract answered 15/5, 2013 at 7:44 Comment(0)
F
3

If you don't mind using PowerShell have a look at this Hey, Scripting Guy! article. The code presented could be adopted to use the wdFormatPDF enumeration value of WdSaveFormat (see here). This blog article presents a different implementation of the same idea.

Frederiksberg answered 15/5, 2011 at 20:53 Comment(1)
I'am a linux/Unix user and more inclined towards python. But the ps script looks pretty simple and exactly what I was looking for. Thanks :)Asthenosphere
D
3

I have modified it for ppt support as well. My solution support all the below-specified extensions.

import sys
import os
import json
import subprocess
from pathlib import Path
from tqdm.auto import tqdm

word_extensions = [".doc", ".odt", ".rtf", ".docx", ".dotm", ".docm"]
ppt_extensions = [".ppt", ".pptx"]


def windows(paths, keep_active):
    import win32com.client
    import pythoncom

    pythoncom.CoInitialize()
    word = win32com.client.dynamic.Dispatch("Word.Application")
    ppt = win32com.client.dynamic.Dispatch("Powerpoint.Application")
    wdFormatPDF = 17
    pptFormatPDF = 32

    if paths["batch"]:
        for ext in word_extensions:
            for docx_filepath in tqdm(sorted(Path(paths["input"]).glob(f"*{ext}"))):
                pdf_filepath = Path(paths["output"]) / (
                    str(docx_filepath.stem) + ".pdf"
                )
                doc = word.Documents.Open(str(docx_filepath))
                doc.SaveAs(str(pdf_filepath), FileFormat=wdFormatPDF)
                doc.Close()
        for ext in ppt_extensions:
            for ppt_filepath in tqdm(sorted(Path(paths["input"]).glob(f"*{ext}"))):
                pdf_filepath = Path(paths["output"]) / (str(ppt_filepath.stem) + ".pdf")
                ppt_ = ppt.Presentations.Open(str(ppt_filepath))
                ppt_.SaveAs(str(pdf_filepath), FileFormat=pptFormatPDF)
                ppt_.Close()
    else:
        pbar = tqdm(total=1)
        input_filepath = Path(paths["input"]).resolve()
        pdf_filepath = Path(paths["output"]).resolve()
        if input_filepath.suffix in word_extensions:
            doc = word.Documents.Open(str(input_filepath))
            doc.SaveAs(str(pdf_filepath), FileFormat=wdFormatPDF)
            doc.Close()
        else:
            ppt_ = ppt.Presentations.Open(str(input_filepath))
            ppt_.SaveAs(str(pdf_filepath), FileFormat=pptFormatPDF)
            ppt_.Close()
        pbar.update(1)

    if not keep_active:
        word.Quit()
        ppt.Quit()


def resolve_paths(input_path, output_path):
    input_path = Path(input_path).resolve()
    output_path = Path(output_path).resolve() if output_path else None
    output = {}
    if input_path.is_dir():
        output["batch"] = True
        output["input"] = str(input_path)
        if output_path:
            assert output_path.is_dir()
        else:
            output_path = str(input_path)
        output["output"] = output_path
    else:
        output["batch"] = False
        # assert str(input_path).endswith(".docx")
        output["input"] = str(input_path)
        if output_path and output_path.is_dir():
            output_path = str(output_path / (str(input_path.stem) + ".pdf"))
        elif output_path:
            assert str(output_path).endswith(".pdf")
        else:
            output_path = str(input_path.parent / (str(input_path.stem) + ".pdf"))
        output["output"] = output_path
    return output


def convert(input_path, output_path=None, keep_active=False):
    paths = resolve_paths(input_path, output_path)
    if sys.platform == "win32":
        return windows(paths, keep_active)
    else:
        raise NotImplementedError(
            "This script is not implemented for linux and macOS as it requires Microsoft Word to be installed"
        )


def main():
    print("Processing...")
    input_path = os.path.abspath(sys.argv[1])
    convert(input_path)
    print("Processed...")


if __name__ == "__main__":
    main()

My Solution: Github Link

I have modified code from Docx2PDF

Distinguish answered 19/2, 2021 at 18:35 Comment(0)
U
2

I tried the accepted answer but wasn't particularly keen on the bloated PDFs Word was producing which was usually an order of magnitude bigger than expected. After looking how to disable the dialogs when using a virtual PDF printer I came across Bullzip PDF Printer and I've been rather impressed with its features. It's now replaced the other virtual printers I used previously. You'll find a "free community edition" on their download page.

The COM API can be found here and a list of the usable settings can be found here. The settings are written to a "runonce" file which is used for one print job only and then removed automatically. When printing multiple PDFs we need to make sure one print job completes before starting another to ensure the settings are used correctly for each file.

import os, re, time, datetime, win32com.client

def print_to_Bullzip(file):
    util = win32com.client.Dispatch("Bullzip.PDFUtil")
    settings = win32com.client.Dispatch("Bullzip.PDFSettings")
    settings.PrinterName = util.DefaultPrinterName      # make sure we're controlling the right PDF printer

    outputFile = re.sub("\.[^.]+$", ".pdf", file)
    statusFile = re.sub("\.[^.]+$", ".status", file)

    settings.SetValue("Output", outputFile)
    settings.SetValue("ConfirmOverwrite", "no")
    settings.SetValue("ShowSaveAS", "never")
    settings.SetValue("ShowSettings", "never")
    settings.SetValue("ShowPDF", "no")
    settings.SetValue("ShowProgress", "no")
    settings.SetValue("ShowProgressFinished", "no")     # disable balloon tip
    settings.SetValue("StatusFile", statusFile)         # created after print job
    settings.WriteSettings(True)                        # write settings to the runonce.ini
    util.PrintFile(file, util.DefaultPrinterName)       # send to Bullzip virtual printer

    # wait until print job completes before continuing
    # otherwise settings for the next job may not be used
    timestamp = datetime.datetime.now()
    while( (datetime.datetime.now() - timestamp).seconds < 10):
        if os.path.exists(statusFile) and os.path.isfile(statusFile):
            error = util.ReadIniString(statusFile, "Status", "Errors", '')
            if error != "0":
                raise IOError("PDF was created with errors")
            os.remove(statusFile)
            return
        time.sleep(0.1)
    raise IOError("PDF creation timed out")
Uraeus answered 1/7, 2017 at 12:37 Comment(0)
G
2

I was working with this solution but I needed to search all .docx, .dotm, .docm, .odt, .doc or .rtf and then turn them all to .pdf (python 3.7.5). Hope it works...

import os
import win32com.client

wdFormatPDF = 17

for root, dirs, files in os.walk(r'your directory here'):
    for f in files:

        if  f.endswith(".doc")  or f.endswith(".odt") or f.endswith(".rtf"):
            try:
                print(f)
                in_file=os.path.join(root,f)
                word = win32com.client.Dispatch('Word.Application')
                word.Visible = False
                doc = word.Documents.Open(in_file)
                doc.SaveAs(os.path.join(root,f[:-4]), FileFormat=wdFormatPDF)
                doc.Close()
                word.Quit()
                word.Visible = True
                print ('done')
                os.remove(os.path.join(root,f))
                pass
            except:
                print('could not open')
                # os.remove(os.path.join(root,f))
        elif f.endswith(".docx") or f.endswith(".dotm") or f.endswith(".docm"):
            try:
                print(f)
                in_file=os.path.join(root,f)
                word = win32com.client.Dispatch('Word.Application')
                word.Visible = False
                doc = word.Documents.Open(in_file)
                doc.SaveAs(os.path.join(root,f[:-5]), FileFormat=wdFormatPDF)
                doc.Close()
                word.Quit()
                word.Visible = True
                print ('done')
                os.remove(os.path.join(root,f))
                pass
            except:
                print('could not open')
                # os.remove(os.path.join(root,f))
        else:
            pass

The try and except was for those documents I couldn't read and won't exit the code until the last document.

Guillerminaguillermo answered 17/2, 2020 at 18:28 Comment(1)
What are you importing?Elegize
A
1

You should start from investigating so called virtual PDF print drivers. As soon as you will find one you should be able to write batch file that prints your DOC files into PDF files. You probably can do this in Python too (setup printer driver output and issue document/print command in MSWord, later can be done using command line AFAIR).

Alduino answered 15/5, 2011 at 20:54 Comment(0)
G
0
import docx2txt
from win32com import client

import os

files_from_folder = r"c:\\doc"

directory = os.fsencode(files_from_folder)

amount = 1

word = client.DispatchEx("Word.Application")
word.Visible = True

for file in os.listdir(directory):
    filename = os.fsdecode(file)
    print(filename)

    if filename.endswith('docx'):
        text = docx2txt.process(os.path.join(files_from_folder, filename))

        print(f'{filename} transfered ({amount})')
        amount += 1
        new_filename = filename.split('.')[0] + '.txt'

        try:
            with open(os.path.join(files_from_folder + r'\txt_files', new_filename), 'w', encoding='utf-8') as t:
                t.write(text)
        except:
            os.mkdir(files_from_folder + r'\txt_files')
            with open(os.path.join(files_from_folder + r'\txt_files', new_filename), 'w', encoding='utf-8') as t:
                t.write(text)
    elif filename.endswith('doc'):
        doc = word.Documents.Open(os.path.join(files_from_folder, filename))
        text = doc.Range().Text
        doc.Close()

        print(f'{filename} transfered ({amount})')
        amount += 1
        new_filename = filename.split('.')[0] + '.txt'

        try:
            with open(os.path.join(files_from_folder + r'\txt_files', new_filename), 'w', encoding='utf-8') as t:
                t.write(text)
        except:
            os.mkdir(files_from_folder + r'\txt_files')
            with open(os.path.join(files_from_folder + r'\txt_files', new_filename), 'w', encoding='utf-8') as t:
                t.write(text)
word.Quit()

The Source Code, see here:

https://neculaifantanaru.com/en/python-full-code-how-to-convert-doc-and-docx-files-to-pdf-from-the-folder.html

Grove answered 21/3, 2022 at 9:24 Comment(1)
Hi and Welcome to SO. Please also include links to packages you import and explain a bit what your solution does different and better than already mentioned ones.Elegize
D
-7

I would suggest ignoring your supervisor and use OpenOffice which has a Python api. OpenOffice has built in support for Python and someone created a library specific for this purpose (PyODConverter).

If he isn't happy with the output, tell him it could take you weeks to do it with word.

Dracaena answered 15/5, 2011 at 20:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.