feature: Slide.duplicate()

In order to create a presentation by populating template slides with dynamic data
As a developer using python-pptx
I need the ability to clone a slide
API suggestion:
cloned_slide = prs.slides.clone_slide(original_slide)

The cloned slide would be appended to the end of the presentation and would be functionally equivalent to copying and pasting a slide using the PPT GUI.

I just have the same usecase. Expecting for your APIs. :)

I'm interested in doing the exact same thing as @AlexMooney.
@scanny: you said that it was difficult to implement in the general case, as there may be complicated linkages between slides. The presentations I'm working with won't have those complicated connections - would you be able to give any hints as to the best way to implement a duplicate() function that works in a limited case of simple presentations? At the moment I'm trying to loop through shapes and add them, but that requires a lot of logic to deal with shape types, copying subobjects etc

I am also interested in doing the exact same thing as @AlexMooney.
My use case is that the client wants to be able to supply any one from a structurally equivalent set of 2-slide templates, and have python code that will create new slides populated from dynamic test data.  So we would like to duplicate from one of the template slides, and insert at the end of the deck.  Upon saving the presentation, the 2 template slides would be stripped out.
Does anyone know if these features have already been implemented in python-pptx?

I don't know of it being implemented in python-pptx, but I have a pretty-awful implementation that works for my use case - but I should warn you that it may well not work for some of your situations, and is likely to be very buggy!
The code is below, and should be reasonably self-explanatory - but I warn you, it may well fail (and definitely only works for duplicating slides within presentations - for copying between presentations you open up a whole other can of worms)
def _get_blank_slide_layout(pres):
    layout_items_count = [len(layout.placeholders) for layout in pres.slide_layouts]
    min_items = min(layout_items_count)
    blank_layout_id = layout_items_count.index(min_items)
    return pres.slide_layouts[blank_layout_id]

def duplicate_slide(pres, index):
    """Duplicate the slide with the given index in pres.

    Adds slide to the end of the presentation"""
    source = pres.slides[index]

    blank_slide_layout = _get_blank_slide_layout(pres)
    dest = pres.slides.add_slide(blank_slide_layout)

    for shp in source.shapes:
        el = shp.element
        newel = copy.deepcopy(el)
        dest.shapes._spTree.insert_element_before(newel, 'p:extLst')

    for key, value in six.iteritems(source.rels):
        # Make sure we don't copy a notesSlide relation as that won't exist
        if not "notesSlide" in value.reltype:
            dest.rels.add_relationship(value.reltype, value._target, value.rId)

    return dest

I very much appreciate that!  Will try it out... trying to decide whether
to use python-pptx or just via MSPPT.py.  Thanks again.
On Tue, Oct 13, 2015 at 2:10 PM, Robin Wilson<EMAIL_ADDRESS>wrote:

I don't know of it being implemented in python-pptx, but I have a
pretty-awful implementation that works for my use case - but I should
warn you that it may well not work for some of your situations, and is
likely to be very buggy!
The code is below, and should be reasonably self-explanatory - but I warn
you, it may well fail (and definitely only works for duplicating slides
within presentations - for copying between presentations you open up a
whole other can of worms)
def _get_blank_slide_layout(pres):
layout_items_count = [len(layout.placeholders) for layout in pres.slide_layouts]
min_items = min(layout_items_count)
blank_layout_id = layout_items_count.index(min_items)
return pres.slide_layouts[blank_layout_id]
def duplicate_slide(pres, index):
"""Duplicate the slide with the given index in pres.    Adds slide to the end of the presentation"""
source = pres.slides[index]
blank_slide_layout = _get_blank_slide_layout(pres)
dest = pres.slides.add_slide(blank_slide_layout)

for shp in source.shapes:
    el = shp.element
    newel = copy.deepcopy(el)
    dest.shapes._spTree.insert_element_before(newel, 'p:extLst')

for key, value in six.iteritems(source.rels):
    # Make sure we don't copy a notesSlide relation as that won't exist
    if not "notesSlide" in value.reltype:
        dest.rels.add_relationship(value.reltype, value._target, value.rId)

return dest

—
Reply to this email directly or view it on GitHub
https://github.com/scanny/python-pptx/issues/132#issuecomment-147854893.

duplicating slides within presentations would be a great feature. the above snippet works, but doesn't seem to copy the table cells correctly (it replicated the tables on the initial slide, but i can't add paragraphs to them). Maybe there is something else going on there? not deep copying the text elements?

Ah yes, I've never tried it with slides containing tables, so it probably doesn't work properly for those. I'm afraid I haven't got time to investigate the problem with tables, but if you do manage to fix it then let me know.

actually, that code works fine with tables! the error was on my side, needed to make sure it was a deepcopy (i was using a weird library for that), all works great, you should add the duplicate slide method to the slide object, with the caveat it only works within a presentation.

@scanny: Would you be interested in this being added as a method to the slide object?

Yes, thank you.
On Fri, Oct 16, 2015 at 12:50 PM, Robin Wilson<EMAIL_ADDRESS>wrote:

@scanny https://github.com/scanny: Would you be interested in this
being added as a method to the slide object?
—
Reply to this email directly or view it on GitHub
https://github.com/scanny/python-pptx/issues/132#issuecomment-148818115.

@robintw: I would, of course :) Probably best if you start with an analysis document like the ones you find here so we can think through the required scope. There's not really a place for methods that only work for certain cases, so we'd need to work out what the scope of the general case would be and account for the various bits. If I recall correctly, the tricky bit on this one is to make sure relationships to external items like images and hyperlinks are properly taken care of.
Then of course you would need to provide the tests. I think that bit is the most rewarding, in the sense it makes you a better programmer, but seems to be beyond the abilities of most contributors.
Let me know if you're still keen and we can get started.

Unfortunately I don't think I'll have the time to engage with this project to that extent. I'm significantly involved in a number of other open-source projects, while also working full-time - and I just can't commit to do this work properly.
I'm more than happy for anyone else who has time to take the code that I've posted in this issue and integrate it with python-pptx, or just use it themselves.

Sorry, I am not in a position to work on this right now either...
On Sat, Oct 17, 2015 at 5:50 AM, Robin Wilson<EMAIL_ADDRESS>wrote:

Unfortunately I don't think I'll have the time to engage with this project
to that extent. I'm significantly involved in a number of other open-source
projects, while also working full-time - and I just can't commit to do this
work properly.
I'm more than happy for anyone else who has time to take the code that
I've posted in this issue and integrate it with python-pptx, or just use it
themselves.
—
Reply to this email directly or view it on GitHub
https://github.com/scanny/python-pptx/issues/132#issuecomment-148914458.

Thank you all for your fantastic work!
I'd like to try to fix this.
But I haven't contribute before, so I have no idea of weather I can do it well.
What's more, my mother language is Chinese, so, if I ask some silly questions with my poor English for help,  could you forgive me? @robintw @scanny

@children1987: You're entirely welcome to give it a try :)  We'll have to see what you can come up with to get an idea how far away you'd be from getting a commit.
You'd need to be able to explain what you're doing and also write the tests. Those are the harder parts, so most people just write the code and don't bother with those bits; but they are what makes the library robust, so we can't accept a pull request without them.
Your English seems good enough so far. I'm sure we can manage to fix up the grammar and so on if your analysis is sound.

@AlexMooney @scanny  thanks a lot.
With little modification of @AlexMooney  code, I am able to build ppt by copy template to another ppt file. Although background image and some format will lost ,it is not critical, it can be imported from jpg after it.
'''
def _get_blank_slide_layout(pres):
layout_items_count = [len(layout.placeholders) for layout in pres.slide_layouts]
min_items = min(layout_items_count)
blank_layout_id = layout_items_count.index(min_items)
return pres.slide_layouts[blank_layout_id]
def copy_slide(pres,pres1,index):
"""Duplicate the slide with the given index in pres.
Adds slide to the end of the presentation"""
source = pres.slides[index]

blank_slide_layout = _get_blank_slide_layout(pres)
dest = pres1.slides.add_slide(blank_slide_layout)

for shp in source.shapes:
    el = shp.element
    newel = copy.deepcopy(el)
    dest.shapes._spTree.insert_element_before(newel, 'p:extLst')

for key, value in six.iteritems(source.rels):
    # Make sure we don't copy a notesSlide relation as that won't exist
    if not "notesSlide" in value.reltype:
        dest.rels.add_relationship(value.reltype, value._target, value.rId)

return dest

'''

@AlexMooney @scanny thanks a lot.
With little modification of @AlexMooney code, I am able to build ppt by copy template to another ppt file. Although background image and some format will lost ,it is not critical, it can be imported from jpg after it.
def _get_blank_slide_layout(pres):
layout_items_count = [len(layout.placeholders) for layout in pres.slide_layouts]
min_items = min(layout_items_count)
blank_layout_id = layout_items_count.index(min_items)
return pres.slide_layouts[blank_layout_id]
def copy_slide(pres,pres1,index):
source = pres.slides[index]
blank_slide_layout = _get_blank_slide_layout(pres)
dest = pres1.slides.add_slide(blank_slide_layout)

for shp in source.shapes:
    el = shp.element
    newel = copy.deepcopy(el)
    dest.shapes._spTree.insert_element_before(newel, 'p:extLst')

for key, value in six.iteritems(source.rels):
    # Make sure we don't copy a notesSlide relation as that won't exist
    if not "notesSlide" in value.reltype:
        dest.rels.add_relationship(value.reltype, value._target, value.rId)

return dest

@robintw  @scanny  thanks
With little modification of @robintw  code, it is able to copy slide from template to new ppt.
It is a great improvement to me. My proj is ppt report auto generation, i made many ppt template combination for various requirement before . now I can summary identical format into one ppt , then do the iteration of slide copy and content substitution.
    def _get_blank_slide_layout(pres):
         layout_items_count = [len(layout.placeholders) for layout in pres.slide_layouts]
         min_items = min(layout_items_count)
         blank_layout_id = layout_items_count.index(min_items)
         return pres.slide_layouts[blank_layout_id]

    def copy_slide(pres,pres1,index):
         source = pres.slides[index]

         blank_slide_layout = _get_blank_slide_layout(pres)
         dest = pres1.slides.add_slide(blank_slide_layout)

         for shp in source.shapes:
              el = shp.element
              newel = copy.deepcopy(el)
              dest.shapes._spTree.insert_element_before(newel, 'p:extLst')

              for key, value in six.iteritems(source.rels):
                         # Make sure we don't copy a notesSlide relation as that won't exist
                       if not "notesSlide" in value.reltype:
                               dest.rels.add_relationship(value.reltype, value._target, value.rId)

              return dest

The code samples above for duplication of a slide use a variable or module called 'six' for function 'iteritems' which is not defined or clear here. Can you expand on your imports or refactor to use standard iteritems tools?
I want to copy a complex template slide 50 times, performing text substitution on each duplicate per some other data, then delete the template slide.

