Tkinter开发YouTube下载器填充Combobox时回调异常如何修复
Tkinter版YouTube下载器Combobox填充异常修复
问题现象
开发YouTube下载器时,输入视频链接后尝试把视频画质、音频音质信息填充到ttk.Combobox组件,程序抛出Exception in Tkinter callback异常。
原始问题代码如下:
import tkinter import tkinter.ttk from tkinter import * from tkinter import filedialog from os import path import pafy from pafy import * class Youtube(): def __init__(self): self.label() self.entry() self.button() self.combobox() self.data=[] def label(self): Label(window, text='URL', font='bold').grid(row=0, column=0, padx=100) Label(window, text='Destination', font='bold').grid(row=2, column=0, padx=100) Label(window, text='File Name', font='bold').grid(row=4, column=0, padx=100) Label(window, text='Video Quality', font='bold').grid(row=6, column=0, pady=20) Label(window, text='Audio Quality', font='bold').grid(row=7, column=0, pady=5) Label(window, text='Download Type', font='bold').grid(row=8, column=0, pady=15) Label(window, text='Combine AV', font='bold').grid(row=8, column=1) def entry(self): global een global en en=Entry(window, width=35, borderwidth=10) en.grid(row=1, column=0, padx=100, pady=5) een=Entry(window, width=35, borderwidth=10) een.grid(row=3, column=0, padx=100, pady=5) Entry(window, width=35, borderwidth=10).grid(row=5, column=0, padx=100, pady=5) def button(self): Button(window, text='i', height=1, width=3, command=self.information).grid(row=1, column=1) Button(window, text='d', height=1, width=3, command=self.destination).grid(row=3, column=1) Button(window, text='Download', font='bold').grid(row=12, column=0, padx=150, pady=10, columnspan=3) def combobox(self): tkinter.ttk.Combobox(window, width=15).grid(row=6, column=1) tkinter.ttk.Combobox(window, width=15).grid(row=7, column=1, pady=5) tkinter.ttk.Combobox(window, values=['Audio only', 'Vido only', 'Audio and vido'], width=15).grid(row=9, column=0) tkinter.ttk.Combobox(window, values=['Yes', 'No'], width=15).grid(row=9, column=1, pady=20) tkinter.ttk.Progressbar(window, orient=HORIZONTAL, length=300, mode='determinate').grid(row=11, column=0, padx=200, pady=10, columnspan=3) def destination(self): path = str(filedialog.askdirectory()) een.insert(tkinter.END, path) def information(self): global link link = en.get() self.video() self.audio() def video(self): video_quality_list = [] self.data = pafy.new(link) video_streams = self.data.videostreams for video_quality in video_streams: video_quality_list.append(video_quality) tkinter.ttk.Combobox(window, values=video_quality_list, width=15).grid(row=6, column=1, pady=5) def audio(self): audio_quality_list = [] self.data = pafy.new(link) audio_streams = self.data.audiostreams for audio_quality in audio_streams: audio_quality_list.append(audio_quality) tkinter.ttk.Combobox(window, values=audio_quality_list, width=15).grid(row=7, column=1) window = Tk() mywin = Youtube() window.title('Youtube Downloader') window.geometry("700x600+10+10") window.mainloop()
报错原因
- 核心问题:直接把pafy返回的
Stream类实例传入Combobox的values参数,ttk.Combobox仅支持字符串类型的选项值,渲染非字符串对象时会直接触发Tkinter回调异常。 - 逻辑问题:初始化界面时已经创建了对应位置的画质、音质选择下拉框,获取流信息后又重复创建同位置的Combobox,会引发组件布局冲突、引用丢失问题。
- 冗余请求:
video()和audio()方法各自调用一次pafy.new(link)拉取视频信息,重复发起网络请求,既拖慢速度也容易触发接口访问限制。 - 细节问题:存在全局变量滥用、选项文本拼写错误(
Vido应为Video)的问题。
修复方案
- 初始化时将画质、音质选择的Combobox存为类属性,后续更新选项时直接操作已有组件,不重复创建。
- 把pafy返回的流对象转换为用户可读的字符串(包含分辨率/码率、格式、编码信息)再传入Combobox。
- 仅调用一次
pafy.new()拉取视频信息,拿到结果后同时解析视频、音频流列表。 - 移除不必要的全局变量,修正选项文本拼写错误。
修复后可运行代码
import tkinter import tkinter.ttk from tkinter import * from tkinter import filedialog import pafy class Youtube(): def __init__(self, root): self.window = root self.data = None self.en = None self.een = None self.video_combobox = None self.audio_combobox = None self.label() self.entry() self.button() self.combobox() def label(self): Label(self.window, text='URL', font='bold').grid(row=0, column=0, padx=100) Label(self.window, text='Destination', font='bold').grid(row=2, column=0, padx=100) Label(self.window, text='File Name', font='bold').grid(row=4, column=0, padx=100) Label(self.window, text='Video Quality', font='bold').grid(row=6, column=0, pady=20) Label(self.window, text='Audio Quality', font='bold').grid(row=7, column=0, pady=5) Label(self.window, text='Download Type', font='bold').grid(row=8, column=0, pady=15) Label(self.window, text='Combine AV', font='bold').grid(row=8, column=1) def entry(self): self.en = Entry(self.window, width=35, borderwidth=10) self.en.grid(row=1, column=0, padx=100, pady=5) self.een = Entry(self.window, width=35, borderwidth=10) self.een.grid(row=3, column=0, padx=100, pady=5) Entry(self.window, width=35, borderwidth=10).grid(row=5, column=0, padx=100, pady=5) def button(self): Button(self.window, text='i', height=1, width=3, command=self.information).grid(row=1, column=1) Button(self.window, text='d', height=1, width=3, command=self.destination).grid(row=3, column=1) Button(self.window, text='Download', font='bold').grid(row=12, column=0, padx=150, pady=10, columnspan=3) def combobox(self): self.video_combobox = tkinter.ttk.Combobox(self.window, width=25) self.video_combobox.grid(row=6, column=1) self.audio_combobox = tkinter.ttk.Combobox(self.window, width=25) self.audio_combobox.grid(row=7, column=1, pady=5) tkinter.ttk.Combobox(self.window, values=['Audio only', 'Video only', 'Audio and video'], width=15).grid(row=9, column=0) tkinter.ttk.Combobox(self.window, values=['Yes', 'No'], width=15).grid(row=9, column=1, pady=20) tkinter.ttk.Progressbar(self.window, orient=HORIZONTAL, length=300, mode='determinate').grid(row=11, column=0, padx=200, pady=10, columnspan=3) def destination(self): select_path = str(filedialog.askdirectory()) self.een.insert(tkinter.END, select_path) def information(self): link = self.en.get().strip() if not link: return self.data = pafy.new(link) self.load_video_streams() self.load_audio_streams() def load_video_streams(self): video_quality_list = [] video_streams = self.data.videostreams for stream in video_streams: quality_str = f"{stream.resolution} | {stream.extension} | {stream.vcodec[:4]}" video_quality_list.append(quality_str) self.video_combobox['values'] = video_quality_list if video_quality_list: self.video_combobox.current(0) def load_audio_streams(self): audio_quality_list = [] audio_streams = self.data.audiostreams for stream in audio_streams: quality_str = f"{stream.bitrate} | {stream.extension} | {stream.acodec[:4]}" audio_quality_list.append(quality_str) self.audio_combobox['values'] = audio_quality_list if audio_quality_list: self.audio_combobox.current(0) if __name__ == "__main__": window = Tk() mywin = Youtube(window) window.title('Youtube Downloader') window.geometry("700x600+10+10") window.mainloop()
补充:如果运行时遇到pafy拉取视频信息报错,是因为pafy自带的解析逻辑适配旧版YouTube接口,安装
yt-dlp后配置pafy后端为yt-dlp即可解决。
内容的提问来源于stack exchange,提问作者Mahshid Saedi
相关产品推荐
相关产品推荐

