【记录】python3 tkinterUI编辑器应用之索尼walkmanA35播放列表编辑工具

news/2024/11/24 21:03:11/

tkinterUI编辑器应用(二)

  • 前言
  • 一、工具使用说明
  • 二、分析播放列表文件
  • 三、tinytag读取歌曲信息
  • 四、treeview列表模式时点击标题进行排序
  • 五、主文件代码,其他代码这里就不展示了


前言

几年前买了个A35播放器,在播放器里编辑播放列表比较麻烦,在电脑上编辑的话貌似需要下载一个官方的什么软件,我只需要一个编辑播放列表的工具,于是就自己用python写了一个,最近完成了tkinterUI编辑器,于是决定用编辑器重写一下。
我只有A35,不知道A55,A105这些支不支持
我没有mac,所以只支持windows,python版本是3.4,工具如下:
在这里插入图片描述
ui的读取创建是通过我之前写的编辑器处理的,可从这里查看编辑器的代码与使用方法

这个工具的所有代码可以在这里下载


一、工具使用说明

  1. 将播放器连接电脑
  2. 打开工具,工具会直接读取所有播放列表
  3. edit菜单中create_song_list可以创建播放列表
  4. edit菜单中delete_song_list可以删除播放列表,鼠标右键点击播放列表也可以弹出菜单进行删除
  5. edit菜单中add_songs可以添加歌曲到选中的播放列表
  6. edit菜单中delete_songs可以删除播放列表中选中的歌曲,鼠标右键歌曲也可以弹出菜单进行删除
  7. 点击index,title,artist,album,duration可以进行排序

二、分析播放列表文件

  1. 先在播放器里新建一个播放列表,然后添加几首歌,添加时发现只能添加同一个盘里面的歌曲,添加后连接到电脑发现在WALKMAN(F:)盘的MUSIC目录里多了一个后缀为.m3u的文件,这个就是播放列表文件,工具只需要操作这个文件就行,文件内容如下:
    #EXTM3U
    #EXTINF:,
    test/fate hf - Aimer - 花の唄 (花之歌).flac
    #EXTINF:,
    八度空间/(01) [周杰倫] 半獸人.flac
    #EXTINF:,
    不能说的秘密/(25) [周杰倫] 不能能的秘密.flac
    
  2. 开始研究m3u文件,发现只有第一行是"#EXTM3U",所以应该是每个播放列表第一行都应该是这个,然后每首歌之间有一行"#EXTINF:,",每首歌存储的是这首歌所在的相对路径,这样就分析完了

三、tinytag读取歌曲信息

  1. 其实这个工具只需要读取文件名就可以了,但是最近我在研究treeview,所以需要读取一下歌曲的数据进行显示,百度后发现tinytag这个库可以读取歌曲信息
  2. pip install tinytag进行安装
  3. from tinytag import TinyTag导入模块,tag = TinyTag.get(song_path)获取歌曲信息,之后可以使用tag.artist, tag.album, tag.duration等等获取属性
  4. 使用这个库的时候发现如果播放列表里歌曲比较多的话,第一次读取会比较慢

四、treeview列表模式时点击标题进行排序

最近在研究treeview,这里记录一下如何进行排序,代码如下:

    def sort_song_list(self, col, reverse):"""歌曲排序:param col: 要排序的列:param reverse: 是否倒序:return: None"""l = []for child in self.song_list.tree.get_children(''):l.append((self.song_list.tree.set(child, col), child))l.sort(key=lambda t:t[0], reverse=reverse)for index, (val, child) in enumerate(l):self.song_list.tree.move(child, '', index)self.song_list.tree.heading(col, command=partial(self.sort_song_list, col, not reverse))def init_song_list(self):"""初始化播放列表控件:return: None"""columns = ("index", "title", "artist", "album", "duration")self.song_list.tree.configure(columns=columns)for name in columns:self.song_list.tree.column(name, anchor="center", width=170)self.song_list.tree.heading(name, text=name, command=partial(self.sort_song_list, name, False))

说明:

  1. self.song_list.tree就是treeview控件

五、主文件代码,其他代码这里就不展示了

