Python练手项目:PDF小工具-fitz版

Python练手项目:PDF小工具-fitz版
python对办公一族来说,是个提高办公效率的得力利器。熟练使用它,可以轻松完成很多繁杂的事情。它可以轻松地操作Word、Excel、PDF这种文档。
今天需要整理一份资料,需要把多个pdf合并为一个,wps这些软件自然是有这个功能,但一般都是收费的,百度上也有很多网站,但资料上传到别人的网站,始终觉得还是不太可靠,故自己搜索了一下使用python来处理pdf文件,故此用tk制作了PDF小工具。
安装以下库:
python -m pip install -U pip
pip install PyMuPDF==1.19.0 -i https://pypi.mirrors.ustc.edu.cn/simple/
pdf_tool_fitz.py
源码如下:

#pip install PyMuPDF==1.19.0 -i https://pypi.mirrors.ustc.edu.cn/simple/
from pathlib import Path
import subprocess
import time
import sys
import tkinter as tk
from tkinter import ttk
import tkinter.filedialog as fd
import tkinter.messagebox as mb
import base64
import fitz
import threading
class PdfHandler:
    page_count=None
    page_index=None
    img_path=None
    def pdf_to_imgs(self,pdf_path,img_path,zoom_x,zoom_y,rotation_angle):
        '''
        pdf文件每页内容都转换成图片
        :param pdf_path:pdf文件的路径
        :param img_path:图像要保存的路径
        :param zoom_x:x方向的缩放系数
        :param zoom_y:y方向的缩放系数
            zoom_x和zoom_y一般取相同值,值越大,图像分辨率越高
        :param rotation_angle:旋转角度
        :return:
        '''
        # 如果没有存储文件的目录,则创建
        if not Path(img_path).exists():
            Path(img_path).mkdir()
            
        # 打开PDF文件
        with fitz.open(pdf_path) as pdf:
            self.page_count=pdf.page_count
            # 逐页读取PDF
            for page_index in range(0, pdf.pageCount):
                self.page_index=page_index
                page = pdf[page_index]
                # 设置缩放和旋转系数,zoom_x, zoom_y取相同值,表示等比例缩放
                trans = fitz.Matrix(zoom_x, zoom_y).prerotate(rotation_angle)
                pm = page.getPixmap(matrix=trans, alpha=False)
                self.img_path=f'{img_path}/{page_index}.png'
                self.img_path=self.img_path.replace('/',"\\")
                # 开始写图像
                pm.writePNG(self.img_path) # 第1张图片名:0.png,以此类推
                pm=None

    def imgs_to_pdf(self,img_path, new_pdf_name):
        '''
        将图片转为pdf
        :param imgPath: 图片路径
        :param new_pdf_name: 多张图片合并成的pdf文件名
        :return:
        '''
        with fitz.open() as doc:  # 新建一个空文档
            img_list=[]
            for item in Path(img_path).iterdir():
                img_list.append(item.name)
            for page_index in range(len(img_list)):
                self.page_index=self.page_index+1
                img = fitz.open(f'{img_path}/{page_index}.png')  # 打开图片
                pdfbytes = img.convertToPDF()  # 使用图片创建单页的 PDF
                with fitz.open("pdf", pdfbytes) as imgpdf:
                    doc.insert_pdf(imgpdf)
                img.close()
            doc.save(new_pdf_name)
    
    def imgs_2_pdf(self,old_pdf_names, new_pdf_name):
        '''
        将图片转为pdf
        :param imgPath: 图片路径
        :param new_pdf_name: 多张图片合并成的pdf文件名
        :return:
        '''
        with fitz.open() as doc:  # 新建一个空文档
            for old_pdf_name in old_pdf_names:
                img = fitz.open(old_pdf_name)  # 打开图片
                pdfbytes = img.convertToPDF()  # 使用图片创建单页的 PDF
                with fitz.open("pdf", pdfbytes) as imgpdf:
                    doc.insert_pdf(imgpdf)
                img.close()
            doc.save(new_pdf_name)
    
    def img_invert_img(self,old_img_name, new_img_name):
        '''
        将图片转为pdf
        :param imgPath: 图片路径
        :param new_pdf_name: 多张图片合并成的pdf文件名
        :return:
        '''
        with fitz.open() as doc:  # 新建一个空文档
            img = fitz.open(old_img_name)  # 打开图片
            pdfbytes = img.convertToPDF()  # 使用图片创建单页的 PDF
            with fitz.open("pdf", pdfbytes) as imgpdf:
                doc.insert_pdf(imgpdf)
            img.close()
            img = doc.get_page_images(0)[0]
            xref = img[0] #图片的xref编号,
            base = img[1] #图片的基本名字,比如 'img0'
            pix = fitz.Pixmap(doc, xref)
            if pix.n < 5:  # 这是一个灰度或者RGB图片
                pix.invertIRect()
                pix.writePNG(new_img_name)
            else:  # 这是一个CMYK图片
                pix1 = fitz.Pixmap(fitz.csRGB, pix)
                pix1.invertIRect()
                pix1.writePNG(new_img_name)
                pix1 = None
            pix = None
            '''
            pm=doc[0].getPixmap(dpi=600,alpha=False)
            pm.invertIRect()
            pm.writePNG(new_img_name)
            pm=None
            '''
    
    def merge_pdfs(self,old_pdf_names,new_pdf_name):
        '''
        从pdf文件合成为新的pdf文件
        :param old_pdf_names: pdf文件名数组
        :param new_pdf_name: 合并出的新pdf文件名
        :return:
        '''
        print(old_pdf_names)
        print(new_pdf_name)
        file_writer = fitz.open()
        for pdf in old_pdf_names:
            file_reader = fitz.open(pdf)# 循环读取需要合并pdf文件
            file_writer.insert_pdf(file_reader)
            file_reader.close()
        file_writer.save(new_pdf_name)
        file_writer.close()
   
    def split_pdf(self,old_pdf_name, new_pdf_name, from_page, to_page):
        '''
        从pdf文件中拆分出部分,拆出部分成为新的pdf文件
        :param old_pdf_name: 原pdf文件名
        :param new_pdf_name: 拆分后合并出的新pdf文件名
        :param from_page: 原pdf文件起始页码索引(0开始,默认0)
        :param to_page: 原pdf文件结尾页码索引(0开始,默认最后一页)
        :return:
        '''
        with fitz.open(old_pdf_name) as old_pdf, fitz.open() as new_pdf:
            new_pdf.insert_pdf(old_pdf, from_page, to_page)
            new_pdf.save(new_pdf_name)

    def get_pdf_text(self,pdf_path):
        '''
        获取每页pdf文件文本
        :param pdf_path: pdf文件路径
        :return:
        '''
        # 打开pdf文件,并新建html文件
        with fitz.open(pdf_path) as pdf:

            # 遍历每一页pdf,并显示进度条
            for page in pdf:
                text = page.get_text() #提取文本,传入参数'html'即:page.get_text('html') 则提取每页内容为html
            print(f'第{page+1}页解析内容:\n{text}')
    
    def get_pdf_images(self,img_path,pdf_path):
        '''
        获取每页pdf文件所有图片
        :param pdf_path: pdf文件路径
        :return:
        '''
        # 如果没有存储文件的目录,则创建
        if not Path(img_path).exists():
            Path(img_path).mkdir()
        doc = fitz.open(pdf_path)
        self.page_count=doc.page_count
        page_index=0
        for i in range(len(doc)):
            self.page_index=i
            for img in doc.get_page_images(i):
                xref = img[0] #图片的xref编号,
                #base = img[1] #图片的基本名字,比如 'img0'
                pix = fitz.Pixmap(doc, xref)
                self.img_path=f"{img_path}/{page_index}.png"
                if pix.n < 5:  # 这是一个灰度或者RGB图片
                    pix.writePNG(self.img_path)
                else:  # 这是一个CMYK图片
                    pix1 = fitz.Pixmap(fitz.csRGB, pix)
                    pix1.writePNG(self.img_path)
                    pix1 = None
                pix = None  # 让图片像素图清除内存
                page_index+=1
        doc.close()

