Speeding up Python program that converts DOCX to PDF in Windows
I recently learned how easy it is to incorporate multiprocessing and multithreading into a Python program, and would like to share it with you.
Python's built-in multiprocessing module offers a simple way to add multiprocessing to a program. However, since your program performs a lot of writes, I think it may be better to use the multiprocessing.dummy module instead. This module offers the same API as the multiprocessing module, but is used for multithreading instead of multiprocessing, and thus is better suited to programs that are IO intensive.
First, import the Pool class from the multiprocessing.dummy module:
import Pool from multiprocessing.dummy as ThreadPool
I aliased it as ThreadPool just for added clarity that we are using multithreading and not multiprocessing. Next, take a look at all the for loops in your code. To add multithreading, we will have to change those for loops to call a function on each iteration instead of performing a series of steps:
def create_qr_code(values):
    data = barcode = values[&quot;barcode&quot;]
    image = pyqrcode.create(data)
    image.png(&quot;codes\\&quot;+f&quot;{barcode}.png&quot;, scale=3)

def create_qr_codes():
    for index, values in df.iterrows():
        create_qr_code(values)

def embed_qr_code(image):
    image_name = os.path.basename(image)
    doc = Document()
    doc.add_picture(image)
    last_paragraph = doc.paragraphs[-1] 
    last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
    doc.save(&quot;docs\\&quot;+f&quot;{image_name}.docx&quot;)
    convert(&quot;docs\\&quot;+f&quot;{image_name}.docx&quot;)

def embed_qr_codes():
    qr_images = glob.glob(&quot;codes\\&quot;+&quot;*.png&quot;)
    for image in qr_images:
        embed_qr_code(image)

def clean_file_name(path):
    newname = path.replace(&quot;.png&quot;, &quot;&quot;)
    if newname != path:
        os.rename(path, newname)

def clean_file_names():
    paths = (os.path.join(root, filename)
        for root, _, filenames in os.walk(&quot;docs\\&quot;)
        for filename in filenames)
    for path in paths:
        clean_file_name(path)

Now for the fun part. In each of the functions that use for loops, we will replace the for loops with usages of ThreadPools as context managers. We can then call each ThreadPool's map method to perform the actions using multithreading:
def create_qr_codes():
    rows = (values for _, values in df.iterrows())
    with ThreadPool() as pool:
        pool.map(create_qr_code, rows)

def embed_qr_codes():
    qr_images = glob.glob(&quot;codes\\&quot;+&quot;*.png&quot;)
    with ThreadPool() as pool:
        pool.map(embed_qr_code, qr_images)

def clean_file_names():
    paths = (os.path.join(root, filename)
        for root, _, filenames in os.walk(&quot;docs\\&quot;)
        for filename in filenames)
    with ThreadPool() as pool:
        pool.map(clean_file_name, paths)

Note that when instantiating a Pool, you can pass a value to it to specify the number of processes (or in our case, threads) to use. According to the Python docs:

If processes is None then the number returned by os.cpu_count() is used.

Also, don't forget to change these two function calls...
create_qr_code()
embed_qr_code()

...to these:
create_qr_codes()
embed_qr_codes()

This should speed up your program significantly. If not, try using the multiprocessing module instead of multiprocessing.dummy.
Finally, one extra tip. You may want to refactor this function:
def make_folder():
    os.mkdir(&quot;codes&quot;)
    os.mkdir(&quot;docs&quot;)

To use the os.makedirs function instead to avoid raising an error if the directories already exist:
def make_folder():
    os.makedirs(&quot;codes&quot;, exist_ok=True)
    os.makedirs(&quot;docs&quot;, exist_ok=True)