@hariedo six is a module that provides some compatibility helpers to make code work on Python 2 and 3. The documentation for the six.iteritems method (available at https://pythonhosted.org/six/#six.iteritems) states:
"Returns an iterator over dictionary‘s items. This replaces dictionary.iteritems() on Python 2 and dictionary.items() on Python 3."
So you should be able to replace it with whichever of these applies to your Python version.

@robintw, thanks for that. My example ppt files don't seem to have any data that would cause source.rels or dest.rels to exist. I get 99% of what I want with that chunk of code removed, but wonder what I will be missing if I leave it out.

Images would be the most likely. Hyperlinks are also "relationship" objects, along with charts, smart art, and media (video, audio). There are a couple others that are more obscure.

@robintw @zhong2000 Thanks for the code. I am trying to apply it to charts and also getting the  AttributeError: 'Slide' object has no attribute 'rels' when accessing source.rels.
Removing the code doesn't copy charts correctly.
I guess you are opening the presentation differently than pres = Presentation(file_name)?

The internals changed a while back to extract a new SlidePart class from the Slide class. The .rels attribute moved over with the SlidePart object. The SlidePart object for a slide is accessed using the .part property on the Slide object.
So where you previously would have slide.rels you would now need slide.part.rels. I believe in the code above the change would be source.rels -> source.part.rels and dest.rels -> dest.part.rels.

It didn't work on python3.X on my project.
/usr/local/lib/python3.5/dist-packages/python_pptx-0.6.6-py3.5.egg/pptx/opc/phys_pkg.py:156: UserWarning: Duplicate name: 'ppt/slideLayouts/_rels/slideLayout1.xml.rels'   self._zipf.writestr(pack_uri.membername, blob) /usr/local/lib/python3.5/dist-packages/python_pptx-0.6.6-py3.5.egg/pptx/opc/phys_pkg.py:156: UserWarning: Duplicate name: 'ppt/slideLayouts/slideLayout10.xml'   self._zipf.writestr(pack_uri.membername, blob) /usr/local/lib/python3.5/dist-packages/python_pptx-0.6.6-py3.5.egg/pptx/opc/phys_pkg.py:156: UserWarning: Duplicate name: 'ppt/slideLayouts/_rels/slideLayout10.xml.rels'   self._zipf.writestr(pack_uri.membername, blob) /usr/local/lib/python3.5/dist-packages/python_pptx-0.6.6-py3.5.egg/pptx/opc/phys_pkg.py:156: UserWarning: Duplicate name: 'ppt/slideLayouts/slideLayout7.xml'   self._zipf.writestr(pack_uri.membername, blob) /usr/local/lib/python3.5/dist-packages/python_pptx-0.6.6-py3.5.egg/pptx/opc/phys_pkg.py:156: UserWarning: Duplicate name: 'ppt/slideLayouts/_rels/slideLayout7.xml.rels'   self._zipf.writestr(pack_uri.membername, blob) /usr/local/lib/python3.5/dist-packages/python_pptx-0.6.6-py3.5.egg/pptx/opc/phys_pkg.py:156: UserWarning: Duplicate name: 'ppt/slideLayouts/slideLayout4.xml'   self._zipf.writestr(pack_uri.membername, blob) /usr/local/lib/python3.5/dist-packages/python_pptx-0.6.6-py3.5.egg/pptx/opc/phys_pkg.py:156: UserWarning: Duplicate name: 'ppt/slideLayouts/_rels/slideLayout4.xml.rels'   self._zipf.writestr(pack_uri.membername, blob)
After lot of those stuff show, the generate ppt file can't open.

Any ideas on why this duplicate method corrupts the output? I just have some tables and charts, I have tried before and after filling them, but corrupts the file just the same.
I wouldn't mind any tool to check what's repairing Powerpoint so I can give more information.
Thanks for your work!

I hope you guys can help me out, or point me in the right direction, with my duplication problem. I've been trying to debug it for a few days but I can't seem to figure it out.
I'm trying to duplicate a sheet using the same method @robintw used but like @Bretto9 I always get a corrupted file IF the sheet has a chart.
I've been examining in the output file the difference between the sheet I'm trying to copy vs the new sheet and the new sheet seems to be missing a few things during the copy method.
The only difference between the original sheet and the copy are:

The original sheet has <p:timing> and <p:extLst> tags
The original sheet has the <p:grpSpPr> populated with children while the copy has only <p:grpSpPr/>
In the original sheet there is a xmlns:r=... attribute in the <c:chart>tag but it's missing in the copied sheet
In the <p:sld> (root) tag the xmlns:p and xmlns:r are in a inverse order. (probably doesnt matter)

<p:sId xmlns:a="..." xmlns:r="..." xmlns:p="..."> vs.
<p:sId xmlns:a="..." xmlns:p="..." xmlns:r="...">

I have tried (using vim) to manually add the missing elements in my output file, but still Powerpoint crashes.
This is probably because of relationship objects because If I skip copying the chart, then the file loads fine.
Could this be because the chartData relations in both the origin and the copy point to the same location? /ppt/charts/charts.xml
Thanks in advance, all clues or ideas are greatly appreciated.

What is the XML of the chart shape? Can you post it up? I believe it's a p:graphicFrame element. Better post your exact code along with it. "Same as X" code requires scrolling and usually reveals later "Oh, except for that change I made!". So in the interest of saving time :)
I'm not at all sure you can refer to the same chart from two separate slides. This implies that you would need to duplicate the chart part and probably also the Excel spreadsheet it embeds.
I think the test for this would be to manually edit a .pptx that contains two slides, each with the same chart, to have them both point to the same one, then see if that loads:

Start with a presentation with a single slide containing only a chart.

Duplicate the slide and save the presentation.

Extract the presentation with opc-diag:
opc extract my-test-deck.pptx my-test-deck

Change the relationship of the second slide to point to the first chart.

Delete the second chart (/ppt/charts/chart2.xml probably). Probably should delete the second embedded Xlsx file as well.

Repackage with opc-diag
opc repackage my-test-deck my-hacked-deck.pptx

See if it opens.

Re the XML particulars:

The p:timing tag controls things like slide transitions and is optional.
The p:extLst tags are optional, unless they contain something (like p:ext elements or whatever).
The p:grpSpPr I couldn't say without looking at it's contents, but if adding that doesn't fix it, I'd look elsewhere.
The xmlns:r=... namespace prefix specifiers can appear in any element above (or at) where they are first used. Typically these are gathered in the root element. Their sequence is not significant.

The XML of the chart shape is this :
<p:graphicFrame>
  <p:nvGraphicFramePr>
    <p:cNvPr id="4" name="Content Placeholder 3"/>
    <p:cNvGraphicFramePr>
      <a:graphicFrameLocks noGrp="1"/>
    </p:cNvGraphicFramePr>
    <p:nvPr>
      <p:ph idx="1"/>
      <p:extLst>
        <p:ext uri="{D42A27DB-BD31-4B8C-83A1-F6EECF244321}">
          <p14:modId xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" val="124628090"/>
        </p:ext>
      </p:extLst>
    </p:nvPr>
  </p:nvGraphicFramePr>
  <p:xfrm>
    <a:off x="681038" y="2336800"/>
    <a:ext cx="9613900" cy="3598863"/>
  </p:xfrm>
  <a:graphic>
    <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/chart">
      <c:chart xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" r:id="rId2"/>
    </a:graphicData>
  </a:graphic>
</p:graphicFrame>

Here is a link to a diff between slide1.xml and slide2.xml where you can see what's different between the to charts. https://www.diffchecker.com/emfbI6Im
There is no difference between slide1.xml.rel and slide2.xml.rel. I think this is the source of my error.
I have confirmed that it's not possible for two sheets to reference the same chart, using the steps that you provided.
The file doesn't open.
However, when I manually change the relationship I get a different error message from
Powerpoint : "The file may be damaged, or it may have been created in a pre-release version of PowerPoint.". But when I use my regular method, Powerpoint offers to repair the file and then it removes the chart form the sheet.
This is the code I use to copy the slide:
def _get_blank_slide_layout(pres):
    layout_items_count = [len(layout.placeholders)
                          for layout in pres.slide_layouts]
    min_items = min(layout_items_count)
    blank_layout_id = layout_items_count.index(min_items)
    return pres.slide_layouts[blank_layout_id]

def duplicate_slide(pres, index):
    """Duplicate the slide with the given index in pres.

    Adds slide to the end of the presentation"""
    source = pres.slides[index]
    blank_slide_layout = _get_blank_slide_layout(pres)
    dest = pres.slides.add_slide(blank_slide_layout)

    for shape in source.shapes:
        newel = copy.deepcopy(shape.element)
        dest.shapes._spTree.insert_element_before(newel, 'p:extLst')

    for key, value in six.iteritems(source.part.rels):
        # Make sure we don't copy a notesSlide relation as that won't exist
        if "notesSlide" not in value.reltype:
            dest.part.rels.add_relationship(value.reltype,
                                            value._target,
                                            value.rId)

prs = Presentation("input.pptx")
duplicate_slide(prs, 0)  # duplicate the first slide
prs.save('output.pptx')

Okay, a couple things for a start:

In your XML, the chart is "lodged" in a content placeholder (see lines 3 and 8). This is the kind that can accept text, a table, a chart, or a picture, maybe others. I don't suppose it matters much what type it is, but the fact that it's a placeholder opens up additional failure modes. In particular, I expect the placeholder p:ph idx= value needs to be unique.
I would take that out of the equation for debugging purposes by making the "source" chart simply a chart object "placed" on the slide, rather than "inserted" into a placeholder. That should simplify the p:graphicFrame XML a little and remove at least one possible failure mode to focus the diagnosis.

I'm not getting exactly what you mean by "when I manually change the relationship...". On this sort of thing, I find it useful to recount the steps and the outcome. Like:

I run my code to copy the slide
I extract the package using opc-diag, and add the relationship ... to slide1.xml.rels
... etc.
When I open the package I get the message: "The file may be damaged ..."

I'd be strongly inclined at this point to see if duplicating the chart and Excel objects makes a working deck. For a start, I'd just copy chart1.xml to chart2.xml and embeddedExcelWhatever1 to ...2 in the extracted package.
Then you'll need to edit the chart2.xml, I think the only bit is at the bottom of that file where it has something like external data.
And you'll need to patch the relationship files. I think the slide relates to the chart and the chart relates to the embedded Xlsx. I don't think there are any other relationships.
An easy way to start might be to duplicate the chart slide using PowerPoint and use that file (extracted) as a model and perhaps a place to pull items from.

I'm sorry for not being clear and thank you so much for helping me out.
I shall try to be more explicit from now on.

I have tried to make the p:ph idx= values unique.
I did that by:

Using opc to extract the file
Incrementing the values in the test1/ppt/slides/slide2.xml using vim
Using opc to repackage the file

I don't know how I should make the "source" chart simply a chart object placed on the slide rather than inserted.
Do you mean I should open the input pptx.file using Powerpoint and doing it there?
I just clicked the "navbar" in Powerpoint and chose to "insert" a chart using Powerpoint.
Is there another/better way to add "place" charts in the pptx file?

When I said "I manually change the relationship", these are the steps that I meant:

Open Powerpoint
Create a new Powerpoint file (test1.pptx) with a single slide, with a single chart
Right click and duplicate the slide (I now have two slides that look the same)
Close Powerpoint
Extract test1.pptx opc extract test1.ppt test1
Open test1/ppt/slides/_rels/slide2.xml.rels using vim
Change Target="../charts/chart2.xml" to Target="../charts/chart1.xml"
Delete the test1/ppt/charts/chart2.xml.rels (tried with and without this step)
Repackage test1.pptx using opc repackage test1 test1.pptx
Try to reopen test1.pptx but it get the message "The file may be damaged, or it may have been created in a pre-release version of PowerPoint."

I'm going to try to do this, will update when finished.

I've been trying to copy a slide by doing these steps:
The file doesn't get corrupted but the slide doesn't appear in the file.

extract powerpoint using opc extract file.pptx file
copy /ppt/slides/slide1.xml to /ppt/slides/slide2.xml
copy /ppt/slides/_rels/slide1.xml.rels to /ppt/slides/_rels/slide2.xml.rels
replace Target="../charts/chart1.xml" as Target="../charts/chart2.xml" in /ppt/slides/_rels/slide2.xml.rels
copy /ppt/charts/chart1.xml to /ppt/charts/chart2.xml
copy /ppt/charts/_rels/chart1.xml.rels to /ppt/charts/_rels/chart2.xml.rels
replace Microsoft_Excel_Worksheet1.xlsx as Microsoft_Excel_Worksheet2.xlsx in /ppt/charts/_rels/chart2.xml.rels
repackage file using opc repackage file file.pptx
Open file.pptx using Powerpoint. File opens but there new slide is missing. No error messages
grep for chart1.xml and slide1.xml, find instances in [Content_Types].xml and /ppt/_rels/presentation.xml.rels
Add ... PartName="/ppt/charts/chart2.xml" ... and ... PartName="/ppt/charts/slide2.xml" ... to [Content_Types].xml
Add ... Target="slides/slide2.xml" ... to /ppt/_rels/presentation.xml.rels
repackage file using opc repackage file file.pptx
Open file.pptx using Powerpoint. No change. File opens but there new slide is missing. No error messages.

I'm going to rest my eyes for a bit. I will continue in the morning.

In presentation.xml there is a list of slides. The element is something like p:sldLst. If the slide doesn't appear there it won't show up in the deck.

Regarding the p:ph idx= values, I remember now that uniqueness is not the important factor, rather that they refer to a placeholder in the slide layout that has that matching idx value (which serves as the "key" for placeholders). They of course need to be unique within a particular slide layout, and will then be unique within a particular slide, but their matching is the most important bit.
Another good reason, I think, to defer using a placeholder until you get it working with a "placed" chart.
To place a chart as a "non-placeholder" shape, simply start with a slide having no placeholder (I'd go with a blank layout for this for the moment) and choose Insert > Chart from the menu, or perhaps from the Ribbon, depending on your PowerPoint version. This puts a chart on the slide as its own shape, without reference to a placeholder.
Regarding "manually changing the relationship", if you're going to get rid of a chart, you'll want to get rid of all of it, to make sure PowerPoint isn't choking on some other anomaly. For a chart, this would include changing the slide.rels relationship, as you did, then deleting chart2.xml, chart2.xml.rels, and also EmbeddedXlsx2.bin or whatever that part is named. I don't thing the Xlsx part has any relationships (.rels file), but if it did you'd want to get rid of that too.
In any case, I imagine it's safe to say referring to the same chart from two slides is either not allowed by PowerPoint, or is a bad idea even if it is allowed. I suppose I can imagine an edge case of some sort where editing a chart on one slide causes a copy on another slide to be updated (maybe), but it certainly wouldn't be the mainstream use case. I'd be strongly inclined to pursue the "clone a chart and its Xlsx when copying a slide having a chart" approach.
Btw, the research you're doing here is a valuable contribution, even if you don't end up submitting a pull request for the completed feature. This is exactly the course of experimentation anyone would need to pursue in preparation for a successful implementation of this feature :)

Again, thank you for your help.
I tried to add the "p:sldLst" tag but that corrupted my file. (It's probably because of a typo error on my part.)
I do not want to have two charts pointing to the same file, so it's alright that it's not possible.
I have had some success now, I was able to manually duplicate a slide in a pptx file.
I decided to create a new Powerpoint file with only a single sheet with a single chart (single.pptx) then duplicating the sheet and saving that as a new file (duplicate.pptx) and look at the difference between the two files.
[using PowerPoint] Duplicating  single.pptx to duplicate.pptx creates these new files:

duplicate/ppt/slides/slide2.xml
duplicate/ppt/slides/_rels/slide2.xml.rels
duplicate/ppt/embeddings/Microsoft_Excel_Worksheet2.xlsx
duplicate/ppt/charts/style2.xml
duplicate/ppt/charts/colors2.xml
duplicate/ppt/charts/chart2.xml
duplicate/ppt/charts/_rels/chart2.xml.rels

First, I checked the difference between the new files that had been created in the duplicate.pptx

Inppt/slides/slide1.xml the val attribute is val="1287465769"  but in ppt/slides/slide2.xml it's val="531334335".
(in <p:extLst><p:ext><p14:creationId val=...>)

The difference between ppt/slides/_rels/slide1.xml.rels vs ppt/slides/_rels/slide2.xml.rels.
In slide1.xml.rels the target attribute is Target="../charts/chart1.xml" but in slide2.xml.rels its Target="../charts/chart2.xml"

/ppt/charts/style1.xml is identical to /ppt/charts/style2.xml

/ppt/charts/colors1.xml is identical to /ppt/charts/colors2.xml

The only difference between /ppt/charts/chart1.xml and /ppt/charts/chart2.xml are several axis value attributes:
<c:barChart><c:axId val="-2124546544"/> vs <c:barChart><c:axId val="-2091599616"/>
<c:barChart><c:axId val="-2096492960"/> vs <c:barChart><c:axId val="-2066918304"/>
<c:catAx><c:crossAx val="-2096492960"/> vs <c:catAx><c:crossAx val="-2066918304"/>
<c:catAx><c:axId val="-2124546544"/> vs <c:catAx><c:axId val="-2091599616"/>
<c:valAx><c:axId val="-2096492960"/> vs <c:valAx><c:axId val="-2066918304/>
<c:valAx><c:axId val="-2124546544"/> vs <c:valAx><c:axId val="-2091599616/>

The difference between /ppt/charts/_rels/chart1.xml.rels and /ppt/charts/_rels/chart2.xml.rels is that the Target attribute.
Target="style1.xml" vs Target="style2.xml"
Target="colors1.xml" vs Target="colors2.xml"
Target="../embeddings/Microsoft_Excel_Worksheet1.xlsx" vs Target="../embeddings/Microsoft_Excel_Worksheet2.xlsx"

There is no (binary) difference between `/ppt/embeddings/Microsoft_Excel_Worksheet1.xlsx vs /ppt/embeddings/Microsoft_Excel_Worksheet2.xlsx.

Secondly, I checked the difference between files in the single.pptx vs duplicate.pptx
Note: If the files are not listed here, then there was no difference between them.

/[Content_Types].xml

These rows had been added in duplicate/[Content_Types].xml
<Override PartName="/ppt/slides/slide2.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>
<Override PartName="/ppt/charts/chart2.xml" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/>
<Override PartName="/ppt/charts/style2.xml" ContentType="application/vnd.ms-office.chartstyle+xml"/>
<Override PartName="/ppt/charts/colors2.xml" ContentType="application/vnd.ms-office.chartcolorstyle+xml"/>

/docProps/app.xml
The Paragraph tag content was changed from <Paragraphs>1</Paragraphs> to <Paragraphs>2</Paragraphs>
The Slides tag content was changed from <Slides>1</Slides> to <Slides>2</Slides>
The vt:i4 tag content (in <HeadingPairs><vt:variant>) was changed from <vt:i4>1</vt:i4> to <vt:i4>2</vt:i4>
The size attribute in vt:vector (invt:vector) was changed from <TitlesOfParts><vt:vector size="5" baseType="lpstr"> to <TitlesOfParts><vt:vector size="6" baseType="lpstr">
And one instance of <vt:lpstr>PowerPoint Presentation</vt:lpstr> was added into <TitlesOfParts><vt:vector ...>

/docProps/core.xml
Timestamp updated in <dcterms:modified> and <c:revision> content incremented by 1

/ppt/_rels/presentation.xml.rels
A <Relationship id="rId3"... Target="slides/slide2.xml"/> has been added.
I noticed that slide1.xml has id="rId2" and the slide2.xml got the next id number
and the relationships presProps, viewProps, theme1 and tableStyles got their id's incremented
(they were changed from rId3, rId4, rId5, rId6 to rId4, rId5, rId6, rId7, respectively).

/ppt/presentation.xml
A new <p:sldId>tag was added to <p:sldIdLst>. It had incremented the id and r:id attribute by one.

<p:sldIdLst>
	<p:sldId id="256" r:id="rId2"/>
        <p:sldId id="257" r:id="rId3"/>
</p:sldIdLst>

/ppt/viewProps.xml
Attribute lastView="sldThumbnailView" was added to the <p:viewPr> tag.

Thirdly, Manually duplicating.
I was able to manage to duplicate a sheet using these steps.

Extracted the single.pptx using opc

$ opc extract single.pptx single
$ cd single

I copied these files:

    cp /ppt/slides/slide1.xml /ppt/slides/slide2.xml
    cp /ppt/slides/_rels/slide1.xml.rels /ppt/slides/_rels/slide2.xml.rels
    cp /ppt/embeddings/Microsoft_Excel_Worksheet1.xlsx /ppt/embeddings/Microsoft_Excel_Worksheet2.xlsx
    cp /ppt/charts/style1.xml /ppt/charts/style2.xml
    cp /ppt/charts/colors1.xml /ppt/charts/colors2.xml
    cp /ppt/charts/chart1.xml /ppt/charts/chart2.xml
    cp /ppt/charts/_rels/chart1.xml.rels /ppt/charts/_rels/chart2.xml.rels

Edit the ppt/slides/_rels/slide2.xml.rels
Changed Target="../charts/chart1.xml" to Target="../charts/chart2.xml"
Edit the ppt/charts/_rels/chart2.xml.rels`` Changed Target="style1.xml"toTarget="style2.xml"ChangedTarget="colors1.xml"toTarget="colors2.xml"ChangedTarget="../embeddings/Microsoft_Excel_Worksheet1.xlsx"toTarget="../embeddings/Microsoft_Excel_Worksheet2.xlsx"`
Added these lines in [Content_Types].xml

<Override PartName="/ppt/slides/slide2.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>
<Override PartName="/ppt/charts/chart2.xml" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/>
<Override PartName="/ppt/charts/style2.xml" ContentType="application/vnd.ms-office.chartstyle+xml"/>
<Override PartName="/ppt/charts/colors2.xml" ContentType="application/vnd.ms-office.chartcolorstyle+xml"/>

Added the slide2 relationship to rels/presentation.xml._rel
Note: I only gave the new relationship the next iteration id ("rId7")

<Relationship Id="rId7" Target="slides/slide2.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" />

Added <p:sldId id="257" r:id="rId7"/> to <p:sldIdLst> in ppt/presentation.xml
Repackaged the single/ using opc

$ opc repackage single `duplicate.pptx`

Voila.

@biggihs

I tried to add the "p:sldLst" tag but that corrupted my file. (It's probably because of a error on my part.)

The element (turns out it is p:sldIdLst http://python-pptx.readthedocs.io/en/latest/dev/analysis/prs-properties.html#xml-specimens) will already be present in the presentation.xml part. You just need to make sure there's a p:sldId element in it for your slide.

[using PowerPoint] Duplicating  single.pptx to duplicate.pptx creates these new files:

duplicate/ppt/slides/slide2.xml
duplicate/ppt/slides/_rels/slide2.xml.rels
duplicate/ppt/embeddings/Microsoft_Excel_Worksheet2.xlsx
duplicate/ppt/charts/style2.xml
duplicate/ppt/charts/colors2.xml
duplicate/ppt/charts/chart2.xml
duplicate/ppt/charts/_rels/chart2.xml.rels

Hmm, this is interesting. The existing code doesn't create a styleN.xml and colorsN.xml parts when adding a chart, so those must be optional. For duplication purposes, the safest bet would be to copy them as well, but that might be a finer point worth looking into for anyone developing a production version.

First, I checked the difference between the new files that had been created in the duplicate.pptx

Inppt/slides/slide1.xml the val attribute is val="1287465769"  but in ppt/slides/slide2.xml it's val="531334335".
(in <p:extLst><p:ext><p14:creationId val=...>)

Hmm, I don't know what this value represents, and I expect it's poorly documented. It might make sense to look it up by the GUID of the extension elsewhere in that element. Sometimes that sheds some light. I would have guessed it was a timestamp, but the second value is less that the original value, so that wouldn't make a lot of sense. The other thing I can think of is a "Creator" ID, possibly different versions of PowerPoint or something.
If it mattered, I'd be inclined just to copy it over unchanged or leave out that p:ext element, whichever is easier.

The only difference between /ppt/charts/chart1.xml and /ppt/charts/chart2.xml are several axis value attributes:
<c:barChart><c:axId val="-2124546544"/> vs <c:barChart><c:axId val="-2091599616"/>
<c:barChart><c:axId val="-2096492960"/> vs <c:barChart><c:axId val="-2066918304"/>
<c:catAx><c:crossAx val="-2096492960"/> vs <c:catAx><c:crossAx val="-2066918304"/>
<c:catAx><c:axId val="-2124546544"/> vs <c:catAx><c:axId val="-2091599616"/>
<c:valAx><c:axId val="-2096492960"/> vs <c:valAx><c:axId val="-2066918304/>
<c:valAx><c:axId val="-2124546544"/> vs <c:valAx><c:axId val="-2091599616/>

I don't know why PowerPoint makes Axis IDs unique across all charts in a presentation. Individual axes in charts created by python-pptx are unique within the chart, but the same for each chart of that type (they're part of the boilerplate XML used to create a specific chart type).
I expect you can copy them without change and not experience any problems, since that's what you'd get if you created both charts using python-pptx.

/docProps/app.xml
The Paragraph tag content was changed from <Paragraphs>1</Paragraphs> to <Paragraphs>2</Paragraphs>
The Slides tag content was changed from <Slides>1</Slides> to <Slides>2</Slides>
The vt:i4 tag content (in <HeadingPairs><vt:variant>) was changed from <vt:i4>1</vt:i4> to <vt:i4>2</vt:i4>
The size attribute in vt:vector (invt:vector) was changed from <TitlesOfParts><vt:vector size="5" baseType="lpstr"> to <TitlesOfParts><vt:vector size="6" baseType="lpstr">
And one instance of <vt:lpstr>PowerPoint Presentation</vt:lpstr> was added into <TitlesOfParts><vt:vector ...>

python-pptx doesn't modify the app.xml part, which hasn't caused a problem yet. So I expect all these changes could be skipped. The only thing I think it would affect is properties seen in a file browser, like the Finder on a Mac and Windows Explorer on a PC.

/docProps/core.xml
Timestamp updated in <dcterms:modified> and <c:revision> content incremented by 1

python-pptx doesn't update this item either.

/ppt/_rels/presentation.xml.rels
A <Relationship id="rId3"... Target="slides/slide2.xml"/> has been added.
I noticed that slide1.xml has id="rId2" and the slide2.xml got the next id number
and the relationships presProps, viewProps, theme1 and tableStyles got their id's incremented
(they were changed from rId3, rId4, rId5, rId6 to rId4, rId5, rId6, rId7, respectively).

The rIdN values chosen have no significance, as long as they are unique and consistent. They are arbitrary keys. In a python-pptx context, you'd just choose the next available key for a new slide and be done with it. There's a method for that somewhere in the packaging API.

/ppt/viewProps.xml
Attribute lastView="sldThumbnailView" was added to the <p:viewPr> tag.

python-pptx doesn't update viewProps.xml either. This one is safely skipped.

... and Voila.

Congratulations! Looks like you've discovered the recipe :) I wish I could tell you it usually isn't this hard, but in fact is often is :)  There are a lot of details that aren't documented and have to be worked out by extended searching and experimentation.

I needed this feature asap, so I created a method to duplicate a slide in a powerpoint file. It just does it brute-force by extracting the file, doing the copies/edits files and then repackaging it.
I would like to implement this feature correctly into python-pptx using the information in this issue and the correct methodologies. I however don't fully understand how all the pieces fit together in the python-pptx library so I would need help/guidance to finish the job sufficiently. Would you be willing to point me in the right direction and tell me what requirements you have towards the feature?
Here is my code, until the feature gets implemented in python-pptx.
https://gist.github.com/biggihs/b8e1374a9f282b117d171f020fe6be45

@biggihs I'm happy to help you as time allows. I've just started a new gig so will have somewhat less time for that sort of thing than I do "between" gigs :)
The first step is getting the research/analysis documented in an analysis page (docs/dev/analysis/...). That's a good place to start and is separately committable.
I expect a good part of that would be just organizing the information that has come out on this thread and adding in some extracts from the XML schema files and so on. Let me know if you need help getting started with that.

Thank you. I won't be able to add the documenation/analysis until after new years but that is where I will start. That will also probably help me understand how to put it together. Thanks again.

Yes, using the robintw's solution to duplicate slides can sometimes force PowerPoint to show 'Repair this file' message on startup..but good thing is - not always

I am also looking for this feature. My use case is merge a bunch of powerpoints into one.

FWIW, this is how I added chart support:
from pptx.parts.chart import ChartPart
from pptx.parts.embeddedpackage import EmbeddedXlsxPart

def _get_blank_slide_layout(pres):
    layout_items_count = [len(layout.placeholders)
                          for layout in pres.slide_layouts]
    min_items = min(layout_items_count)
    blank_layout_id = layout_items_count.index(min_items)
    return pres.slide_layouts[blank_layout_id]

def duplicate_slide(pres, index):
    """Duplicate the slide with the given index in pres.

    Adds slide to the end of the presentation"""
    source = pres.slides[index]
    blank_slide_layout = _get_blank_slide_layout(pres)
    dest = pres.slides.add_slide(blank_slide_layout)

    for shape in source.shapes:
        newel = copy.deepcopy(shape.element)
        dest.shapes._spTree.insert_element_before(newel, 'p:extLst')

    for key, value in source.part.rels.items():
        # Make sure we don't copy a notesSlide relation as that won't exist
        if "notesSlide" not in value.reltype:
            target = value._target
            # if the relationship was a chart, we need to duplicate the embedded chart part and xlsx
            if "chart" in value.reltype:
                partname = target.package.next_partname(
                    ChartPart.partname_template)
                xlsx_blob = target.chart_workbook.xlsx_part.blob
                target = ChartPart(partname, target.content_type,
                                   copy.deepcopy(target._element), package=target.package)

                target.chart_workbook.xlsx_part = EmbeddedXlsxPart.new(
                    xlsx_blob, target.package)

            dest.part.rels.add_relationship(value.reltype,
                                            target,
                                            value.rId)

    return dest

I am trying to the duplicate slide code posted by nshgraph.
ChartPart is not defined here:
                   ChartPart.partname_template)