# 防止字符串乱码
# os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8'
data=[]
id=0
mydir='D:/'
kind='PDF'
PdfHandler=PdfHandler()

def thread_it(func,args):
	'''将函数打包进线程'''
	# 创建
	t = threading.Thread(target=func, args=args)
	# 守护 !!!
	t.setDaemon(True)
	# 启动
	t.start()
	# 阻塞--卡死界面!
	# t.join()	

def base64_to_image(base64_string, image_path):
    base64_data = base64_string.encode("utf-8")
    image_data = base64.b64decode(base64_data)
    with open(image_path, "wb") as image_file:
        image_file.write(image_data)

base64_string = '''
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
'''
image_path = "pdf_tool_ico.ico"  # 解码后的图像文件保存路径
if Path(image_path).exists():
    #获得文件创建时间
    creation_time = time.gmtime(Path(image_path).stat().st_mtime+28800)
    creation_time_str=time.strftime("%Y-%m-%d %H:%M:%S",creation_time)
    #print(creation_time_str)
    #获得当前时间
    current_time = time.localtime()
    current_time_str=time.strftime("%Y-%m-%d %H:%M:%S",current_time)
    #print(current_time_str)
    # 比较年、月、日是否相同
    is_same_day = creation_time_str[:10]== current_time_str[:10]
    # 输出结果
    if is_same_day:
        #print("时间处于同一天")
        sys.exit()
    else:
        #print("时间不处于同一天")
        Path(image_path).unlink()
    
