Skip to content

File copy progress window with PyQt/PySide and shutil

I recently wanted to show the progress of a file copy made from a python script (or actually, from a python script running inside of Maya and Nuke). First I looked to piggyback on the OS native ways of copying a file, but oddly enough it turned out there was no way of showing a progress bar when doing this.

So I turned to Python’s shutil and PySide (but PyQt would work just as well for this). Shutil cannot show progress by itself, but when asking around about this, I learned that you can implement your own copy function in Python using its built-in copyfileobj.

Below is a simple python script I came up with, to illustrate how to implement your own file copy with progress in Python.

# -*- coding: utf-8 -*-
#
#
#
#      ___      ___                                                                                                        
#    /'___\ __ /\_ \                                                                                                       
#   /\ \__//\_\\//\ \      __    ___    ___   _____   __  __      _____   _ __   ___      __   _ __    __    ____    ____  
#   \ \ ,__\/\ \ \ \ \   /'__`\ /'___\ / __`\/\ '__`\/\ \/\ \    /\ '__`\/\`'__\/ __`\  /'_ `\/\`'__\/'__`\ /',__\  /',__\
#    \ \ \_/\ \ \ \_\ \_/\  __//\ \__//\ \L\ \ \ \L\ \ \ \_\ \   \ \ \L\ \ \ \//\ \L\ \/\ \L\ \ \ \//\  __//\__, `\/\__, `\
#     \ \_\  \ \_\/\____\ \____\ \____\ \____/\ \ ,__/\/`____ \   \ \ ,__/\ \_\\ \____/\ \____ \ \_\\ \____\/\____/\/\____/
#      \/_/   \/_/\/____/\/____/\/____/\/___/  \ \ \/  `/___/> \   \ \ \/  \/_/ \/___/  \/___L\ \/_/ \/____/\/___/  \/___/
#                                               \ \_\     /\___/    \ \_\                 /\____/                          
#                                                \/_/     \/__/      \/_/                 \_/__/                           
#
#
# by Fredrik Averpil, http://fredrik.averpil.com, fredrik.averpil [at] gmail.com


import sys, os, datetime
from PySide import QtGui, QtCore
from optparse import OptionParser



class FileCopyProgress( QtGui.QWidget ):
    '''Custom shutil file copy with progress'''
    def __init__(self, parent=None, src=None, dest=None):
        super(FileCopyProgress, self).__init__()

        self.src = src
        self.dest = dest
        self.build_ui()



    def build_ui(self):

        hbox = QtGui.QVBoxLayout()

        lbl_src = QtGui.QLabel('Source: ' + self.src)
        lbl_dest = QtGui.QLabel('Destination: ' + self.dest)
        self.pb = QtGui.QProgressBar()

        self.pb.setMinimum(0)
        self.pb.setMaximum(100)
        self.pb.setValue(0)


        hbox.addWidget(lbl_src)
        hbox.addWidget(lbl_dest)
        hbox.addWidget(self.pb)
        self.setLayout(hbox)

        self.setWindowTitle('File copy')

        self.auto_start_timer = QtCore.QTimer()
        self.auto_start_timer.timeout.connect( lambda : self.copyfileobj( src=self.src, dst=self.dest, callback_progress=self.progress, callback_copydone=self.copydone )  )
        self.auto_start_timer.start(2000)

        self.show()


    def progress(self, fsrc, fdst, copied):
        size_src = os.stat( fsrc.name ).st_size
        size_dst = os.stat( fdst.name ).st_size

        float_src = float( size_src )
        float_dst = float( size_dst )

        percentage = int(float_dst/float_src*100)

        try:
            self.pb.setValue( percentage )
        except:
            pass

        app.processEvents()


    def copydone(self, fsrc, fdst, copied):
        self.pb.setValue( 100 )
        self.close()


    def copyfileobj(self, src, dst, callback_progress, callback_copydone, length=8*1024):

        # Prevent progress callback from being made each cycle
        c = 0
        c_max = 50

        try:
            self.auto_start_timer.stop()
        except:
            print 'Error: could not stop QTimer'


        with open(src, 'r') as fsrc:
            with open(dst, 'w') as fdst:
                copied = 0
                while True:
                    buf = fsrc.read(length)
                    if not buf:
                        break
                    fdst.write(buf)
                    copied += len(buf)
                    c += 1
                    if c == c_max:
                        callback_progress(fsrc=fsrc, fdst=fdst, copied=copied)
                        c = 0
                callback_copydone(fsrc=fsrc, fdst=fdst, copied=copied)


if __name__ == '__main__':


    parser = OptionParser()
    parser.add_option("-s", "--src", dest="src",
                      help="source FILE", metavar="FILE")
    parser.add_option("-d", "--dest", dest="dest",
                        help="destination FILE", metavar="FILE")
    (options, args) = parser.parse_args()

    if os.path.isfile( options.src ):
        app = QtGui.QApplication(sys.argv)
        ex = FileCopyProgress(src=options.src, dest=options.dest)
        sys.exit(app.exec_())

You can run the file like this:

python filecopyprogress.py --src=c:\hello1.png --dest=c:\hello2.png

Please, go ahead and do whatever you wish with this code. And let me know if you improve it, such as adding support for folders!

Comments