@jsolack did you import it? from pptx.parts.chart import ChartPart

I have tried implementing the code of @zhong2000 . I have two seperate .pptx files with the same theme, slidemaster and slide-layouts. When I copy a slide from one presentation to the other, I get the error: UserWarning: Duplicate name: 'ppt/slideLayouts/slideLayout1.xml' (also for ppt/theme/theme1.xml and ppt/slideMasters/slideMaster1.xml).
When I look into the files within the generated PowerPoint file, I see the duplicates showing up:

I think that the copy.deepcopy copies the slide, including it's slidemaster, slidelayouts and theme. Is there a way to only copy the slide, instead of everything (or maybe increment the index of the slidemaster, slidelayouts and theme) so I don't get a duplicate error?
When I open the saved file in PowerPoint, the slides are pasted (so copying does work). It just shows a repair-box all the time, which has to do with the above. Copying slides within one PowerPoint presentation works completely fine without getting any errors.
Code for saving the file (on top of code from @zhong2000 ):
from pptx import Presentation
import six
import copy

prs1 = Presentation('test2.pptx')
prs2 = Presentation('test.pptx')

copy_slide(prs1, prs2, 0)

prs2.save('2.pptx')

@robintw thank you
With slight modification of code, it is able to copy slide from template to new ppt.
It is a great improvement to me. My proj is ppt report auto generation, I made many ppt template for various requirement before . now I can summary identical format into one ppt , then do the iteration of slide copy and content substitution.
known bug: bg and some format will be lost. Although it is not critical for me, hope you can help .
    def _get_blank_slide_layout(pres):
         layout_items_count = [len(layout.placeholders) for layout in pres.slide_layouts]
         min_items = min(layout_items_count)
         blank_layout_id = layout_items_count.index(min_items)
         return pres.slide_layouts[blank_layout_id]

    def copy_slide(pres,pres1,index):
         source = pres.slides[index]

         blank_slide_layout = _get_blank_slide_layout(pres)
         dest = pres1.slides.add_slide(blank_slide_layout)

         for shp in source.shapes:
              el = shp.element
              newel = copy.deepcopy(el)
              dest.shapes._spTree.insert_element_before(newel, 'p:extLst')

              for key, value in six.iteritems(source.rels):
                         # Make sure we don't copy a notesSlide relation as that won't exist
                       if not "notesSlide" in value.reltype:
                               dest.rels.add_relationship(value.reltype, value._target, value.rId)

              return dest