base64_to_image(base64_string, image_path)


def invert_color():
    try:
        ssid=tv.selection()[0]
    except:
        mb.showinfo('警告!', '请从PDF合并表中选择一个文件!')
        return
    img_path=tv.item(ssid)['values'][2]
    fwr=en_path.get()+'/'+en_file.get()+'.png'
    if Path(fwr).exists():
        fwr=show_dialog(fwr)
        if fwr==None:
            return
        else:
            PdfHandler.img_invert_img(img_path,fwr)
    else:
        PdfHandler.img_invert_img(img_path,fwr)
    print('图片反色成功')
    fwr=fwr.replace('/',"\\")
    if(mb.askyesno(title='PDF合成成功',message='是否打开文件:%s'%fwr)):
        subprocess.run(f'start "" "{fwr}"',shell=True,close_fds=True)
        
def pdf_compose(fres,fwr):
    PdfHandler.merge_pdfs(fres,fwr)
        
def img_compose(fres,fwr):
    PdfHandler.imgs_2_pdf(fres,fwr)
    
def pdf_dismantle(fre,fwr,spage,epage):
    PdfHandler.split_pdf(fre,fwr,from_page=spage, to_page=epage-spage-1)

def center_window(root,w, h):
    ws = root.winfo_screenwidth()
    hs = root.winfo_screenheight()
    x = (ws/2) - (w/2)   
    y = (hs/2) - (h/2)
    root.geometry('%dx%d+%d+%d' % (w, h, x, y))

def delete():
    try:
        ssid=tv.selection()[0]
    except:
        return
    sid=int(ssid)
    tv.delete(sid)
    del data[:]
    sim=tv.get_children()
    #print(sim)
    for i in sim:
        ins=tv.item(i)['values']
        data.append(ins)
        tv.delete(i)
    #print(data)
    global id
    id=0
    for ins in data:
        id+=1
        ins[0]=id
        tv.insert('','end',iid=id,values=ins)   
        
def insert():
    global mydir,kind,id
    if kind=='PDF':
        pdf_paths = fd.askopenfilenames(title='选择文件',initialdir = mydir,filetypes=[("PDF file", "*.pdf")])
        for pdf_path in pdf_paths:
            print(pdf_path)
            if(pdf_path==''):
                return
            id+=1
            pdf_file=pdf_path.split ('/')[-1].split ('.pdf')[0]
            pdf = fitz.open(pdf_path)
            pdf_pages=pdf.page_count
            pdf.close()
            pdf=None
            mydir=pdf_path.rsplit('/',1)[0]+'/'
            ins=(id,pdf_file,pdf_path,pdf_pages)
            tv.insert('','end',iid=id,values=ins)
    else:
        img_paths = fd.askopenfilenames(title='选择文件',initialdir =mydir,filetypes=[("JPEG file", "*.jpeg;*.jpg"),("PNG file", "*.png"),("GIF file","*.gif"),("BMP file","*.bmp")])
        for img_path in img_paths:
            print(img_path)
            if(img_path==''):
                return
            id+=1
            img_file=img_path.split ('/')[-1].split ('.pdf')[0]
            img_pages='1'
            mydir=img_path.rsplit('/',1)[0]+'/'
            ins=(id,img_file,img_path,img_pages)
            tv.insert('','end',iid=id,values=ins)