# -*- coding: UTF-8 -*-import osfrom tkinter import *
from tinytag import TinyTag
from functools import partial
from componentMgr import componentMgr
from components import create_default_component
from tkinter.filedialog import askopenfilenames, asksaveasfilenameM3U_HEAD = '#EXTM3U\n'
M3U_NEW_LINE = '#EXTINF:,'class SonySongListEditor(componentMgr):def __init__(self, master, gui_path):componentMgr.__init__(self, master)self.load_from_xml(master, gui_path, True)self.music_path_list = []self.song_index = 0self.right_edit_menu_1 = Noneself.right_edit_menu_2 = Noneself.build_music_path()self.init_frame()@propertydef editor_window(self):return self.master.children.get("sonySongListEditor", None)@propertydef song_list_list(self):return self.editor_window.children.get("songlistlist", None)@propertydef song_list(self):return self.editor_window.children.get("songlist", None)def build_music_path(self):"""创建音乐路径:return: None"""for i in range(ord("A"), ord("Z") + 1):path = chr(i) + ":\\" + "MUSIC"self.music_path_list.append(path)def init_frame(self):"""初始化窗口:return: None"""self.init_menu()self.init_song_list_list()self.init_song_list()self.scan_music_list()def init_menu(self):"""初始化菜单:return: None"""main_menu = Menu(self.master, tearoff=0, name="menu")edit_menu = Menu(main_menu, tearoff=0, name="edit")edit_menu.add_command(label="create_song_list", command=self.create_song_list)edit_menu.add_command(label="delete_song_list", command=self.delete_song_list)edit_menu.add_command(label="add_songs", command=self.add_songs)edit_menu.add_command(label="delete_songs", command=self.delete_songs)main_menu.add_cascade(label="edit", menu=edit_menu)self.master.config(menu=main_menu)self.right_edit_menu_1 = Menu(self.master, tearoff=0)self.right_edit_menu_1.add_command(label="delete_song_list", command=self.delete_song_list)self.right_edit_menu_2 = Menu(self.master, tearoff=0)self.right_edit_menu_2.add_command(label="delete_songs", command=self.delete_songs)def init_song_list_list(self):"""初始化播放列表列表控件:return: None"""self.song_list_list.set_handle_cancel_select_row(self.cancel_select_song_list)self.song_list_list.set_handle_select_row(self.select_song_list)def cancel_select_song_list(self, index):"""取消选中播放列表:param index: 索引:return: None"""row = self.song_list_list.get_row_by_index(index)if not row:returnrow.configure(state="normal")def select_song_list(self, index):"""选中播放列表:param index: 索引:param event::return:"""row = self.song_list_list.get_row_by_index(index)if not row:returnrow.configure(state="active")data = self.song_list_list.get_data_by_index(index)self.add_songs_from_path(data["list_path"], data["path"])def add_songs_from_path(self, song_list_path, path):"""从给定的路径读取歌曲信息:param song_list_path: 歌单路径:param path: 歌单所在文件夹路径:return: None"""self.song_list.clear_all_node()all_lines = []self.song_index = 0with open(song_list_path, 'r', encoding='utf-8') as f:line_head = f.readline()if line_head != M3U_HEAD:returnline = f.readline()i = 1while line:if i % 2 == 0:all_lines.append(os.path.join(path, line[:-1]))i += 1line = f.readline()for line in all_lines:self.add_song(line)self.song_list.update_scroll()def add_song(self, song_path):"""添加一首歌曲:param song_path: 歌曲路径:param index: 歌曲索引:return: None"""tag = TinyTag.get(song_path)title = self.calc_name(tag.title, song_path)self.song_list.add_node("", self.song_index, values=(str(self.song_index), title, tag.artist, tag.album, tag.duration, song_path))self.song_index += 1@staticmethoddef calc_name(title, song_path):"""如果没有读取出title则直接读取名字:param title: 读取出来的title:param song_path: 歌曲路径:return: string"""if title:return titleindex = song_path.find("/")return song_path[index:]def sort_song_list(self, col, reverse):"""歌曲排序:param col: 要排序的列:param reverse: 是否倒序:return: None"""l = []for child in self.song_list.tree.get_children(''):l.append((self.song_list.tree.set(child, col), child))l.sort(key=lambda t:t[0], reverse=reverse)for index, (val, child) in enumerate(l):self.song_list.tree.move(child, '', index)self.song_list.tree.heading(col, command=partial(self.sort_song_list, col, not reverse))def init_song_list(self):"""初始化播放列表控件:return: None"""columns = ("index", "title", "artist", "album", "duration")self.song_list.tree.configure(columns=columns)for name in columns:self.song_list.tree.column(name, anchor="center", width=170)self.song_list.tree.heading(name, text=name, command=partial(self.sort_song_list, name, False))self.song_list.set_on_select_tree(self.on_select_song)def on_select_song(self, event):"""选中歌曲时触发:param event::return: None"""self.right_edit_menu_2.post(event.x_root, event.y_root)def scan_music_list(self):"""扫描歌单:return:None"""self.song_list_list.clear_rows()for path in self.music_path_list:if not os.path.exists(path):continuefor file_name in os.listdir(path):self.scan_music_list_next(path, file_name)self.song_list_list.do_layout_row()self.song_list_list.select_first_row_base()def scan_music_list_next(self, path, file_name):"""扫描歌单下一步:param path: 扫描路径:param file_name: 文件路径:return: int"""file_name_base, file_ext = os.path.splitext(file_name)if file_ext != ".m3u":returnlist_path = os.path.join(path, file_name)created_num = self.song_list_list.get_created_row_num()prop = {"text": list_path, "activebackground": "red", "font_anchor": "w", "width": 40,}row, info = create_default_component(self.song_list_list.get_child_master(), "Label", "row_" + str(created_num), prop)row.bind("<ButtonRelease-1>", lambda event:self.song_list_list.set_selected_row(created_num))def right_click(event):self.song_list_list.set_selected_row(created_num)self.right_edit_menu_1.post(event.x_root, event.y_root)row.bind("<ButtonRelease-3>", right_click)data = {"list_path": list_path, "path": path,}self.song_list_list.add_row_base(row, False, data)return created_numdef create_song_list(self):"""创建播放列表:return: None"""file_path = asksaveasfilename(title=u"选择文件", filetypes=[("m3u files", "m3u"), ], defaultextension=".m3u")if not file_path:returnwith open(file_path, "w", encoding="utf-8") as f:f.writelines(M3U_HEAD)base_path, file_name = os.path.split(file_path)index = self.scan_music_list_next(base_path, file_name)self.song_list_list.do_layout_row()self.song_list_list.set_selected_row(index)def delete_song_list(self):"""删除播放列表:return: None"""selected = self.song_list_list.get_selected_row()if selected is None:returndata = self.song_list_list.get_data_by_index(selected)os.remove(data["list_path"])self.song_list.clear_all_node()self.song_list_list.delete_row_base(selected)def add_songs(self):"""将歌曲添加到播放列表:return: None"""selected = self.song_list_list.get_selected_row()if selected is None:returndata = self.song_list_list.get_data_by_index(selected)music_files = askopenfilenames(initialdir=data["path"])if not music_files:returnfor music_path in music_files:self.add_song(music_path)self.save_song_list(data["list_path"], data["path"])def delete_songs(self):"""从播放列表中删除歌曲:return: None"""selected = self.song_list_list.get_selected_row()if selected is None:returnselect_songs = self.song_list.tree.selection()for idx in select_songs:self.song_list.tree.delete(idx)data = self.song_list_list.get_data_by_index(selected)self.save_song_list(data["list_path"], data["path"])def save_song_list(self, song_list, base_path):"""保存播放列表:param song_list: 播放列表路径:param base_path: 播放列表base路径:return: None"""with open(song_list, "w", encoding="utf-8") as f:f.writelines(M3U_HEAD)songs = self.get_all_songs()song_list_new = []for song in songs:new_path = self.get_relatively_path(song, base_path)song_list_new.append(M3U_NEW_LINE + '\n')if not new_path.endswith('\n'):new_path += '\n'song_list_new.append(new_path)f.writelines(song_list_new)def get_all_songs(self):"""获取所有歌曲:return: None"""all_songs = []for item in self.song_list.tree.get_children():item_text = self.song_list.tree.item(item, "values")all_songs.append(item_text[5])return all_songs@staticmethoddef get_relatively_path(music_path, base_path):"""计算相对路径:param music_path:音乐路径:param base_path:歌单路径:return:string"""index = os.path.normpath(music_path).find(os.path.normpath(base_path))if index == -1:raise Exception("音乐文件必须在MUSIC目录中")return music_path[index + len(base_path) + 1:]def main():root = Tk()path = os.path.join(os.getcwd(), 'SonySongListEditor.xml')SonySongListEditor(root, path)root.mainloop()if __name__ == "__main__":main()


