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中显示如下:
同时,在处理PDF时,这段代码需要使用Poppler(名为bin的文件夹)(我将其放在相同目录下的.py文件中)
在我的代码中,我通过以下方式定位文件夹bin:
在我try 了以下几种方法后:
Pyinstaller -F -w -p Lib/site-packages --add-data "bin;bin" PDF_Night_reading.py
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正常工作?