@Motta23
My case is same as you. Do you have any new solution? If no copy rels, it will work fine with text in the slide, but images are lost.
Anybody have any good idea about this?

What about duplicating the layout and formatting?
I am looking into taking all the layouts and designs in Slide 1 and populating them with text/content from Slide 2. The task seems to be really hectic... any recommendations would be appreciated. Thanks!

I am also looking for this feature. My use case is merge a bunch of powerpoints into one.

I also trying to merge powerpoint files.
Did anyone find any way to do it ?

I will try to extract some slides from seveal pptx file
below code works very well sometimes,
but, it doesn't work when slides  include video clip, etc....
It makes error ''str' object has no attribute 'rels'
If I remove code "for key, value in source.part.rels.items(): ---------------
dest.part.rels.add_relationship(value.reltype, value._target, value.rId)"
It works, but slide's quality is not good
Please Help me, I want to extract good quality slide from pptx file
How can I fix error 'str' object has no attribute 'rels'?
I am using python-pptx v0.6.18,  python 3.7
`def _get_blank_slide_layout(pres):
layout_items_count = [len(layout.placeholders)
for layout in pres.slide_layouts]
min_items = min(layout_items_count)
blank_layout_id = layout_items_count.index(min_items)
return pres.slide_layouts[blank_layout_id]
def copy_slide(pres, pres1, index):
source = pres.slides[index]
blank_slide_layout = _get_blank_slide_layout(pres)
dest = pres1.slides.add_slide(blank_slide_layout)
for shape in source.shapes:
    newel = copy.deepcopy(shape.element)
    dest.shapes._spTree.insert_element_before(newel, 'p:extLst')

for key, value in source.part.rels.items():
    if not "notesSlide" in value.reltype:
        dest.part.rels.add_relationship(value.reltype, value._target, value.rId)
return dest

`

Is there a way to copy the noteSlides to the new slides? Everyone seems to be avoiding noteSlide because there is a different relationship for it, but is there a way to modify the code to add this functionality?