http://www.ppmy.cn/news/221530.html

相关文章

运维小白必学篇之基础篇第七集:磁盘管理实验

磁盘管理实验 实验作业&#xff1a; 1、添加1块磁盘&#xff0c;并查看&#xff08;lsblk&#xff09; 2、使用MBR分区表的格式对添加的磁盘划分分区&#xff0c;完成以下操作&#xff1a; 1、创建3个主分区&#xff0c;每个分区大小为2个GB 2、创建扩展分区&#xff0c;将剩…

【学习记录】win10 + ubuntu 22.04双系统安装

一、背景 因为家里的台式&#xff08;Windows 10&#xff09;最近一直频繁蓝屏&#xff0c;再加上Win10之前经常性的资源管理器未响应&#xff0c;对Windows系统逐渐失去了信心&#xff0c;于是想着安装稳定性较好的Linux。以前抵触Linux是因为其人机交互界面没Windows那么直观…

windows 下使用lex

官网下载win flex-bison https://sourceforge.net/projects/winflexbison/ 解压 cmd到win——flex的目录 a.l文件内容 cpp %{ #include <stdio.h> #include <stdlib.h> int count 0; %} delim [" "\n\t\r] whitespace {delim} opera…

DELL R720更新IDRAC固件

1.开机看见DELL图标之后按F10进入LIFECYCLE CONTROLLER界面&#xff0c;选择固件更新&#xff0c;并启动固件更新。 2.使将所需要更新的固件放在U盘中&#xff0c;选择本地驱动器&#xff0c;点击下一步。 3.插入U盘点击浏览后&#xff0c;选择相应固件&#xff0c;为方便使…