def correct():
    try:
        sid=tv.selection()[0]
    except:
        return
    global mydir
    if kind=='PDF':
        pdf_path = fd.askopenfilename(title='选择文件',initialdir = mydir,filetypes=[("PDF file", "*.pdf")])
        with fitz.open(pdf_path) as pdf:
            pdf_pages=pdf.page_count
    else:
        pdf_path = fd.askopenfilename(title='选择文件',initialdir =mydir,filetypes=[("JPEG file", "*.jpeg;*.jpg"),("PNG file", "*.png"),("GIF file","*.gif"),("BMP file","*.bmp")])
        pdf_pages='1'
    if(pdf_path==''):
        return
    pdf_file=pdf_path.split ('/')[-1].split ('.pdf')[0]
    
    mydir=pdf_path.rsplit('/',1)[0]+'/'
    tv.set(sid,column='file', value=pdf_file)
    tv.set(sid,column='path', value=pdf_path)
    tv.set(sid,column='pages', value=pdf_pages)

def compose():
    global kind
    if kind=='PDF':
        sim=tv.get_children()
        if(len(sim)<2):
            mb.showinfo('警告!', 'PDF合并表需要添加二个以上文件才能合并!')
            return
        del data[:]
        for i in sim:
            data.append(tv.item(i)['values'])
        fres=[]
        for i in data:
            fres.append(i[2])
        fwr=en_path.get()+'/'+en_file.get()+'.pdf'
        if fwr in fres:
            mb.showinfo('警告!', '输出文件名跟PDF合并表中的一个文件同名!')
            return
        if Path(fwr).exists():
            fwr=show_dialog(fwr)
            if fwr==None:
                return
            else:
                pdf_compose(fres,fwr)
        else:
            pdf_compose(fres,fwr)
        print('PDF合成成功')
        fwr=fwr.replace('/',"\\")
        if(mb.askyesno(title='PDF合成成功',message='是否打开文件:%s'%fwr)):
            subprocess.run(f'start "" "{fwr}"',shell=True,close_fds=True)
    else:
        sim=tv.get_children()
        if(len(sim)<1):
            mb.showinfo('警告!', 'PDF合并表需要添加一个以上文件才能合并!')
            return
        del data[:]
        for i in sim:
            data.append(tv.item(i)['values'])
        fres=[]
        for i in data:
            fres.append(i[2])
        fwr=en_path.get()+'/'+en_file.get()+'.pdf'
        if Path(fwr).exists():
            fwr=show_dialog(fwr)
            if fwr==None:
                return
            else:
                img_compose(fres,fwr)
        else:
            img_compose(fres,fwr)
        print('PDF合成成功')
        fwr=fwr.replace('/',"\\")
        if(mb.askyesno(title='PDF合成成功',message='是否打开文件:%s'%fwr)):
            subprocess.run(f'start "" "{fwr}"',shell=True,close_fds=True)
            
def openFloder():
    folder_path = fd.askdirectory(initialdir = 'D:/') # 打开文件
    if(folder_path==''):
        return
    folder_path = folder_path[:-1] if folder_path[-1]=='/' else folder_path
    en_path.config(state='normal')
    en_path.delete(0,'end')  # 清空
    en_path.insert(0,folder_path)  #写入路径
    en_path.config(state='disabled')