@micbenn  Thanks, Still I don't  solve my Problem.
But, after some test, I find some cause that makes 'str' object has no attribute 'rels' error.
When I save presentation that include external hyperlink shape, error occurced
so I want to remove external hyperlink in shape, before presentation saved
How Can I remove external hyperlink in shape? Please help me

In my case, my source slide has a background not as a part of master or layout template, but as <p:cSld><p:bg><p:bgPr>... structure.
So, to make it work,
I added following code at the end of the function before returning the dest.
if source.background:
    el = source.background._cSld.bg.bgPr
    newel = copy.deepcopy(el)
    cSld = dest.background._cSld
    cSld.get_or_add_bgPr()
    cSld.bg._remove_bgPr()
    cSld.bg._insert_bgPr(newel)

@micbenn Thanks, Still I don't solve my Problem.
But, after some test, I find some cause that makes 'str' object has no attribute 'rels' error.
When I save presentation that include external hyperlink shape, error occurced
so I want to remove external hyperlink in shape, before presentation saved
How Can I remove external hyperlink in shape? Please help me

@biggihs Did you solve the problem. I'm also facing the issue.
Can you please share the solution

Has anyone tried duplicating the same slide multiple times into a new pptx file? The code from @nshgraph works for me if I duplicate a slide only one time, although I always get a prompt to let powerpoint repair the file.
When duplicating more than once, the first slide will be shown but the others are just blank...
I also get a lot of these error warnings when saving to pptx.

@marcomilov Did you solve the problem. I'm also getting duplicate warning and unable to open in Microsoft.
How can we skip saving SlideLayouts.

Tengo la solución a estas advertencias, lo único que tuve que hacer fue agregar un if antes del "dest.part.rels.add_relationship" de la siguiente manera:
for key, value in source.part.rels.items():
      # Make sure we don't copy a notesSlide relation as that won't exist
      if "notesSlide" not in value.reltype:
      target = value._target
      # if the relationship was a chart, we need to duplicate the embedded chart part and xlsx
      if "chart" in value.reltype:
          partname = target.package.next_partname(
              ChartPart.partname_template)
          xlsx_blob = target.chart_workbook.xlsx_part.blob
          target = ChartPart(partname, target.content_type,
                         copy.deepcopy(target._element), package=target.package)

          target.chart_workbook.xlsx_part = EmbeddedXlsxPart.new(
              xlsx_blob, target.package)

      if not "xml" in str(value.target_ref):
          if value.is_external:
              dest.part.rels.add_relationship(value.reltype, value.target_ref, value.rId, value.is_external)
          else:
              dest.part.rels.add_relationship(value.reltype, value._target, value.rId) #value.target_part

Basicamente, es un if que ignora los archivos xml en los ítems recorridos, que al final son los que se duplican en los registros al agregarlos como una relación, no aseguro que esta solución les sirva a todos pero al menos en mi caso no mostró las advertencias ni mostró el message box de powerpoint con la opción de "reparar" la presentación, y la diapositiva se duplicó exitosamente.

I had to machine translate @Lirioooo response, but it seemed to suggest checking for .xml file endings in the target ref to avoid a corrupted file when opening a modified file. This did not work for me. I unzipped the output file and logically the slide relations don't contain the .xml relations - yet those are required for e.g. the chart to work.
My current solution is to downgrade to version 0.6.19 and then the answer suggested solution should work.
With the current release (version 0.6.21)  i did not come up with a working solution. I managed to adapt some parts, but the output is still a broken file.
def pptx_copy_slide(pres: pptx.Presentation, source: pptx.slide.Slide):
    dest = pres.slides.add_slide(source.slide_layout)
    for shape in dest.shapes:
        shape.element.getparent().remove(shape.element)

    for shape in source.shapes:
        new_shape = copy.deepcopy(shape.element)
        dest.shapes._spTree.insert_element_before(new_shape, 'p:extLst')

    for rel in source.part.rels:
        #dest.part.rels.add_relationship(rel.reltype, rel._target, rel.rId, rel.is_external)
        # dest.part.rels._add_relationship(rel.reltype, rel._target, rel.is_external)

        target = rel._target

        if "notesSlide" in rel.reltype:
            continue

        if 'chart' in rel.reltype:
            # https://github.com/scanny/python-pptx/issues/132#issuecomment-414001942
            # Does not work for Treemap, modern charts.
            # TODO: Replace with underlying lxml code
            partname = target.package.next_partname(pptx.parts.chart.ChartPart.partname_template)
            xlsx_blob = target.chart_workbook.xlsx_part.blob
            target = pptx.parts.chart.ChartPart(
                partname = partname,
                content_type = target.content_type,
                element = copy.deepcopy(target._element),
                package=target.package)
            target.chart_workbook.xlsx_part = pptx.parts.chart.EmbeddedXlsxPart.new(
                blob=xlsx_blob,
                package=target.package)

        if rel.is_external:
            dest.part.rels.get_or_add_ext_rel(rel.reltype, rel._target)
        else:
            dest.part.rels.get_or_add(rel.reltype, rel._target)

    return dest

Hey all, has anyone found a stable solution for copying slides? None of the above suggestions worked for me (python-pptx==0.6.19):

When a function produced a .pptx, my PowerPoint couldn't open it properly but had to 'repair' it.
After repair,  @nshgraph 's solution was closest to providing a copy, but the formatting was screwed up.

FWIW the use case I'm pursuing is splitting a PPT with multiple pages into multiple PPT's with a single page each.

Hi @scanny any updates for this please ?

T

I don't know if my solution is reliable, and if it will work for all kinds of presentations and charts into them, but with this code I successfully managed to merge two presentations and obtain a clean, uncorrupted file which opened successfully in Microsoft PowerPoint without any warning.
def _get_blank_slide_layout(pres):
    layout_items_count = [len(layout.placeholders) for layout in pres.slide_layouts]
    min_items = min(layout_items_count)
    blank_layout_id = layout_items_count.index(min_items)
    layout_0 = pres.slide_layouts[blank_layout_id]
    for shape in layout_0.shapes:
        sp = shape.element
        sp.getparent().remove(sp)
    return layout_0

def move_slides(prs1, prs2):
    """Duplicate each slide in prs2 and "moves" it into prs1.
    Adds slides to the end of the presentation"""
    for slide in prs2.slides:
        sl = prs1.slides.add_slide(_get_blank_slide_layout(prs1))
        for shape in slide.shapes:
            newel = copy.deepcopy(shape.element)
            sl.shapes._spTree.insert_element_before(newel, 'p:extLst')
        try:
            sl.shapes.title.text = slide.shapes.title.text
            sl.placeholders[0].text = slide.placeholders[0].text
        except Exception as e:
            print(f"Error \"{e}\", suppressing it...")
    return prs1

This did the job for my case! Thx!

Minor quibble: You're calling _get_blank_slide_layout for every slide you add.
More importantly, I'd be curious to know what kinds of objects are on the slides you're copying in. For example, graphics.

Hi all,
I created a small set of utilities to clone a chart completely, which work for my simple use cases.
These functions read the PPTX XML structure to replicate the styling and colors. As of now I have yet to find issues, but I am happy to improve on them.
This is the way to use them:
dest = slide.shapes
graphical_frame = # pptx object Graphical Frame containing the chart
result = clone_chart(graphical_frame, dest)

And these are the utilities:
from typing import Union

import pandas as pd

def chart_to_dataframe(graphical_frame) -> pd.DataFrame:
    """
    Helper to parse chart data to a DataFrame.

    :param graphical_frame:
    :return:
    """
    from openpyxl import load_workbook

    from io import BytesIO
    wb = load_workbook(BytesIO(graphical_frame.chart.part.chart_workbook.xlsx_part.blob))

    ws = wb.active

    from itertools import islice
    import pandas as pd
    data = ws.values
    cols = next(data)[1:]
    data = list(data)
    idx = [r[0] for r in data]
    data = (islice(r, 1, None) for r in data)
    df = pd.DataFrame(data, index=idx, columns=cols)

    return df

def dataframe_to_chart_data(df):
    """
    Transforms a DataFrame to a CategoryChartData for PPT compilation.

    The indexes of the DataFrame are the categories, with each column becoming a series.

    :param df:
    :return:
    """
    from pptx.chart.data import CategoryChartData
    import numpy as np

    copy_data = CategoryChartData()
    copy_data.categories = df.index.astype(str).to_list()

    edge_cases = 0
    for c in df.columns:
        series_data = df[c].copy()
        fixed_series_data = series_data.replace([np.inf, -np.inf, np.nan], None)

        edge_cases = edge_cases + np.count_nonzero(fixed_series_data != series_data)

        copy_data.add_series(str(c), fixed_series_data.to_list())

    # Warning over data filled for compatibility
    if edge_cases > 0:
        import warnings
        warnings.warn("Series data containing NaN/INF values: filled to empty")

    return copy_data

def clone_chart(graphical_frame, dest):
    """
    Helper to clone a chart with related styling.

    :param graphical_frame:
    :param dest:
    :return:
    """
    chart = graphical_frame.chart

    df = chart_to_dataframe(graphical_frame)
    chart_data = dataframe_to_chart_data(df)

    new_chart = dest.shapes.add_chart(
        chart.chart_type,
        graphical_frame.left,
        graphical_frame.top,
        graphical_frame.width,
        graphical_frame.height,
        chart_data
    )

    # Fix offset for Graphical shape
    import copy
    cur_el = new_chart._element.xpath(".//p:nvGraphicFramePr")[0]
    ref_el = graphical_frame._element.xpath(".//p:nvGraphicFramePr")[0]
    parent = cur_el.getparent()
    parent.insert(
        parent.index(cur_el) + 1,
        copy.deepcopy(ref_el)
    )
    parent.remove(cur_el)

    # Clone styling from old chart to new one
    from random import randrange
    from lxml import etree
    from pptx.oxml import parse_xml

    id_attribute = '{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id'

    old_chart_ref_id = graphical_frame.element.xpath(".//c:chart")[0].attrib[id_attribute]
    chart_ref_id = new_chart.element.xpath(".//c:chart")[0].attrib[id_attribute]

    new_chart_part = new_chart.part.rels._rels[chart_ref_id].target_part
    old_chart_part = graphical_frame.part.rels._rels[old_chart_ref_id].target_part

    chart_data_reference_id = new_chart_part._element.xpath(".//c:externalData")[0].attrib[id_attribute]

    cloned_styling = copy.deepcopy(old_chart_part._element)
    cloned_styling.xpath(".//c:externalData")[0].set(id_attribute, chart_data_reference_id)
    cloned_styling.xpath(".//c:autoUpdate")[0].set("val", "1")
    new_chart_part.part._element = cloned_styling

    # Parse other relationships of the chart
    from pptx.opc.constants import CONTENT_TYPE as CT, RELATIONSHIP_TYPE as RT
    from pptx.opc.package import XmlPart

    class ColorsPart(XmlPart):
        partname_template = "/ppt/charts/colors%d.xml"

        @classmethod
        def new(cls, package, element):
            part = cls.load(
                package.next_partname(cls.partname_template),
                CT.OFC_CHART_COLORS,
                package,
                element,
            )
            return part

    class StylePart(XmlPart):
        partname_template = "/ppt/charts/style%d.xml"

        @classmethod
        def new(cls, package, element):
            part = cls.load(
                package.next_partname(cls.partname_template),
                CT.OFC_CHART_STYLE,
                package,
                element,
            )
            return part

    new_chart_refs = new_chart_part.rels
    old_chart_refs = old_chart_part.rels

    # Fix styling and colors applied to the new chart
    for k, v in dict(old_chart_refs._rels).items():
        if v.reltype == 'http://schemas.microsoft.com/office/2011/relationships/chartStyle':
            targ = v.target_part

            new_el = parse_xml(copy.deepcopy(targ.blob))
            new_el.set("id", str(randrange(10 ** 5, 10 ** 9)))
            new_colors_ref = StylePart.new(targ.package, etree.tostring(new_el))
            new_chart_refs.get_or_add("http://schemas.microsoft.com/office/2011/relationships/chartStyle",
                                      new_colors_ref)
        elif v.reltype == RT.CHART_COLOR_STYLE:
            targ = v.target_part

            new_el = parse_xml(copy.deepcopy(targ.blob))
            new_el.set("id", str(randrange(10 ** 5, 10 ** 9)))
            new_colors_ref = ColorsPart.new(targ.package, etree.tostring(new_el))
            new_chart_refs.get_or_add(RT.CHART_COLOR_STYLE, new_colors_ref)

    return new_chart

