I am a Python learner.

我写了一段代码,可以很好地在晚上阅读PDF. 导入库的代码如下(我将把完整代码放在最后):

import tkinter as tk
from tkinterdnd2 import DND_FILES, TkinterDnD
from tkinter import filedialog, messagebox, simpledialog
from PIL import Image, ImageOps, ImageTk
from pdf2image import convert_from_path
import numpy as np
import sys
import os
import threading

我的安装包在PyCharm中显示如下:

The package in Pycharm

同时,在处理PDF时,这段代码需要使用Poppler(名为bin的文件夹)(我将其放在相同目录下的.py文件中)

My folder information

在我的代码中,我通过以下方式定位文件夹bin:

The locating way

在我try 了以下几种方法后:

Pyinstaller -F -w -p Lib/site-packages --add-data "bin;bin" PDF_Night_reading.py

The process I run

The exe I got to run in dist resulted in the following:
enter image description here

我的完整代码如下:

import tkinter as tk
from tkinterdnd2 import DND_FILES, TkinterDnD
from tkinter import filedialog, messagebox, simpledialog
from PIL import Image, ImageOps, ImageTk
from pdf2image import convert_from_path
import numpy as np
import sys
import os
import threading


class PDFNightModeConverter:
    def __init__( self, root ):
       self.root = root
       self.root.title( 'PDF夜间阅读模式转化' )
       self.output_pdf_path = ""
       self.export_pdf_path = ""  # 用于存储导出路径
       self.poppler_path = "./bin"
       self.chosen_gray_value = 49  # 默认灰度值
       self.processing = False  # 文件是否正在处理
       self.preview_image = None  # 用于在Canvas上显示的PIL图像
       self.setup_ui( )
    
    
    
    def process_page( self, img, output_images, index, lock ):
       img_gray = img.convert( "L" )
       img_inverted = ImageOps.invert( img_gray )
       data = np.array( img_inverted )
       
       low_gray_mask = data < self.chosen_gray_value  #低于 Select 值的像素变为 Select 值
       data[low_gray_mask] = self.chosen_gray_value
       
       high_gray_mask = data >= 200  #高于 Select 值的像素变为固定值
       data[high_gray_mask] = 240
       
       img_modified = Image.fromarray( data )
       with lock:
          output_images[index] = img_modified
    
    def setup_ui( self ):
       text_color = '#000000'  # 黑色字体
       bg_color = '#FFFFFF'  # 白色背景
       self.lbl_color_prompt = tk.Label( self.root, text = "请 Select 转换后的背景亮度", bg = bg_color, fg = text_color )
       self.lbl_color_prompt.pack( pady = (20, 0) )
       
       self.color_frame = tk.Frame( self.root, bg = bg_color )
       self.color_frame.pack( pady = 10 )
       
       btn_color1 = tk.Button( self.color_frame, text = '  ', bg = '#20201f',\
                               width = 5, command = lambda:self.choose_color( 31 ) )
       btn_color1.pack( side = tk.LEFT, padx = 10 )
       
       btn_color2 = tk.Button( self.color_frame, text = '  ', bg = '#313233',\
                               width = 5, command = lambda:self.choose_color( 49 ) )
       btn_color2.pack( side = tk.LEFT, padx = 10 )
       
       lbl_color_choice = tk.Label( self.root, text = "(左偏暗,右偏亮,不选默认为右)", bg = bg_color, fg = text_color )
       lbl_color_choice.pack( pady = (5, 20) )
       
       # 文件 Select 框
       file_frame = tk.Frame( self.root, bg = bg_color )
       file_frame.pack( pady = 10 )
       
       self.entry_filename = tk.Entry( file_frame, width = 50, bg = bg_color, fg = text_color )
       self.entry_filename.pack( side = tk.LEFT, padx = (0, 10) )
       self.entry_filename.insert( 0, " Select 文件路径" )
       
       btn_select = tk.Button( file_frame, text = " Select ", command = self.select_file, bg = bg_color, fg = text_color )
       btn_select.pack( side = tk.LEFT )
       
       # 导出路径 Select 框
       export_file_frame = tk.Frame( self.root, bg = bg_color )
       export_file_frame.pack( pady = 10 )
       
       self.entry_export_filename = tk.Entry( export_file_frame, width = 50, bg = bg_color, fg = text_color )
       self.entry_export_filename.pack( side = tk.LEFT, padx = (0, 10) )
       self.entry_export_filename.insert( 0, "导出文件路径(不选则与原文件处于同一目录)" )
       
       btn_export_select = tk.Button( export_file_frame, text = " Select ",\
                                      command = self.select_export_path, bg = bg_color, fg = text_color )
       btn_export_select.pack( side = tk.LEFT )
       
       # 添加一个标签用于显示文字"预览"
       self.lbl_preview = tk.Label( self.root, text = "预览", bg = 'white' )
       self.lbl_preview.pack( pady = (10, 0) )
       
       # 修改 Select 页数按钮位置
       self.btn_choose_page = tk.Button( self.root, text = " Select 页数", command = self.choose_page )
       self.btn_choose_page.pack( )
       
       # "开始处理"按钮
       self.btn_start = tk.Button( self.root, text = "开始处理", command = self.start_processing, bg = bg_color,
                                   fg = text_color )
       self.btn_start.pack( side = tk.LEFT, padx = (0, 10) )
       
       # 状态标签
       self.lbl_status = tk.Label( self.root, text = "", bg = bg_color, fg = text_color )
       self.lbl_status.pack( ipadx = 10, ipady = 10, fill = 'x' )
       self.btn_open_folder = tk.Button( self.root, text = "打开文件所在目录",\
                                         command = self.open_output_directory )
       # 注意:这个按钮暂时还没有放入布局中
    
    def update_preview( self, page_num ):
       try:
          # 载入PDF的指定页码
          pdf_path = self.entry_filename.get( )
          pdf_page = convert_from_path( pdf_path, first_page = page_num, last_page = page_num,
                                        poppler_path = self.poppler_path )[0]
          
          # 处理页面
          img_gray = pdf_page.convert( "L" )
          img_inverted = ImageOps.invert( img_gray )
          data = np.array( img_inverted )
          
          low_gray_mask = data < self.chosen_gray_value  #低于 Select 值的像素变为 Select 值
          data[low_gray_mask] = self.chosen_gray_value
          
          high_gray_mask = data >= 200  #高于 Select 值的像素变为固定值
          data[high_gray_mask] = 240
          
          img_modified = Image.fromarray( data )
          
          # 将图像调整到Canvas的大小
          self.preview_image_full_size = img_modified  # 保存完整尺寸的图像副本
          img_modified.thumbnail( (self.preview_canvas.winfo_width( ), self.preview_canvas.winfo_height( )),
                                  Image.Resampling.LANCZOS )
          
          # 将处理后的图像转换为Tkinter兼容的图片
          self.preview_image = ImageTk.PhotoImage( image = img_modified )
          
          # 在Canvas上展示图片,并绑定点击事件
          self.preview_canvas.create_image(
             0,
             0,
             anchor = 'nw',
             image = self.preview_image
          )
          self.preview_canvas.config( scrollregion = self.preview_canvas.bbox( 'all' ) )
          self.preview_canvas.bind( "<Button-1>", self.popup_image )  # 点击图像时触发popup_image
       except Exception as e:
          messagebox.showerror( "错误", str( e ) )
    
    def choose_color( self, gray_value ):
       self.chosen_gray_value = gray_value
       self.lbl_status.config( text = f"选定亮度: {self.chosen_gray_value}" )
       self.root.configure( bg = self.gray_to_hex( gray_value ) )  # 改变窗口背景 colored颜色 PDF_Night_reading
    
    def gray_to_hex( self, gray_value ):
       hex_value = format( gray_value, 'x' )
       return f'#{hex_value}{hex_value}{hex_value}'
    
    def select_file( self ):
       file_path = filedialog.askopenfilename( )
       if file_path:
          self.entry_filename.delete( 0, tk.END )
          self.entry_filename.insert( 0, file_path )
          self.lbl_status.config( text = "" )
    
    def choose_page( self ):
       page_num = simpledialog.askinteger( " Select 页数", "请输入想要预览的页数:", parent = self.root )
       if page_num is not None:
          if 1 <= page_num:  # 假设没有最大页数限制
             self.selected_page = page_num
             self.show_page_in_window( page_num )  # 在新窗口中显示所选页面
    
    def show_page_in_window( self, page_num ):
       try:
          pdf_path = self.entry_filename.get( )
          pdf_page = convert_from_path( pdf_path, first_page = page_num, last_page = page_num,
                                        poppler_path = self.poppler_path )[0]
          
          img_gray = pdf_page.convert( "L" )
          img_inverted = ImageOps.invert( img_gray )
          data = np.array( img_inverted )
          
          low_gray_mask = data < self.chosen_gray_value
          data[low_gray_mask] = self.chosen_gray_value
          
          high_gray_mask = data >= 200
          data[high_gray_mask] = 240
          
          img_modified = Image.fromarray( data )
          
          # 创建一个新窗口
          top = tk.Toplevel( )
          top.title( "Page Preview" )
          
          # 获取原始图像的尺寸
          width, height = img_modified.size
          
          # 在新窗口中添加滚动条
          scrollbar_vert = tk.Scrollbar( top, orient = "vertical" )
          scrollbar_hor = tk.Scrollbar( top, orient = "horizontal" )
          
          # 添加一个Canvas来显示图像,并配置滚动条
          canvas = tk.Canvas( top, width = width, height = height, yscrollcommand = scrollbar_vert.set,
                              xscrollcommand = scrollbar_hor.set )
          scrollbar_vert.config( command = canvas.yview )
          scrollbar_hor.config( command = canvas.xview )
          
          # 打包Canvas和滚动条到窗口中
          canvas.pack( side = tk.LEFT, expand = True, fill = tk.BOTH )
          scrollbar_vert.pack( side = tk.RIGHT, fill = tk.Y )
          scrollbar_hor.pack( side = tk.BOTTOM, fill = tk.X )
          
          img_tk = ImageTk.PhotoImage( image = img_modified )
          canvas.create_image( 0, 0, anchor = "nw", image = img_tk )
          canvas.config( scrollregion = canvas.bbox( "all" ) )
          
          # 保存 img_tk 到类的属性,以防止它被垃圾回收
          self.img_tk = img_tk
          
          # 函数:用于Windows和MacOS鼠标滚轮事件处理
          def on_mousewheel( event ):
             canvas.yview_scroll( int( -1 * (event.delta / 120) ), "units" )
          
          # 函数:用于Linux鼠标滚轮向上滚动事件处理
          def on_linux_scroll_up( event ):
             canvas.yview_scroll( -1, "units" )
          
          # 函数:用于Linux鼠标滚轮向下滚动事件处理
          def on_linux_scroll_down( event ):
             canvas.yview_scroll( 1, "units" )
          
          # 绑定鼠标滚轮事件
          top.bind( "<MouseWheel>", on_mousewheel )  # 对于Windows和MacOS
          top.bind( "<Button-4>", on_linux_scroll_up )  # 对于Linux向上滚动
          top.bind( "<Button-5>", on_linux_scroll_down )  # 对于Linux向下滚动
       
       except Exception as e:
          messagebox.showerror( "错误", str( e ) )
    
    def select_export_path( self ):
       export_path = filedialog.asksaveasfilename( defaultextension = ".pdf" )
       if export_path:
          self.entry_export_filename.delete( 0, tk.END )
          self.entry_export_filename.insert( 0, export_path )
          self.export_pdf_path = export_path
    
    def open_output_directory( self ):
       if os.path.exists( self.output_pdf_path ):
          os.startfile( os.path.dirname( self.output_pdf_path ) )
       else:
          messagebox.showinfo( "信息", "文件不存在." )
    
    def start_processing( self ):
       file_path = self.entry_filename.get( )
       if file_path and not self.processing:
          self.lbl_status.config( text = "正在处理..." )
          self.processing = True
          self.btn_start.config( state = "disabled" )  # 禁用按钮
          threading.Thread( target = self.process_pdf_multithreaded, args = (file_path,), daemon = True ).start( )
          self.disable_buttons( )  # 禁用所有按钮
    
    def preview_page( self, page_num ):
       # 判断是否有可用的PDF页面图像
       if not hasattr( self, 'pdf_images' ) or not self.pdf_images:
          messagebox.showinfo( "错误", "没有可预览的文件." )
          return
       if page_num - 1 < len( self.pdf_images ):
          img = self.pdf_images[page_num - 1]  # 获取正确的页面图像
          img_processed = self.process_page_for_preview( img )  # 处理图像以预览
          self.display_image_on_canvas( img_processed )  # 在 Canvas 上显示图像
    
    def process_page_for_preview( self, img ):
       img_gray = img.convert( "L" )
       img_inverted = ImageOps.invert( img_gray )
       data = np.array( img_inverted )
       
       low_gray_mask = data < self.chosen_gray_value
       data[low_gray_mask] = self.chosen_gray_value
       
       high_gray_mask = data >= 200
       data[high_gray_mask] = 240
       
       img_modified = Image.fromarray( data )
       img_tk = ImageTk.PhotoImage( image = img_modified )
       return img_tk
    
    def display_image_on_canvas( self, img_tk ):
       self.preview_canvas.delete( "all" )  # 清除Canvas上的内容
       self.preview_canvas.create_image( 0, 0, anchor = "nw", image = img_tk )
       self.preview_canvas.image = img_tk  # 避免图像被垃圾收集器回收
    
    def process_pdf_multithreaded( self, pdf_path ):
       try:
          images = convert_from_path( pdf_path, poppler_path = self.poppler_path )
          output_images = [None] * len( images )
          threads = []
          lock = threading.Lock( )
          
          for index, img in enumerate( images ):
             thread = threading.Thread( target = self.process_page, args = (img, output_images, index, lock) )
             threads.append( thread )
             thread.start( )
          
          for thread in threads:
             thread.join( )
          
          if self.export_pdf_path:
             self.output_pdf_path = self.export_pdf_path
          else:
             self.output_pdf_path = os.path.splitext( pdf_path )[0] + "_DarkRead.pdf"
          
          if output_images:
             output_images[0].save( self.output_pdf_path, "PDF", resolution = 100.0,\
                                    save_all = True, append_images = output_images[1:] )
          
          self.root.after( 0, self.update_ui_post_processing )
       except Exception as e:
          self.root.after( 0, messagebox.showerror, "错误", str( e ) )
          self.root.after( 0, self.lbl_status.config, { "text":"文件处理失败" } )
          self.root.after( 0, self.reset_processing_state )
    
    def update_ui_post_processing( self ):
       self.lbl_status.config( text = "文件处理已完成" )
       self.btn_open_folder.pack( pady = (5, 20) )
       self.reset_processing_state( )
    
    def reset_processing_state( self ):
       self.processing = False
       self.enable_buttons( )  # 启用所有按钮
    
    def disable_buttons( self ):
       self.btn_start.config( state = "disabled" )
       self.btn_choose_page.config( state = "disabled" )
       # 禁用其他所有按钮
    
    def enable_buttons( self ):
       self.btn_start.config( state = "normal" )
       self.btn_choose_page.config( state = "normal" )
       # 启用其他所有按钮