def dismantle():
    try:
        ssid=tv.selection()[0]
    except:
        mb.showinfo('警告!', '请从PDF合并表中选择一个文件!')
        return
    if(tv.item(ssid)['values'][3]<2):
        mb.showinfo('警告!', 'PDF只有一页!无需拆分!')
        return
    a=en_page.get()
    b='-'
    c=tv.item(ssid)['values'][3]
    #print(a.split(b)[0])
    #print(a.split(b)[1])
    if(a.count(b)>1 or a.count(b)<1 or a[0].count(b)==1 or a[-1].count(b)==1 or int(a.split(b)[0])>=int(a.split(b)[1]) or int(a.split(b)[1])>int(c)):
        mb.showinfo('警告!', '页码输入错误')        
        return
    fre=tv.item(ssid)['values'][2]
    spage=int(a.split(b)[0])
    epage=int(a.split(b)[1])
    fwr=en_path.get()+'/'+en_file.get()+'.pdf'
    if fwr == fre:
        mb.showinfo('警告!', '输出文件名跟PDF合并表中选择的文件同名!')
        return
    if Path(fwr).exists():
        fwr=show_dialog(fwr)
        if fwr==None:
            return
        else:
            pdf_dismantle(fre,fwr,spage,epage)
    else:
        pdf_dismantle(fre,fwr,spage,epage)
    print('PDF拆分成功')
    fwr=fwr.replace('/',"\\")
    if(mb.askyesno(title='PDF拆分成功',message='是否打开文件:%s'%fwr)):
        subprocess.run(f'start "" "{fwr}"',shell=True,close_fds=True)
        
def pdf_imagepdf():
    try:
        ssid=tv.selection()[0]
    except:
        mb.showinfo('警告!', '请从PDF合并表中选择一个文件!')
        return
    fre=tv.item(ssid)['values'][2]
    fwr=en_path.get()+'/'+en_file.get()+'.pdf'
    print(Path.cwd())
    img_path=f"{Path.cwd()}/img_path"
    if fwr == fre:
        mb.showinfo('警告!', '输出文件名跟PDF合并表中选择的文件同名!')
        return
    
    if Path(fwr).exists():
        fwr=show_dialog(fwr)
        if fwr==None:
            return
        else:
            thread_it(p_i_p,(fre,img_path,fwr))
            thread_it(progressbar_show,(fwr,2))
    else:
        thread_it(p_i_p,(fre,img_path,fwr))
        thread_it(progressbar_show,(fwr,2))
    
def p_i_p(fre,img_path,fwr):
    PdfHandler.pdf_to_imgs(fre,img_path,2,2,0)
    PdfHandler.imgs_to_pdf(img_path,fwr)
    print('转纯图PDF成功')
    time.sleep(0.5)
    fwr=fwr.replace('/',"\\")
    if(mb.askyesno(title='转纯图PDF成功',message='是否打开文件:%s'%fwr)):
        subprocess.run(f'start "" "{fwr}"',shell=True,close_fds=True)
    for path in Path(img_path).iterdir():
        Path(path).unlink()#删除文件
    Path(img_path).rmdir()#删除文件夹

def progressbar_show(fwr=None,num=1):
    scrolling.deiconify()
    scrolling.attributes("-topmost",True)#窗口置顶
    root.attributes('-disabled',True)#父窗口操作锁定
    while True:
        page_count=PdfHandler.page_count
        page_index=PdfHandler.page_index
        if(page_count!=None):
            progressbar['value'] = (page_index/(num*page_count))*100
            if(page_count<=page_index+1):
                scrolling_label['text'] = f'正在生成:{fwr}'
            else:
                scrolling_label['text'] = f'正在生成:{PdfHandler.img_path}'
            # 更新画面
            root.update()
            time.sleep(0.05)
            if(num*page_count==page_index+1):
                break
        else:
            time.sleep(0.1)
    print('滚动条结束')
    PdfHandler.page_count=None
    PdfHandler.page_index=None
    PdfHandler.img_path=None
    scrolling.withdraw()
    scrolling.attributes("-topmost",False)
    root.attributes('-disabled',False)
    progressbar['value'] =0

def run_pdf_imgs():
    try:
        ssid=tv.selection()[0]
    except:
        mb.showinfo('警告!', '请从PDF合并表中选择一个文件!')
        return
    # 创建线程并启动
    thread1 = threading.Thread(target=pdf_imgs)
    thread1.start()
    
    thread2 = threading.Thread(target=progressbar_show)
    thread2.start()
    