I also share the utilities I am using to duplicate a slide, applying the previous chart cloning functions.
These include a fix for creating a new slide, as internal file names could sometimes conflict when creating a new slide.
I do not ensure this works for all use cases, but I am happy to improve on them.
This is the way to use them:
new_slide = duplicate_slide(ppt, 1)

And these are the utilities:
def _exp_add_slide(ppt, slide_layout):
    """
    Function to handle slide creation in the Presentation, to avoid issues caused by default implementation.

    :param slide_layout:
    :return:
    """

    def generate_slide_partname(self):
        """Return |PackURI| instance containing next available slide partname."""
        from pptx.opc.packuri import PackURI

        sldIdLst = self._element.get_or_add_sldIdLst()

        existing_rels = [f.target_partname for f in self.rels]
        partname_str = "/ppt/slides/slide%d.xml" % (len(sldIdLst) + 1)

        while partname_str in existing_rels:
            import random
            import string

            random_part = ''.join(random.choice(string.ascii_letters) for i in range(2))
            partname_str = "/ppt/slides/slide%s%d.xml" % (random_part, len(sldIdLst) + 1)

        return PackURI(partname_str)

    def add_slide_part(self, slide_layout):
        """
        Return an (rId, slide) pair of a newly created blank slide that
        inherits appearance from *slide_layout*.
        """
        from pptx.opc.constants import RELATIONSHIP_TYPE as RT
        from pptx.parts.slide import SlidePart

        partname = generate_slide_partname(self)
        slide_layout_part = slide_layout.part
        slide_part = SlidePart.new(partname, self.package, slide_layout_part)
        rId = self.relate_to(slide_part, RT.SLIDE)
        return rId, slide_part.slide

    def add_slide_ppt(self, slide_layout):
        rId, slide = add_slide_part(self.part, slide_layout)
        slide.shapes.clone_layout_placeholders(slide_layout)
        self._sldIdLst.add_sldId(rId)
        return slide

    # slide_layout = self.get_master_slide_layout(slide_layout)
    return add_slide_ppt(ppt.slides, slide_layout)

def copy_shapes(source, dest):
    """
    Helper to copy shapes handling edge cases.

    :param source:
    :param dest:
    :return:
    """
    from pptx.shapes.group import GroupShape
    import copy

    # Copy all existing shapes
    for shape in source:
        if isinstance(shape, GroupShape):
            group = dest.shapes.add_group_shape()
            group.name = shape.name
            group.left = shape.left
            group.top = shape.top
            group.width = shape.width
            group.height = shape.height
            group.rotation = shape.rotation

            # Recursive copy of contents
            copy_shapes(shape.shapes, group)

            # Fix offset
            cur_el = group._element.xpath(".//p:grpSpPr")[0]
            ref_el = shape._element.xpath(".//p:grpSpPr")[0]
            parent = cur_el.getparent()
            parent.insert(
                parent.index(cur_el) + 1,
                copy.deepcopy(ref_el)
            )
            parent.remove(cur_el)

            result = group
        elif hasattr(shape, "image"):
            import io

            # Get image contents
            content = io.BytesIO(shape.image.blob)
            result = dest.shapes.add_picture(
                content, shape.left, shape.top, shape.width, shape.height
            )
            result.name = shape.name
            result.crop_left = shape.crop_left
            result.crop_right = shape.crop_right
            result.crop_top = shape.crop_top
            result.crop_bottom = shape.crop_bottom
        elif hasattr(shape, "has_chart") and shape.has_chart:
            from .charts import clone_chart
            result = clone_chart(shape, dest)
        else:
            import copy

            newel = copy.deepcopy(shape.element)
            dest.shapes._spTree.insert_element_before(newel, "p:extLst")
            result = dest.shapes[-1]

def duplicate_slide(ppt, slide_index: int):
    """
    Duplicate the slide with the given number in presentation.
    Adds the new slide by default at the end of the presentation.

    :param ppt:
    :param slide_index: Slide number
    :return:
    """
    source = ppt.slides[slide_index]

    dest = _exp_add_slide(ppt, source.slide_layout)

    # Remove all shapes from the default layout
    for shape in dest.shapes:
        remove_shape(shape)

    # Copy all existing shapes
    copy_shapes(source.shapes, dest, is_duplication=True)

    # Copy all existing shapes
    if source.has_notes_slide:
        txt = source.notes_slide.notes_text_frame.text
        dest.notes_slide.notes_text_frame.text = txt

    return dest

def remove_shape(shape):
    """
    Helper to remove a specific shape.

    :param shape:
    :return:
    """
    el = shape.element  # --- get reference to XML element for shape
    el.getparent().remove(el)  # --- remove that shape element from its tree

Nice @Dasc3er.  If you had to manipulate other parts it would've been tough.
It occurs to me an open source project that adds a "companion module" to python-pptx could work. python-pptx need then only be fixed to support eg new Python releases.

Hello, i found issues when trying to copy some element from slide to slide using the code bellow, i think this function can not support the copy of elements of type <p:sp> in the Open XML of the file. Any suggestions please !
`def _get_blank_slide_layout(pres):
layout_items_count = [len(layout.placeholders) for layout in pres.slide_layouts]
min_items = min(layout_items_count)
blank_layout_id = layout_items_count.index(min_items)
return pres.slide_layouts[blank_layout_id]
def duplicate_slide(pres, index):
"""Duplicate the slide with the given index in pres.
Adds slide to the end of the presentation"""
source = pres.slides[index]

blank_slide_layout = _get_blank_slide_layout(pres)
dest = pres.slides.add_slide(blank_slide_layout)

for shp in source.shapes:
    el = shp.element
    newel = copy.deepcopy(el)
    dest.shapes._spTree.insert_element_before(newel, 'p:extLst')

for key, value in six.iteritems(source.rels):
    # Make sure we don't copy a notesSlide relation as that won't exist
    if not "notesSlide" in value.reltype:
        dest.rels.add_relationship(value.reltype, value._target, value.rId)

return dest`

I had to machine translate @Lirioooo response, but it seemed to suggest checking for .xml file endings in the target ref to avoid a corrupted file when opening a modified file. This did not work for me. I unzipped the output file and logically the slide relations don't contain the .xml relations - yet those are required for e.g. the chart to work. My current solution is to downgrade to version 0.6.19 and then the answer suggested solution should work.
With the current release (version 0.6.21) i did not come up with a working solution. I managed to adapt some parts, but the output is still a broken file. Here is my current try:
def pptx_copy_slide(pres: pptx.Presentation, source: pptx.slide.Slide):
    dest = pres.slides.add_slide(source.slide_layout)
    for shape in dest.shapes:
        shape.element.getparent().remove(shape.element)

    for shape in source.shapes:
        new_shape = copy.deepcopy(shape.element)
        dest.shapes._spTree.insert_element_before(new_shape, 'p:extLst')

    for rel in source.part.rels:
        target = rel._target

        if "notesSlide" in rel.reltype:
            continue

        if 'chart' in rel.reltype:
            # https://github.com/scanny/python-pptx/issues/132#issuecomment-414001942
            partname = target.package.next_partname(pptx.parts.chart.ChartPart.partname_template)
            xlsx_blob = target.chart_workbook.xlsx_part.blob
            target = pptx.parts.chart.ChartPart(
                partname = partname,
                content_type = target.content_type,
                element = copy.deepcopy(target._element),
                package=target.package)
            target.chart_workbook.xlsx_part = pptx.parts.chart.EmbeddedXlsxPart.new(
                blob=xlsx_blob,
                package=target.package)

        if rel.is_external:
            dest.part.rels.get_or_add_ext_rel(rel.reltype, rel._target)
        else:
            dest.part.rels.get_or_add(rel.reltype, rel._target)

    return dest

Ideas why this is not working would be appreciated.
Honestly I think a different approach had some benefits, when you unzip the powerpoint file, duplicating a slide can be done fairly easily I think:

Duplicate the slide file and rename it
Duplicate the relation file and rename it
Link the slide in the presentation relations ("rels/presentation.xml.rels")
This would be fairly easy to do in python and you would account for all cases (e.g. no issues with charts or diagrams). The only problem is, that you would need to create a temp file and read it in again.