continue、break的用法与区别

continue、break的用法与区别&#xff08;Java语言等&#xff09; 介绍一些语言中的break、continue的用法与区别 1/5、break语句可以用于switch、for、while、do-while&#xff0c;用于结束最近一层。如果是两层循环则只能跳出最近的一层循环。 如下的例子&#xff1a; 计算0&…

Lexar雷克沙新品上市 | 这一抹白色我们称之为雷神铠 OC DDR4 UDIMM

Lexar雷克沙新品上市 | 这一抹白色我们称之为雷神铠 OC DDR4 UDIMM 从某种意义上来说&#xff0c;白色有着“永不过时且高贵纯洁”的特性&#xff0c;白色内存亦是如此&#xff0c;它的存在&#xff0c;虽不是电脑硬件领域里的新鲜物&#xff0c;但每每与白色主板搭配&#xff…

lex 常用函数

yyleng 只要扫描程序匹配标记时&#xff0c;标记的文本就存储在以空字符终止的字符串yytext中&#xff0c;而且它的长度存储在yyleng中&#xff0c;yyleng中的长度与由strlen(yytext)返回的值是相同的。 yyless() 从与规则相关的代码中调用yyless(n)&#xff0c;这条规则推回除…

Lex学习笔记——规范

lex是构建词法分析程序的工具&#xff0c;词法分析程序把随机输入流标记化&#xff08;tokenize&#xff09;&#xff0c;即&#xff0c;将它拆分成词法标记。当编写lex规范时&#xff0c;可以创建lex匹配输入所用的一套规则。每次匹配一个模式时&#xff0c;lex程序就调用你提…