def run_get_pdf_imgs():
    try:
        ssid=tv.selection()[0]
    except:
        mb.showinfo('警告!', '请从PDF合并表中选择一个文件!')
        return
    # 创建线程并启动
    thread1 = threading.Thread(target=get_pdf_imgs)
    thread1.start()
    
    thread2 = threading.Thread(target=progressbar_show)
    thread2.start()

def pdf_imgs():
    try:
        ssid=tv.selection()[0]
    except:
        mb.showinfo('警告!', '请从PDF合并表中选择一个文件!')
        return
    pdf_path=tv.item(ssid)['values'][2]
    img_path=en_path.get()+'/'+en_file.get()
    PdfHandler.pdf_to_imgs(pdf_path,img_path,2,2,0)
    print('PDF转图片成功')
    time.sleep(0.5)
    img_path=img_path.replace('/',"\\")
    if(mb.askyesno(title='PDF转图片成功',message='是否打开文件夹:%s'%img_path)):
        subprocess.run(["explorer.exe",img_path])
    
def get_pdf_imgs():
    try:
        ssid=tv.selection()[0]
    except:
        mb.showinfo('警告!', '请从PDF合并表中选择一个文件!')
        return
    pdf_path=tv.item(ssid)['values'][2]
    img_path=en_path.get()+'/'+en_file.get()
    PdfHandler.get_pdf_images(img_path,pdf_path)
    print('PDF提取图片成功')
    time.sleep(0.5)
    img_path=img_path.replace('/',"\\")
    if(mb.askyesno(title='PDF提取图片成功',message='是否打开文件夹:%s'%img_path)):
        subprocess.run(["explorer.exe",img_path])
    
root = tk.Tk()
root.title('强联-PDF小工具                                         QQ群号:202013774')
root.iconbitmap(r'pdf_tool_ico.ico')
center_window(root,530,276)
root.resizable(width=False, height=False)

style = ttk.Style()
style.configure('TButton',foreground='black')
mystyle = ttk.Style()
mystyle.configure('my.TButton',foreground='red')

ttk.Label(root,text='PDF合并表').grid(column=0,row=0,columnspan=4)
area=('id','文件','路径','页数')
ac=('id','file','path','pages')
tv=ttk.Treeview(root,columns=ac,show='headings',height=5)
for i in range(4):
    tv.column(ac[i],width=35,anchor='e')
    tv.heading(ac[i],text=area[i])
tv.column(ac[1],width=140)
tv.column(ac[2],width=315)
tv.grid(column=0,row=1,columnspan=4)
ttk.Button(root,text='删除',command=delete).grid(column=0,row=2)
ttk.Button(root,text='添加',command=insert).grid(column=1,row=2)
ttk.Button(root,text='修改',command=correct).grid(column=2,row=2)
ttk.Button(root,text='PDF合并',command=compose).grid(column=3,row=2)

ttk.Button(root,text='输出目录',command=openFloder).grid(column=0,row=3)
en_path=ttk.Entry(root,text='默认输出目录',width=40)
en_path.grid(column=1,row=3,columnspan=2,sticky='W')
en_path.config(state='normal')
folder_path = str(Path.cwd())
print(folder_path)
folder_path = folder_path[:-1] if folder_path[-1]=='\\' else folder_path
en_path.insert(0,folder_path)
en_path.config(state='disabled')

en_page=ttk.Entry(root,text='默认输出页码',width=5,style='my.TButton',justify='center')
en_page.grid(column=3,row=3)
folder_page='0-1'
en_page.insert(0,folder_page)

ttk.Label(root,text='输出文件名').grid(column=0,row=4)
en_file=ttk.Entry(root,text='默认输出文件名',width=40)
en_file.grid(column=1,row=4,columnspan=2,sticky='W')
folder_file='合并'
en_file.insert(0,folder_file)

dis_button=ttk.Button(root,text='PDF拆分',style='my.TButton',command=dismantle)
dis_button.grid(column=3,row=4)