Have you had any success with the proposed approach? @Ithaam
I still cant get a clean file and duplicating the same slide multiple times is just not working. :(

Given solution works for copying slides from single file. It does not work when I am trying to copy slides from multiple files as it does not copy slide layouts from multiple files. It copies slide layouts only when I load a particular file in output presentation and I am able to load only a single file in output presentation. Is there any way to copy slides from multiple files including it's slide layouts and master styles.
Here is my version of code.
`def merge_presentations_list(path, outputPres, slide_list):
"""
Merge a list of PowerPoint presentations into a single presentation.
"""
templatePres = Presentation(path)
for i in range(len(templatePres.slides)):
if i in slide_list:
print('slide num', i)
move_slide(templatePres, i, outputPres)
return outputPres
def move_slide(copyFromPres, slideIndex, pasteIntoPres):
"""
Copy a slide from one presentation to another.
"""
print('copied slide index', slideIndex)
slide_to_copy = copyFromPres.slides[slideIndex]
slide_layout = slide_to_copy.slide_layout #This has no effect if I load empty presentation in output
new_slide = pasteIntoPres.slides.add_slide(slide_layout)
for shp in slide_to_copy.shapes:
    if 'Picture' in shp.name:
        with open(shp.name + '.jpg', 'wb') as f:
            f.write(shp.image.blob)
        new_slide.shapes.add_picture(shp.name + '.jpg', shp.left, shp.top, shp.width, shp.height)
        os.remove(shp.name + '.jpg')
    else:
        print(shp.name)
        el = shp.element
        newel = copy.deepcopy(el)
        new_slide.shapes._spTree.insert_element_before(newel, 'p:extLst')

return new_slide

def create_strawman_presentation(filenames, slidelists, output_root=None):
"""
Create a final PowerPoint presentation based on specified criteria.
"""
final_name = 'sample_ppt.pptx'
first_file = filenames[0]
#This is where I am reading first file in output presentation.
# I am able to copy all slide layouts of filtered slides from source file in output presentation.
# I need a way to copy slide with slide layouts with master styles from all the files.
outputPres = Presentation(first_file)

for i in range(filenames):
    source_file = filenames[i]
    slide_list = slidelists[i]
    outputPres = merge_presentations_list(path=source_file, outputPres=outputPres, slide_list=slide_list)

outputPres.save(os.path.join(output_root, final_name))
return final_name`

Given solution works for copying slides from single file. It does not work when I am trying to copy slides from multiple files as it does not copy slide layouts from multiple files. It copies slide layouts only when I load a particular file in output presentation and I am able to load only a single file in output presentation. Is there any way to copy slides from multiple files including it's slide layouts and master styles.

Here is my version of code.

def merge_presentations_list(path, outputPres, slide_list):
"""
Merge a list of PowerPoint presentations into a single presentation.
"""
templatePres = Presentation(path)
for i in range(len(templatePres.slides)):
if i in slide_list:
move_slide(templatePres, i, outputPres)
return outputPres

def move_slide(copyFromPres, slideIndex, pasteIntoPres):
"""
Copy a slide from one presentation to another.
"""
slide_to_copy = copyFromPres.slides[slideIndex]
slide_layout = slide_to_copy.slide_layout #This has no effect if I load empty presentation in output
new_slide = pasteIntoPres.slides.add_slide(slide_layout)

for shp in slide_to_copy.shapes:
    if Picture in shp.name:
        with open(shp.name + '.jpg', 'wb') as f:
            f.write(shp.image.blob)
        new_slide.shapes.add_picture(shp.name + '.jpg', shp.left, shp.top, shp.width, shp.height)
        os.remove(shp.name + '.jpg')
    else:
        print(shp.name)
        el = shp.element
        newel = copy.deepcopy(el)
        new_slide.shapes._spTree.insert_element_before(newel, 'p:extLst')

return new_slide'
def create_strawman_presentation(filenames, slidelists, output_root=None):
"""
Create a final PowerPoint presentation based on specified criteria.
"""
final_name = 'sample_ppt.pptx'
first_file = filenames[0]

#This is where I am reading first file in output presentation.
# I am able to copy all slide layouts of filtered slides from source file in output presentation.
# I need a way to copy slide with slide layouts with master styles from all the files.
outputPres = Presentation(first_file)

for i in range(filenames):
    source_file = filenames[i]
    slide_list = slidelists[i]
    outputPres = merge_presentations_list(path=source_file, outputPres=outputPres, slide_list=slide_list)

outputPres.save(os.path.join(output_root, final_name))
return final_name

Given solution works for copying slides from single file. It does not work when I am trying to copy slides from multiple files as it does not copy slide layouts from multiple files. It copies slide layouts only when I load a particular file in output presentation and I am able to load only a single file in output presentation. Is there any way to copy slides from multiple files including it's slide layouts and master styles.

Did you even check my post? Does exactely what you ask

Given solution works for copying slides from single file. It does not work when I am trying to copy slides from multiple files as it does not copy slide layouts from multiple files. It copies slide layouts only when I load a particular file in output presentation and I am able to load only a single file in output presentation. Is there any way to copy slides from multiple files including it's slide layouts and master styles.

Did you even check my post? Does exactely what you ask

Yes I have used your code. It was not working as expected for me so I made some modifications in the code as given above. I suppose your code copies content of the slides without copying slide layouts and styles. It loads a single file in output presentation and applies styles and layouts of that presentation to all other presentation.
I would like to copy slides with slide layouts and styles.

@rajeshm71  I am trying to copy a slide from template to new slide. Can you share the git link of your code.

Bumping this. A deep copy of slide layout would be useful.

Hello everyone, I have a new version of the function working on presentations with images, text and even tables. I still have not used it in presentation with graphs, maybe you could test it.
Here is my code:
def merge_presentations_list(paths: list, final_name: str = None):
    outputPres = Presentation(paths[0])

    for path in paths[1:]:
        templatePres = Presentation(path)
        for i in range(len(templatePres.slides)):  # We iterate over all the slides
            move_slide(templatePres, i, outputPres)

    if final_name:
        outputPres.save(final_name)
    return outputPres
def get_links_from_table(table):
    """This function saves to a dict in memory the data about table cells for later retrieval
        Since the cells are always read from left to right and top to bottom, we can use enumerate to use their index as an ID
        So we associate each hyperlink with the Idx of the cell where we found it
    """
    links = {}
    for idx, cell in enumerate(table.iter_cells()):
        if hasattr(cell, 'text_frame'):
            for paragraph in cell.text_frame.paragraphs:
                p = paragraph._p
                if hasattr(paragraph, 'runs'):
                    r = paragraph.runs[0]
                    if r.hyperlink.address:
                        links.update({idx: r.hyperlink.address})
    return links

def rewrite_hyperlinks_in_table(shape, table_hlink_data):
    """
    Rewrites hyperlinks in the table cells of a shape.

    Parameters:
    shape (Shape): The shape containing a table whose hyperlinks need to be restored.
    table_hlink_data (dict): A dictionary containing hyperlink data for table cells.
                             The keys correspond to cell indices, and the values are the hyperlink addresses.

    Note: This function matches the cell index with stored hyperlink data and re-applies the hyperlinks to the cells.
    """
    if shape.name in table_hlink_data:
        data = table_hlink_data[shape.name]
        for idx, cell in enumerate(shape.table.iter_cells()):
            if hasattr(cell, 'text_frame'):
                for paragraph in cell.text_frame.paragraphs:
                    if hasattr(paragraph, 'runs'):
                        for key in data:
                            # Re-assign the hyperlink if the cell index matches the saved data
                            if idx == key:
                                paragraph.runs[0].hyperlink._add_hlinkClick(data[idx])

def move_slide(copyFromPres: Presentation, slideIndex: int, pasteIntoPres: Presentation):
    """
    Copies a slide from one presentation to another.

    Parameters:
    copyFromPres (Presentation): The source presentation to copy from.
    slideIndex (int): The index of the slide to be copied from the source presentation.
    pasteIntoPres (Presentation): The target presentation to paste the copied slide into.

    Returns:
    Slide: The newly created slide in the target presentation.

    Note: The layout of the new slide in the target presentation is based on the first available layout.
    The function handles copying text, images, and hyperlinks. Special handling is required for tables and
    their hyperlinks, which are reconstructed separately.
    """

    # Copy the slide at the specified index
    slide_to_copy = copyFromPres.slides[slideIndex]

    # Use the first available layout in the target presentation
    slide_layout = pasteIntoPres.slide_layouts[0]

    # Create a new slide in the target presentation with the selected layout
    new_slide = pasteIntoPres.slides.add_slide(slide_layout)

    # Dictionary to store image data for re-adding after other elements
    imgDict = {}

    # Variables for handling hyperlinks
    haddress = None
    shape_w_hyperlink_id = None
    table_hlink_data = {}

    # Iterate through all shapes in the original slide
    for shp in slide_to_copy.shapes:

        # Handle images by saving them temporarily to avoid corrupt files
        if 'Picture' in shp.name:
            with open(shp.name + '.jpg', 'wb') as f:
                f.write(shp.image.blob)
            imgDict[shp.name + '.jpg'] = [shp.left, shp.top, shp.width, shp.height]

        else:
            # Copy the shape element
            el = shp.element

            # Handle text and potential hyperlinks in text
            if hasattr(shp, 'text_frame'):
                text_frame = shp.text_frame
                p = text_frame.paragraphs[0]

                # Check if the shape contains a hyperlink
                if p.runs:
                    r = p.runs[0]
                    shape_w_hyperlink_id = shp.shape_id
                    haddress = r.hyperlink.address

        # Handle tables and store their hyperlink data for later reconstruction
        if shp.has_table:
            table_hlink_data.update({shp.name: get_links_from_table(shp.table)})

        # Insert the copied shape into the new slide
        new_slide.shapes._spTree.insert_element_before(el, 'p:extLst')

        # Re-assign the hyperlink to the copied shape if it exists
        if haddress:
            for shape in new_slide.shapes:
                if shape.shape_id == shape_w_hyperlink_id:
                    try:
                        shape.text_frame.paragraphs[0].runs[0].hyperlink._add_hlinkClick(haddress)
                    except Exception as e:
                        message = f"Failed to move the hyperlink with id {shape_w_hyperlink_id}: {e}"
                        print(message)

    # Handle table hyperlinks by reconstructing them in the copied slide
    for shape in new_slide.shapes:
        if shape.has_table:
            rewrite_hyperlinks_in_table(shape, table_hlink_data)

    # Add images back to the new slide after copying all other elements
    for k, v in imgDict.items():
        new_slide.shapes.add_picture(k, v[0], v[1], v[2], v[3])
        os.remove(k)  # Remove the temporary image file

    # Handle potential issues with the title shape
    try:
        new_slide.shapes.title.text = ' '
    except Exception as e:
        print(f"Warning, unable to set title text: {e}")

    return new_slide

Hey all,
I had a lot of trouble with all of the different implementations folks made, which used python-pptx. The last one, courtesy of @Mike3285, mostly worked for me at copying the slide content, but all the layout and styling didn't get copied over.
Unless python-pptx adds native support to duplicate a slide, including its placement and all its styling, with the ability to place into another presentation, I can't imagine an elegant solution existing that uses that package. For anyone else in my shoes who's just looking for a simple solution to exactly copy slides from one PPT to another (on Windows), as easily as it is to do it through the GUI, here's a solution I came across. The main bit came from https://software-solutions-online.com/copy-slides-one-presentation-another-vba-powerpoint/, and I had ChatGPT help me turn it into a PPT script which uses win32com to dispatch the same COM messages as the VB script in the example.
import win32com.client

def copy_presentation(source_path, destination_path):
    # Create a PowerPoint application object
    ppt = win32com.client.Dispatch("PowerPoint.Application")

    # Make PowerPoint visible (optional)
    ppt.Visible = True

    # Open the source presentation
    pres1 = ppt.Presentations.Open(source_path)

    # Create a new presentation
    new_pres = ppt.Presentations.Add()

    # Copy each slide from the source presentation to the new presentation
    for i in range(1, pres1.Slides.Count + 1):
        pres1.Slides.Item(i).Copy()
        new_pres.Slides.Paste()
        new_pres.Slides.Item(new_pres.Slides.Count).Design = pres1.Slides.Item(i).Design

    # Save the new presentation
    new_pres.SaveAs(destination_path)

    # Close the presentations
    pres1.Close()
    new_pres.Close()

    # Quit PowerPoint application
    ppt.Quit()

    # Clean up
    del pres1
    del new_pres
    del ppt

# Example usage
source_pptx = r'path/to/source.pptx'
destination_pptx = r'path/to/destination.pptx'
copy_presentation(source_pptx, destination_pptx)

I think win32com is a great solution if you're on Windows, have PowerPoint installed, and are only doing one job at a time. I've used it myself several times, although not lately since I'm on a Mac now for 10 years or so :)
Those limitations are all show-stoppers though, if you're building a production application to run in the cloud in containers running Linux servers, etc., which is all the work I've done in the last several years.
You should definitely do what works for you, and if you're already on Windows with Office and just processing files for yourself/locally, I wouldn't hesitate to go the win32com route. There are other options like saving as PDF etc. that opens up and definitely much faster and less tedious than doing it all by hand.

I had a similar need and tried with many different combinations of code that I got from stackoverflow, this thread etc. Nothing really worked as I expected. My usecase was that I had to copy a slide from one Presentation object to another, but both had the same template. So the slide_masters and slide_layouts were same for both. After hours of searching, experimenting and chatting with chatgpt, below is the code that finally worked for me. May be someone finds it useful.
def find_layout_index(prs: Presentation, slide: Slide):
    """Finds the index of the layout of a slide in a presentation

    Args:
        prs (Presentation): Presentation to find the layout index in.
        slide (Slide): Slide to find the layout index of.

    Returns:
        tuple[int,int]: Index of the slide_master and index of the layout in the slide_master.slide_layouts.
    """
    layouts_per_master = [list(master.slide_layouts) for master in prs.slide_masters]
    slide_layout = slide.slide_layout
    for outer_index, inner_list in enumerate(layouts_per_master):
        if slide_layout in inner_list:
            inner_index = inner_list.index(slide_layout)
            return outer_index, inner_index
    raise ValueError("Slide layout not found in presentation")

def copy_placeholder_text(source_placeholder, target_placeholder):
    if not source_placeholder.has_text_frame or not target_placeholder.has_text_frame:
        return

    target_text_frame = target_placeholder.text_frame
    target_text_frame.clear()  # Clear any existing text

    for i, paragraph in enumerate(source_placeholder.text_frame.paragraphs):
        if i == 0:
            # First paragraph is already there
            new_paragraph = target_text_frame.paragraphs[0]
        else:
            new_paragraph = target_text_frame.add_paragraph()
        new_paragraph.level = paragraph.level

        # Copy paragraph formatting
        new_paragraph.font.bold = paragraph.font.bold
        new_paragraph.font.italic = paragraph.font.italic
        new_paragraph.font.underline = paragraph.font.underline
        new_paragraph.font.size = paragraph.font.size
        if paragraph.font.color and hasattr(paragraph.font.color, "rgb"):
            new_paragraph.font.color.rgb = paragraph.font.color.rgb
        new_paragraph.alignment = paragraph.alignment

        for run in paragraph.runs:
            new_run = new_paragraph.add_run()
            new_run.text = run.text

            # Copy run formatting
            new_run.font.bold = run.font.bold
            new_run.font.italic = run.font.italic
            new_run.font.underline = run.font.underline
            new_run.font.size = run.font.size
            if run.font.color and hasattr(run.font.color, "rgb") and run.font.color.rgb:
                new_run.font.color.rgb = run.font.color.rgb
            elif run.font.color and hasattr(run.font.color, "theme_color"):
                new_run.font.color.theme_color = run.font.color.theme_color
            if run.font.color and hasattr(run.font.color, "brightness") and run.font.color.brightness:
                new_run.font.color.brightness = run.font.color.brightness

def copy_slide(source_ppt: Presentation, target_ppt: Presentation, slide: Slide) -> Slide:
    """Duplicate each slide in source_ppt and "moves" it into target_ppt.
    Adds slides to the end of the presentation

    Args:
        source_ppt (Presentation): Source presentation.
        target_ppt (Presentation): Target presentation.
        slide (Slide): Slide to copy.

    Returns:
        Slide: Slide that was copied
    """
    master_index, layout_index = find_layout_index(source_ppt, slide)
    target_ppt_layouts = [list(master.slide_layouts) for master in target_ppt.slide_masters]
    new_slide = target_ppt.slides.add_slide(target_ppt_layouts[master_index][layout_index])
    for new_ph, old_ph in zip(new_slide.placeholders, slide.placeholders):
        new_ph.height = old_ph.height
        new_ph.width = old_ph.width
        new_ph.left = old_ph.left
        new_ph.top = old_ph.top
        copy_placeholder_text(old_ph, new_ph)
    for shape in slide.shapes:
        if shape.is_placeholder:
            continue
        newel = copy.deepcopy(shape.element)
        new_slide.shapes._spTree.insert_element_before(newel, "p:extLst")
    # copy slide notes
    if slide.notes_slide:
        for new_ph, old_ph in zip(
            new_slide.notes_slide.placeholders, slide.notes_slide.placeholders
        ):
            copy_placeholder_text(old_ph, new_ph)
        new_slide_notes = new_slide.notes_slide
        for shape in slide.notes_slide.placeholders:
            if shape.is_placeholder:
                continue
            newel = copy.deepcopy(shape.element)
            new_slide_notes.shapes._spTree.insert_element_before(newel, "p:extLst")
    return new_slide

Shamless plug: pptx-renderer - package to create presentations from templates by replacing python placeholders with their values.

I tried all of them, and the implementation by @Dasc3er looks like the best  to be the best fit for my case, which involves copying a slide within the same presentation. So far, the only issue I’ve encountered is that the slide background (whether a solid color, gradient, picture, etc.) is not being copied. Does anyone have any suggestions on how to copy the slide background?

I also share the utilities I am using to duplicate a slide, applying the previous chart cloning functions. These include a fix for creating a new slide, as internal file names could sometimes conflict when creating a new slide.
I do not ensure this works for all use cases. GIST with the aggregated utilities: https://gist.github.com/Dasc3er/2af5069afb728c39d54434cb28a1dbb8
This is the way to use them:
new_slide = duplicate_slide(ppt, 1)

And these are the utilities:
def _object_rels(obj):
    rels = obj.rels

    # Change required for python-pptx 0.6.22
    check_rels_content = [k for k in rels]
    if isinstance(check_rels_content.pop(), str):
        return [v for k, v in rels.items()]
    else:
        return [k for k in rels]

def _exp_add_slide(ppt, slide_layout):
    """
    Function to handle slide creation in the Presentation, to avoid issues caused by default implementation.

    :param slide_layout:
    :return:
    """

    def generate_slide_partname(self):
        """Return |PackURI| instance containing next available slide partname."""
        from pptx.opc.packuri import PackURI

        sldIdLst = self._element.get_or_add_sldIdLst()

        existing_rels = [k.target_partname for k in _object_rels(self)]
        partname_str = "/ppt/slides/slide%d.xml" % (len(sldIdLst) + 1)

        while partname_str in existing_rels:
            import random
            import string

            random_part = ''.join(random.choice(string.ascii_letters) for i in range(2))
            partname_str = "/ppt/slides/slide%s%d.xml" % (random_part, len(sldIdLst) + 1)

        return PackURI(partname_str)

    def add_slide_part(self, slide_layout):
        """
        Return an (rId, slide) pair of a newly created blank slide that
        inherits appearance from *slide_layout*.
        """
        from pptx.opc.constants import RELATIONSHIP_TYPE as RT
        from pptx.parts.slide import SlidePart

        partname = generate_slide_partname(self)
        slide_layout_part = slide_layout.part
        slide_part = SlidePart.new(partname, self.package, slide_layout_part)
        rId = self.relate_to(slide_part, RT.SLIDE)
        return rId, slide_part.slide

    def add_slide_ppt(self, slide_layout):
        rId, slide = add_slide_part(self.part, slide_layout)
        slide.shapes.clone_layout_placeholders(slide_layout)
        self._sldIdLst.add_sldId(rId)
        return slide

    # slide_layout = self.get_master_slide_layout(slide_layout)
    return add_slide_ppt(ppt.slides, slide_layout)

def copy_shapes(source, dest):
    """
    Helper to copy shapes handling edge cases.

    :param source:
    :param dest:
    :return:
    """
    from pptx.shapes.group import GroupShape
    import copy

    # Copy all existing shapes
    for shape in source:
        if isinstance(shape, GroupShape):
            group = dest.shapes.add_group_shape()
            group.name = shape.name
            group.left = shape.left
            group.top = shape.top
            group.width = shape.width
            group.height = shape.height
            group.rotation = shape.rotation

            # Recursive copy of contents
            copy_shapes(shape.shapes, group)

            # Fix offset
            cur_el = group._element.xpath(".//p:grpSpPr")[0]
            ref_el = shape._element.xpath(".//p:grpSpPr")[0]
            parent = cur_el.getparent()
            parent.insert(
                parent.index(cur_el) + 1,
                copy.deepcopy(ref_el)
            )
            parent.remove(cur_el)

            result = group
        elif hasattr(shape, "image"):
            import io

            # Get image contents
            content = io.BytesIO(shape.image.blob)
            result = dest.shapes.add_picture(
                content, shape.left, shape.top, shape.width, shape.height
            )
            result.name = shape.name
            result.crop_left = shape.crop_left
            result.crop_right = shape.crop_right
            result.crop_top = shape.crop_top
            result.crop_bottom = shape.crop_bottom
        elif hasattr(shape, "has_chart") and shape.has_chart:
            from .charts import clone_chart
            result = clone_chart(shape, dest)
        else:
            import copy

            newel = copy.deepcopy(shape.element)
            dest.shapes._spTree.insert_element_before(newel, "p:extLst")
            result = dest.shapes[-1]

def duplicate_slide(ppt, slide_index: int):
    """
    Duplicate the slide with the given number in presentation.
    Adds the new slide by default at the end of the presentation.

    :param ppt:
    :param slide_index: Slide number
    :return:
    """
    source = ppt.slides[slide_index]

    dest = _exp_add_slide(ppt, source.slide_layout)

    # Remove all shapes from the default layout
    for shape in dest.shapes:
        remove_shape(shape)

    # Copy all existing shapes
    copy_shapes(source.shapes, dest, is_duplication=True)

    # Copy all existing shapes
    if source.has_notes_slide:
        txt = source.notes_slide.notes_text_frame.text
        dest.notes_slide.notes_text_frame.text = txt

    return dest

def remove_shape(shape):
    """
    Helper to remove a specific shape.

    :source: https://stackoverflow.com/questions/64700638/is-there-a-way-to-delete-a-shape-with-python-pptx

    :param shape:
    :return:
    """
    el = shape.element  # --- get reference to XML element for shape
    el.getparent().remove(el)  # --- remove that shape element from its tree

Edit: fixes for python-pptx 0.6.22


feat: handle noConfiguration plugins

 Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.You have signed the CLA already but the status is still pending? Let us recheck it.


Add tests

We need to add tests here to ensure JSS updates don't break this package. Anyone interested to help?

@kohlmannj maybe?

@msnider mb you are also interested in this one

Yeah, I can help on this


setBitmapImage() problem

I'm setting only for the first element of the timeline (TYPE_START) a bitmap image, but when I scroll the list, the bitmap is drawn even for TYPE_MIDDLE elements. How can I solve this problem?
Thanks in advance

I will check :)