# 创建主窗口
root = TkinterDnD.Tk( )
app = PDFNightModeConverter( root )
root.mainloop( )

if __name__ == "__main__":
    main( )

100

我可以做些什么来使EXE正常工作?

推荐答案

在该链接https://stackoverflow.com/questions/69348567/pyinstaller-tkdnd-tkinterdnd2-unable-to-load-tkdnd-library-when-launching-fr/处描述了类似的问题.

解决方案是在启动PyInstaller时添加--collect-all TkinterDnD2标志.在您的情况下,完整的命令将是

pyinstaller -F -w -p Lib/site-packages --add-data "bin;bin" --collect-all TkinterDnD2 PDF_Night_reading.py 

我从你的截图中复制了其余的旗帜.

Python相关问答推荐

如果条件不满足,我如何获得掩码的第一个索引并获得None?

在嵌套span下的span中擦除信息

如何在Python中使用另一个数据框更改列值(列表)

Matplotlib中的字体权重

计算空值

Odoo16:模板中使用的docs变量在哪里定义?

从嵌套极轴列的列表中删除元素

如何在SQLAlchemy + Alembic中定义一个"Index()",在基表中的列上

如果服务器设置为不侦听创建,则QWebSocket客户端不连接到QWebSocketServer;如果服务器稍后开始侦听,则不连接

在任何要保留的字段中添加引号的文件,就像在Pandas 中一样

#将多条一维曲线计算成其二维数组(图像)表示

pyspark where子句可以在不存在的列上工作

为什么内置的sorted()对于一个包含降序数字的列表来说,如果每个数字连续出现两次,会变慢?

S最大值除以最小值,然后减1的结果是什么?

意外的麻木图像reshape 为网格问题

将COLUMN BY GROUP中的值连接为列表,并将其赋值给PANAS数据框中的变量

如何在上一次迭代中跳过某些内容(&q;)?

有没有更python的方法来复制python中列表的第n个元素?例如,使用列表理解

每像素级图像处理的毕达式优化

将CONTEXT_PROCESSOR数据返回到html进行循环时出现问题