ttk.Label(root,text='-------小工具-------------------------------------------------------------------------------------------').grid(column=0,row=5,columnspan=4)
def on_selection_change(event):
    global id,kind
    kind=combo.get()
    print("选中的值:", kind)
    
    del data[:]
    sim=tv.get_children()
    #print(sim)
    for i in sim:
        ins=tv.item(i)['values']
        data.append(ins)
        tv.delete(i)
    id=0
    
    if kind=='PDF':
        en_page.config(state='normal')
        fi_button.grid_forget()
        gpi_button.grid(column=1,row=6)
        dis_button.config(state='normal')
        pi_button.config(state='normal')
        pip_button.config(state='normal')
    else:
        en_page.config(state='disabled')
        gpi_button.grid_forget()
        fi_button.grid(column=1,row=6)
        dis_button.config(state='disabled')
        pi_button.config(state='disabled')
        pip_button.config(state='disabled')
# 创建下拉单选框
combo = ttk.Combobox(root, width=12)
combo.grid(column=0,row=6)
combo["values"] = ("PDF", "图片转PDF")
combo.current(0)  # 设置默认选中第一个选项
combo.bind("<<ComboboxSelected>>", on_selection_change)
gpi_button=ttk.Button(root,text='PDF提取图片',command=run_get_pdf_imgs)
gpi_button.grid(column=1,row=6)
fi_button=ttk.Button(root,text='图片反色',command=invert_color)
fi_button.grid(column=1,row=6)
fi_button.grid_forget()
pi_button=ttk.Button(root,text='PDF转图片',command=run_pdf_imgs)
pi_button.grid(column=2,row=6)
pip_button=ttk.Button(root,text='转纯图PDF',command=pdf_imagepdf)
pip_button.grid(column=3,row=6)

def show_dialog(fwr):
    sfwr=fwr.rsplit('/',1)
    re_fwr = None
    dialog=tk.Toplevel(root)
    dialog.title('当前位置存在同名文件')
    dialog.iconbitmap(r'pdf_tool_ico.ico')
    dialog.transient(root)
    #dialog.resizable(width=False, height=False)
    #dialog.attributes("-toolwindow",True)
    center_window(dialog,350,100)
    
    def get_choose(num):
        nonlocal re_fwr
        if num==1:
            re_fwr=fwr
        elif num==2:
            re_fwr=sfwr[0]+'/'+sfwr[1][:-4]+'_副本_'+sfwr[1][-4:]
        else:
            re_fwr=None
        dialog.destroy()
    
    ttk.Label(dialog, text=f'是否替换“{sfwr[1]}”?',font=("微软雅黑", 15)).grid(row=0,column=0,columnspan=3,sticky=tk.W)
    ttk.Button(dialog,text='替换文件',command=lambda: get_choose(1)).grid(column=0,row=1,sticky=tk.E)
    ttk.Button(dialog,text='创建副本',command=lambda: get_choose(2)).grid(column=1,row=1)
    ttk.Button(dialog,text='取消',command=lambda: get_choose(None)).grid(column=2,row=1,sticky=tk.W)
    dialog.rowconfigure(0,weight=3)
    dialog.rowconfigure(1,weight=1)
    dialog.columnconfigure(0,weight=3)
    dialog.columnconfigure(1,weight=1)
    dialog.columnconfigure(2,weight=1)
    dialog.protocol("WM_DELETE_WINDOW",lambda: get_choose(None))
    
    root.wait_window(dialog)
    return re_fwr

scrolling=tk.Toplevel(root)
scrolling.overrideredirect(True)#去除标题栏
#scrolling.attributes("-topmost",True)
center_window(scrolling,350,50)

scrolling_label=ttk.Label(scrolling,font=("微软雅黑", 8),foreground='red')
scrolling_label.grid(row=0,column=0,sticky=tk.SW)
progressbar =ttk.Progressbar(scrolling,length=350)
progressbar.grid(row=1,column=0,sticky=tk.NW)
scrolling.rowconfigure(0,weight=1)
scrolling.rowconfigure(1,weight=1)
scrolling.withdraw()

def appQuit():
    Path(r'pdf_tool_ico.ico').unlink()
    root.destroy()
root.protocol("WM_DELETE_WINDOW", appQuit)

root.mainloop()

1.bat

@start  "CMD窗口闭关" "pythonw" "pdf_tool_fitz.py" %1 %2 %3 %4 %5 %6 %7 %8 %9

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值