Let me know :+1:

i have error relating to bitmap. the error message for bitmap is "cant resolve symbol" and error for internalBitmapCache is "its not public in com.alorma.timeline....
here is the code that getting red:
@Override     protected void drawBitmap(Canvas canvas, float left, float top, int size) {         if (bitmap != null) {             if (internalBitmapCache == null) {                 internalBitmapCache = transform(bitmap, size);             }             if (internalBitmapCache != null) {                 canvas.drawBitmap(internalBitmapCache, left, top, null);             }         }     }
Any suggestion please?


CompatOCISpec: limit usage of CompatOCISpec

Fixes: #2023
CompatOCISpec is used to gurantee backward compatbility for old runtime
specs, after we convert CompatOCISpec to standard specs.Spec, we should
use specs.Spec instead of CompatOCISpec, and CompatOCISpec should be
useless from then.
Spread usage of CompatOCISpec can make code structure confusing and making
the runtime spec usage non-standard. Besides, this can be the very first
step of removing CompatOCISpec from config's Annotations field.
Signed-off-by: Wei Zhang<EMAIL_ADDRESS>

/test

/test

/test

ping @jodh-intel @bergwolf @sboeuf

This is ready to land but let's just see if @lifupan or @bergwolf have any comments...

@bergwolf @lifupan please merge when you are happy with this!