Compare commits

...

No commits in common. 'd126233f376416870106f21e7ebe5000e932e770' and '869f24d6fc742d45f9210380ba941b89524c38c0' have entirely different histories.

131
.gitignore vendored

@ -1,131 +0,0 @@
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/

@ -1,318 +0,0 @@
Creative Commons Attribution-NonCommercial 3.0 Unported CREATIVE COMMONS CORPORATION
IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS
LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS
PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING
FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS
PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR
OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS
LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO
BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED
TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION
OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and other
pre-existing works, such as a translation, adaptation, derivative work, arrangement
of music or other alterations of a literary or artistic work, or phonogram
or performance and includes cinematographic adaptations or any other form
in which the Work may be recast, transformed, or adapted including in any
form recognizably derived from the original, except that a work that constitutes
a Collection will not be considered an Adaptation for the purpose of this
License. For the avoidance of doubt, where the Work is a musical work, performance
or phonogram, the synchronization of the Work in timed-relation with a moving
image ("synching") will be considered an Adaptation for the purpose of this
License.
b. "Collection" means a collection of literary or artistic works, such as
encyclopedias and anthologies, or performances, phonograms or broadcasts,
or other works or subject matter other than works listed in Section 1(f) below,
which, by reason of the selection and arrangement of their contents, constitute
intellectual creations, in which the Work is included in its entirety in unmodified
form along with one or more other contributions, each constituting separate
and independent works in themselves, which together are assembled into a collective
whole. A work that constitutes a Collection will not be considered an Adaptation
(as defined above) for the purposes of this License.
c. "Distribute" means to make available to the public the original and copies
of the Work or Adaptation, as appropriate, through sale or other transfer
of ownership.
d. "Licensor" means the individual, individuals, entity or entities that offer(s)
the Work under the terms of this License.
e. "Original Author" means, in the case of a literary or artistic work, the
individual, individuals, entity or entities who created the Work or if no
individual or entity can be identified, the publisher; and in addition (i)
in the case of a performance the actors, singers, musicians, dancers, and
other persons who act, sing, deliver, declaim, play in, interpret or otherwise
perform literary or artistic works or expressions of folklore; (ii) in the
case of a phonogram the producer being the person or legal entity who first
fixes the sounds of a performance or other sounds; and, (iii) in the case
of broadcasts, the organization that transmits the broadcast.
f. "Work" means the literary and/or artistic work offered under the terms
of this License including without limitation any production in the literary,
scientific and artistic domain, whatever may be the mode or form of its expression
including digital form, such as a book, pamphlet and other writing; a lecture,
address, sermon or other work of the same nature; a dramatic or dramatico-musical
work; a choreographic work or entertainment in dumb show; a musical composition
with or without words; a cinematographic work to which are assimilated works
expressed by a process analogous to cinematography; a work of drawing, painting,
architecture, sculpture, engraving or lithography; a photographic work to
which are assimilated works expressed by a process analogous to photography;
a work of applied art; an illustration, map, plan, sketch or three-dimensional
work relative to geography, topography, architecture or science; a performance;
a broadcast; a phonogram; a compilation of data to the extent it is protected
as a copyrightable work; or a work performed by a variety or circus performer
to the extent it is not otherwise considered a literary or artistic work.
g. "You" means an individual or entity exercising rights under this License
who has not previously violated the terms of this License with respect to
the Work, or who has received express permission from the Licensor to exercise
rights under this License despite a previous violation.
h. "Publicly Perform" means to perform public recitations of the Work and
to communicate to the public those public recitations, by any means or process,
including by wire or wireless means or public digital performances; to make
available to the public Works in such a way that members of the public may
access these Works from a place and at a place individually chosen by them;
to perform the Work to the public by any means or process and the communication
to the public of the performances of the Work, including by public digital
performance; to broadcast and rebroadcast the Work by any means including
signs, sounds or images.
i. "Reproduce" means to make copies of the Work by any means including without
limitation by sound or visual recordings and the right of fixation and reproducing
fixations of the Work, including storage of a protected performance or phonogram
in digital form or other electronic medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit,
or restrict any uses free from copyright or rights arising from limitations
or exceptions that are provided for in connection with the copyright protection
under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License, Licensor
hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for
the duration of the applicable copyright) license to exercise the rights in
the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more Collections,
and to Reproduce the Work as incorporated in the Collections;
b. to create and Reproduce Adaptations provided that any such Adaptation,
including any translation in any medium, takes reasonable steps to clearly
label, demarcate or otherwise identify that changes were made to the original
Work. For example, a translation could be marked "The original work was translated
from English to Spanish," or a modification could indicate "The original work
has been modified.";
c. to Distribute and Publicly Perform the Work including as incorporated in
Collections; and,
d. to Distribute and Publicly Perform Adaptations.
The above rights may be exercised in all media and formats whether now known
or hereafter devised. The above rights include the right to make such modifications
as are technically necessary to exercise the rights in other media and formats.
Subject to Section 8(f), all rights not expressly granted by Licensor are
hereby reserved, including but not limited to the rights set forth in Section
4(d).
4. Restrictions. The license granted in Section 3 above is expressly made
subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms of
this License. You must include a copy of, or the Uniform Resource Identifier
(URI) for, this License with every copy of the Work You Distribute or Publicly
Perform. You may not offer or impose any terms on the Work that restrict the
terms of this License or the ability of the recipient of the Work to exercise
the rights granted to that recipient under the terms of the License. You may
not sublicense the Work. You must keep intact all notices that refer to this
License and to the disclaimer of warranties with every copy of the Work You
Distribute or Publicly Perform. When You Distribute or Publicly Perform the
Work, You may not impose any effective technological measures on the Work
that restrict the ability of a recipient of the Work from You to exercise
the rights granted to that recipient under the terms of the License. This
Section 4(a) applies to the Work as incorporated in a Collection, but this
does not require the Collection apart from the Work itself to be made subject
to the terms of this License. If You create a Collection, upon notice from
any Licensor You must, to the extent practicable, remove from the Collection
any credit as required by Section 4(c), as requested. If You create an Adaptation,
upon notice from any Licensor You must, to the extent practicable, remove
from the Adaptation any credit as required by Section 4(c), as requested.
b. You may not exercise any of the rights granted to You in Section 3 above
in any manner that is primarily intended for or directed toward commercial
advantage or private monetary compensation. The exchange of the Work for other
copyrighted works by means of digital file-sharing or otherwise shall not
be considered to be intended for or directed toward commercial advantage or
private monetary compensation, provided there is no payment of any monetary
compensation in connection with the exchange of copyrighted works.
c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections,
You must, unless a request has been made pursuant to Section 4(a), keep intact
all copyright notices for the Work and provide, reasonable to the medium or
means You are utilizing: (i) the name of the Original Author (or pseudonym,
if applicable) if supplied, and/or if the Original Author and/or Licensor
designate another party or parties (e.g., a sponsor institute, publishing
entity, journal) for attribution ("Attribution Parties") in Licensor's copyright
notice, terms of service or by other reasonable means, the name of such party
or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably
practicable, the URI, if any, that Licensor specifies to be associated with
the Work, unless such URI does not refer to the copyright notice or licensing
information for the Work; and, (iv) consistent with Section 3(b), in the case
of an Adaptation, a credit identifying the use of the Work in the Adaptation
(e.g., "French translation of the Work by Original Author," or "Screenplay
based on original Work by Original Author"). The credit required by this Section
4(c) may be implemented in any reasonable manner; provided, however, that
in the case of a Adaptation or Collection, at a minimum such credit will appear,
if a credit for all contributing authors of the Adaptation or Collection appears,
then as part of these credits and in a manner at least as prominent as the
credits for the other contributing authors. For the avoidance of doubt, You
may only use the credit required by this Section for the purpose of attribution
in the manner set out above and, by exercising Your rights under this License,
You may not implicitly or explicitly assert or imply any connection with,
sponsorship or endorsement by the Original Author, Licensor and/or Attribution
Parties, as appropriate, of You or Your use of the Work, without the separate,
express prior written permission of the Original Author, Licensor and/or Attribution
Parties.
d. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in which
the right to collect royalties through any statutory or compulsory licensing
scheme cannot be waived, the Licensor reserves the exclusive right to collect
such royalties for any exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in which the
right to collect royalties through any statutory or compulsory licensing scheme
can be waived, the Licensor reserves the exclusive right to collect such royalties
for any exercise by You of the rights granted under this License if Your exercise
of such rights is for a purpose or use which is otherwise than noncommercial
as permitted under Section 4(b) and otherwise waives the right to collect
royalties through any statutory or compulsory licensing scheme; and,
iii. Voluntary License Schemes. The Licensor reserves the right to collect
royalties, whether individually or, in the event that the Licensor is a member
of a collecting society that administers voluntary licensing schemes, via
that society, from any exercise by You of the rights granted under this License
that is for a purpose or use which is otherwise than noncommercial as permitted
under Section 4(c).
e. Except as otherwise agreed in writing by the Licensor or as may be otherwise
permitted by applicable law, if You Reproduce, Distribute or Publicly Perform
the Work either by itself or as part of any Adaptations or Collections, You
must not distort, mutilate, modify or take other derogatory action in relation
to the Work which would be prejudicial to the Original Author's honor or reputation.
Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise
of the right granted in Section 3(b) of this License (the right to make Adaptations)
would be deemed to be a distortion, mutilation, modification or other derogatory
action prejudicial to the Original Author's honor and reputation, the Licensor
will waive or not assert, as appropriate, this Section, to the fullest extent
permitted by the applicable national law, to enable You to reasonably exercise
Your right under Section 3(b) of this License (right to make Adaptations)
but not otherwise.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS
THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING
THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT,
OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE
OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE
EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW,
IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS
LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY
OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate automatically
upon any breach by You of the terms of this License. Individuals or entities
who have received Adaptations or Collections from You under this License,
however, will not have their licenses terminated provided such individuals
or entities remain in full compliance with those licenses. Sections 1, 2,
5, 6, 7, and 8 will survive any termination of this License.
b. Subject to the above terms and conditions, the license granted here is
perpetual (for the duration of the applicable copyright in the Work). Notwithstanding
the above, Licensor reserves the right to release the Work under different
license terms or to stop distributing the Work at any time; provided, however
that any such election will not serve to withdraw this License (or any other
license that has been, or is required to be, granted under the terms of this
License), and this License will continue in full force and effect unless terminated
as stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection,
the Licensor offers to the recipient a license to the Work on the same terms
and conditions as the license granted to You under this License.
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers
to the recipient a license to the original Work on the same terms and conditions
as the license granted to You under this License.
c. If any provision of this License is invalid or unenforceable under applicable
law, it shall not affect the validity or enforceability of the remainder of
the terms of this License, and without further action by the parties to this
agreement, such provision shall be reformed to the minimum extent necessary
to make such provision valid and enforceable.
d. No term or provision of this License shall be deemed waived and no breach
consented to unless such waiver or consent shall be in writing and signed
by the party to be charged with such waiver or consent.
e. This License constitutes the entire agreement between the parties with
respect to the Work licensed here. There are no understandings, agreements
or representations with respect to the Work not specified here. Licensor shall
not be bound by any additional provisions that may appear in any communication
from You. This License may not be modified without the mutual written agreement
of the Licensor and You.
f. The rights granted under, and the subject matter referenced, in this License
were drafted utilizing the terminology of the Berne Convention for the Protection
of Literary and Artistic Works (as amended on September 28, 1979), the Rome
Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances
and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised
on July 24, 1971). These rights and subject matter take effect in the relevant
jurisdiction in which the License terms are sought to be enforced according
to the corresponding provisions of the implementation of those treaty provisions
in the applicable national law. If the standard suite of rights granted under
applicable copyright law includes additional rights not granted under this
License, such additional rights are deemed to be included in the License;
this License is not intended to restrict the license of any rights under applicable
law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty whatsoever
in connection with the Work. Creative Commons will not be liable to You or
any party on any legal theory for any damages whatsoever, including without
limitation any general, special, incidental or consequential damages arising
in connection to this license. Notwithstanding the foregoing two (2) sentences,
if Creative Commons has expressly identified itself as the Licensor hereunder,
it shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the Work is
licensed under the CCPL, Creative Commons does not authorize the use by either
party of the trademark "Creative Commons" or any related trademark or logo
of Creative Commons without the prior written consent of Creative Commons.
Any permitted use will be in compliance with Creative Commons' then-current
trademark usage guidelines, as may be published on its website or otherwise
made available upon request from time to time. For the avoidance of doubt,
this trademark restriction does not form part of the License.
Creative Commons may be contacted at http://creativecommons.org/.

@ -1,19 +0,0 @@
#### 从命令行创建一个新的仓库
```bash
touch README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://bdgit.educoder.net/hnu202010040122/music_player.git
git push -u origin master
```
#### 从命令行推送已经创建的仓库
```bash
git remote add origin https://bdgit.educoder.net/hnu202010040122/music_player.git
git push -u origin master
```

@ -0,0 +1,878 @@
import os
import re
import threading
import time
import wx
import wx.adv
import wx.lib.mixins.listctrl
from pygame import mixer
import ruijia
user_music_folder = os.path.expanduser(r'~\Music')
DEFAULT_SETTING = { # 默认设置
# 用户可设置
'bg_color': '#88fefe',
'font_size': 13,
'text_color': '#000000',
'lyrics_fg_color': '#ff1122',
'lyrics_bg_color': '#000000',
'volume': 0.3, # 自动保存
'font': '宋体',
'underline': False,
'music_folder_type': 0, # 0表示默认目录1表示用户音乐路径2表示自定义路径
'music_folder_list': ['music_folder', user_music_folder, ''],
# 用户不可设置
'max_music_name_len': 30,
'app_title': 'music_player',
}
FONT_LIST = ['黑体', '楷体', '宋体', '仿宋', 'Arial', '等线', '华文琥珀', '华文行楷', '华文新魏'] # 字体列表
FONT_LIST = [font for font in FONT_LIST if wx.Font().SetFaceName(font)] # 字体可能会不存在
def get_lyrics(lyric_path: str) -> list:
"""
从歌词文件中读取歌词返回列表
:param lyric_path:歌词路径str
:return:歌词列表时间文字元组
"""
lyric_row_reg = '\[[0-9]{2}:[0-9]{2}.[0-9]{2,}\]'
if os.path.exists(lyric_path):
with open(lyric_path, 'r', encoding="utf-8") as f:
content_list = [line.strip() for line in f.readlines()] # 去除每行的\n
lyrics_list = []
for content in content_list:
while re.match(lyric_row_reg, content): # 一句歌词可能有多个时间
lyric_time = float(content[1:3]) * 60 + float(content[4:6]) + float(content[7:9]) / 100
lyric = content[content.rindex(']') + 1:]
lyrics_list.append((lyric_time, lyric))
content = content[content.index(']') + 1:]
lyrics_list.sort(key=lambda x: x[0]) # 按时间排序
return lyrics_list
else:
return [(0, '纯音乐或无歌词')]
def get_setting() -> dict:
"""
从设置文件中读取设置如不存在则使用默认设置
:return: 设置项目的字典
"""
if os.path.exists('setting.txt'):
with open('setting.txt', 'r', encoding='utf-8') as f:
setting_dic = eval(f.read())
else:
setting_dic = DEFAULT_SETTING
return setting_dic
class RankListNotebook(wx.Notebook):
def __init__(self, parent):
wx.Notebook.__init__(self, parent)
self.SetFont(parent.user_font)
self.text_color = parent.setting_dic['text_color']
url_dic = {
'酷狗飙升榜': 'http://www.kugou.com/yy/rank/home/{}-6666.html',
'酷狗TOP500': 'http://www.kugou.com/yy/rank/home/{}-8888.html',
'华语新歌榜': 'http://www.kugou.com/yy/rank/home/{}-31308.html',
'酷狗雷达榜': 'https://www.kugou.com/yy/rank/home/{}-37361.html'
}
# 进度dialog初始化
max_ = 200*len(url_dic)
dlg = wx.ProgressDialog("处理中", "获取数据中", maximum=max_, parent=self,
style=wx.PD_AUTO_HIDE | wx.PD_CAN_ABORT | wx.PD_APP_MODAL)
count = 0
for name in url_dic: # 每个排行榜为一页
page = wx.ScrolledWindow(self)
self.AddPage(page, name) # TODO 页面描述文字不可设置字体颜色
page.SetScrollbars(10, 10, 1, 1)
sizer = wx.BoxSizer(wx.HORIZONTAL)
page.SetSizer(sizer)
sizer.Add((200, -1), 0) # 设置榜单左方空白区域
column_sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(column_sizer, proportion=1)
column_sizer.Add((0, 50), 0) # 榜单上方空白
for i in range(50): # 进度条更新
dlg.Update(count := count + 1)
if dlg.WasCancelled(): # 当dialog取消时退出循环
break
ranking_list = ruijia.main(url_dic[name]) # 从ruijia.py获取榜单
for line in range(100):
song = str(line+1) + ranking_list[line]['song'] # 排名加上歌名——歌手
st = wx.StaticText(page, -1, song)
st.SetFont(parent.user_font)
st.SetForegroundColour(self.text_color)
column_sizer.Add(st, 1, flag=wx.TOP, border=5)
dlg.Update(count := count + 1) # 进度条更新
column_sizer.Add((0, 50), 0) # 榜单下方空白
for i in range(50): # 进度条更新
dlg.Update(count := count + 1)
if dlg.WasCancelled(): # 当dialog取消时退出循环
break
dlg.Destroy()
class SettingPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.parent = parent
self.present_setting = parent.setting_dic # 当前(未修改)的设置
self.unsaved_setting = parent.unsaved_setting # 修改后未保存的设置
self.bg_color = self.present_setting['bg_color']
self.text_color = self.present_setting['text_color']
# 使设置panel居中显示
setting_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.SetSizer(setting_sizer)
setting_sizer.AddStretchSpacer(1)
self.main_panel = wx.Panel(self, size=(800, 550))
self.main_panel.SetFont(parent.user_font)
self.main_panel.SetForegroundColour(self.text_color)
setting_sizer.Add(self.main_panel, 0, wx.ALIGN_CENTRE)
# 标题----------------------------------------------------------------------------------------------------------
title = wx.StaticText(self.main_panel, -1, '设置(重启后生效)')
title.SetFont(wx.Font(pointSize=23, family=wx.FONTFAMILY_DEFAULT, style=wx.FONTSTYLE_NORMAL,
weight=wx.FONTWEIGHT_NORMAL, faceName='黑体')) # title字体及颜色不可更改
title.SetForegroundColour('#000')
# 重置按钮
reset_button = wx.Button(self.main_panel, -1, '重置', pos=(300, 5))
reset_button.SetForegroundColour(self.text_color)
reset_button.SetBackgroundColour(self.bg_color)
reset_button.Bind(wx.EVT_LEFT_DOWN, self.reset)
# 歌词前景背景颜色 ------------------------------------------------------------------------------------------------
self.box1 = wx.StaticBox(self.main_panel, -1, "歌词设置", pos=(50, 50), size=(700, 70))
# 歌词前景色文字及按钮
text1 = f"歌词前景色:{self.present_setting['lyrics_fg_color']}"
self.lyrics_fg_color_text = wx.StaticText(self.box1, -1, text1, pos=(30, 30))
self.lyrics_fg_color_text.SetForegroundColour(self.present_setting['lyrics_fg_color'])
self.lyrics_fg_color_button = wx.Button(self.box1, -1, '修改', pos=(230, 25))
self.lyrics_fg_color_button.SetForegroundColour(self.text_color)
self.lyrics_fg_color_button.SetBackgroundColour(self.bg_color)
self.lyrics_fg_color_button.Bind(wx.EVT_LEFT_DOWN, self.change_lyrics_fg_color)
# 歌词背景色文字及按钮
text2 = f"歌词背景色:{self.present_setting['lyrics_bg_color']}"
self.lyrics_bg_color_text = wx.StaticText(self.box1, -1, text2, pos=(400, 30))
self.lyrics_bg_color_text.SetForegroundColour(self.present_setting['lyrics_bg_color'])
self.lyrics_bg_color_button = wx.Button(self.box1, -1, '修改', pos=(600, 25))
self.lyrics_bg_color_button.SetForegroundColour(self.text_color)
self.lyrics_bg_color_button.SetBackgroundColour(self.bg_color)
self.lyrics_bg_color_button.Bind(wx.EVT_LEFT_DOWN, self.change_lyrics_bg_color)
# 界面颜色,字体设置 ----------------------------------------------------------------------------------------------
self.box2 = wx.StaticBox(self.main_panel, -1, "界面设置", pos=(50, 145), size=(700, 150))
# 页面背景颜色文字及按钮
text3 = f"页面背景色:{self.bg_color}"
self.bg_color_text = wx.StaticText(self.box2, -1, text3, pos=(30, 30))
self.bg_color_text.SetForegroundColour(self.text_color)
self.bg_color_button = wx.Button(self.box2, -1, '修改', pos=(230, 25))
self.bg_color_button.SetForegroundColour(self.text_color)
self.bg_color_button.SetBackgroundColour(self.bg_color)
self.bg_color_button.Bind(wx.EVT_LEFT_DOWN, self.change_bg_color)
# 文字颜色文字及按钮
text4 = f" 文字颜色 {self.text_color}"
self.text_color_text = wx.StaticText(self.box2, -1, text4, pos=(400, 30))
self.text_color_text.SetForegroundColour(self.text_color)
self.text_color_button = wx.Button(self.box2, -1, '修改', pos=(600, 25))
self.text_color_button.SetForegroundColour(self.text_color)
self.text_color_button.SetBackgroundColour(self.bg_color)
self.text_color_button.Bind(wx.EVT_LEFT_DOWN, self.change_text_color)
# 文字大小文字及text ctrl和spin button
self.font_size_text = wx.StaticText(self.box2, -1, '文字大小:', pos=(30, 90))
font_size = self.present_setting['font_size']
self.font_size = wx.TextCtrl(self.box2, -1, str(font_size), pos=(120, 85),
size=(40, -1), style=wx.TE_READONLY | wx.TE_CENTRE)
self.font_size.SetBackgroundColour(self.bg_color)
self.font_size.SetForegroundColour(self.text_color)
font_size_width = self.font_size.GetSize()[1]
self.font_size_spin = wx.SpinButton(self.box2, -1, pos=(160, 85), size=(15, font_size_width))
self.font_size_spin.SetRange(9, 15)
self.font_size_spin.SetValue(font_size)
self.font_size_spin.Bind(wx.EVT_SPIN, self.change_font_size)
# 文字字体combobox
self.font_name_text = wx.StaticText(self.box2, -1, '当前字体:', pos=(280, 90))
self.font_choice = wx.ComboBox(self.box2, -1, pos=(380, 85),
size=(150, -1), choices=FONT_LIST)
self.font_choice.SetBackgroundColour(self.bg_color)
self.font_choice.SetForegroundColour(self.text_color)
self.font_choice.SetSelection(FONT_LIST.index(self.present_setting['font']))
self.font_choice.Bind(wx.EVT_TEXT, self.change_font)
# 是否显示下划线的checkbox
self.underline = wx.CheckBox(self.box2, -1, '下划线', pos=(600, 88))
self.underline.SetValue(self.present_setting['underline'])
self.underline.Bind(wx.EVT_CHECKBOX, self.is_underline)
# 播放路径设置 ---------------------------------------------------------------------------------------------------
folder_type = self.present_setting['music_folder_type']
self.box3_text = f"歌曲路径选择({['默认路径','用户音乐目录','自定义路径'][folder_type]}"
self.box3 = wx.StaticBox(self.main_panel, -1, self.box3_text,
pos=(50, 320), size=(700, 100))
wx.StaticText(self.box3, -1, '当前路径:', pos=(10, 70))
# 路径textctrl
path = os.path.abspath(self.present_setting['music_folder_list'][folder_type])
self.path_text_ctrl = wx.TextCtrl(self.box3, -1, path, pos=(100, 65), size=(480, -1))
self.path_text_ctrl.SetBackgroundColour(self.bg_color)
self.path_text_ctrl.SetForegroundColour(self.text_color)
# 默认路径按钮
self.folder_button1 = wx.Button(self.box3, -1, label="默认路径", pos=(100, 27))
self.folder_button1.SetBackgroundColour(self.bg_color)
self.folder_button1.SetForegroundColour(self.text_color)
self.folder_button1.Bind(wx.EVT_LEFT_DOWN, self.select_path_type1)
# 用户音乐目录按钮
self.folder_button2 = wx.Button(self.box3, -1, label="用户音乐目录", pos=(233, 27))
self.folder_button2.SetBackgroundColour(self.bg_color)
self.folder_button2.SetForegroundColour(self.text_color)
self.folder_button2.Bind(wx.EVT_LEFT_DOWN, self.select_path_type2)
# 自定义路径按钮
self.button3 = wx.Button(self.box3, -1, label="自定义路径", pos=(400, 27))
self.button3.SetBackgroundColour(self.bg_color)
self.button3.SetForegroundColour(self.text_color)
self.button3.Bind(wx.EVT_LEFT_DOWN, self.select_path_type3)
# 路径选择按钮(自定义路径时显示)
self.path_select_button = wx.Button(self.box3, label="选择路径", pos=(580, 27))
self.path_select_button.SetBackgroundColour(self.bg_color)
self.path_select_button.SetForegroundColour(self.text_color)
self.path_select_button.Bind(wx.EVT_LEFT_DOWN, self.select_path)
# 确认路径按钮(自定义路径时显示)
self.folder_confirm_button = wx.Button(self.box3, label="确认", pos=(607, 65))
self.folder_confirm_button.SetBackgroundColour(self.bg_color)
self.folder_confirm_button.SetForegroundColour(self.text_color)
self.folder_confirm_button.Bind(wx.EVT_LEFT_DOWN, self.folder_confirm)
self.hide_show_button(folder_type == 2) # 是否显示路径选择按钮和确认路径按钮
setting_sizer.AddStretchSpacer(1)
def reset(self, evt):
"""
重置为默认设置
"""
for i in DEFAULT_SETTING:
self.unsaved_setting[i] = DEFAULT_SETTING[i]
dlg = wx.MessageDialog(self, r'成功重置所有设置,重启后生效', '成功', wx.OK)
dlg.ShowModal()
dlg.Destroy()
def change_lyrics_fg_color(self, evt):
"""
修改歌词前景色
"""
dlg = wx.ColourDialog(self)
if dlg.ShowModal() == wx.ID_OK:
color = dlg.GetColourData().GetColour().GetAsString(flags=wx.C2S_HTML_SYNTAX)
self.unsaved_setting['lyrics_fg_color'] = color
self.lyrics_fg_color_text.SetForegroundColour(color)
self.lyrics_fg_color_text.SetLabel(f'歌词前景色:{color}')
dlg.Destroy()
def change_lyrics_bg_color(self, evt):
"""
修改歌词背景色
"""
dlg = wx.ColourDialog(self)
if dlg.ShowModal() == wx.ID_OK:
color = dlg.GetColourData().GetColour().GetAsString(flags=wx.C2S_HTML_SYNTAX)
self.unsaved_setting['lyrics_bg_color'] = color
self.lyrics_bg_color_text.SetForegroundColour(color)
self.lyrics_bg_color_text.SetLabel(f'歌词背景色:{color}')
dlg.Destroy()
def change_bg_color(self, evt):
"""
修改界面背景色
"""
dlg = wx.ColourDialog(self)
if dlg.ShowModal() == wx.ID_OK:
color = dlg.GetColourData().GetColour().GetAsString(flags=wx.C2S_HTML_SYNTAX)
self.unsaved_setting['bg_color'] = color
self.bg_color_text.SetBackgroundColour(color)
self.bg_color_text.SetLabel(f'页面背景色:{color}')
dlg.Destroy()
def change_text_color(self, evt):
"""
修改文字颜色不包括歌词
"""
dlg = wx.ColourDialog(self)
if dlg.ShowModal() == wx.ID_OK:
color = dlg.GetColourData().GetColour().GetAsString(flags=wx.C2S_HTML_SYNTAX)
self.unsaved_setting['text_color'] = color
self.text_color_text.SetForegroundColour(color)
self.text_color_text.SetLabel(f' 文字颜色 {color}')
dlg.Destroy()
def change_font_size(self, evt):
"""
修改文字大小
"""
size = self.font_size_spin.GetValue()
self.font_size.SetLabel(str(size))
self.unsaved_setting['font_size'] = size
def change_font(self, evt):
"""
修改文字字体
"""
font_name = evt.GetEventObject().GetValue()
if font_name in FONT_LIST:
self.unsaved_setting['font'] = font_name
# font = self.parent.user_font 此方式会在后续设置中更改父类中的原字体
font = wx.Font(self.parent.user_font)
font.SetFaceName(font_name)
self.font_name_text.SetFont(font)
self.font_choice.SetFont(font)
def is_underline(self, evt):
"""
修改是否显示下划线
"""
underline = self.underline.GetValue()
# font = self.parent.user_font 此方式会在后续设置中更改父类中的原字体
font = wx.Font(self.parent.user_font)
font.SetUnderlined(underline)
self.underline.SetFont(font)
self.unsaved_setting['underline'] = underline
def select_path_type1(self, evt):
"""
修改路径类型默认路径
"""
self.hide_show_button(0)
self.box3.SetLabel('歌曲路径选择(默认路径)')
self.box3.Refresh()
self.unsaved_setting['music_folder_type'] = 0
folder_path = os.path.abspath('music_folder')
self.path_text_ctrl.SetLabel(folder_path)
dlg = wx.MessageDialog(self, r'成功设置路径:默认路径(.\music_folder)', '成功', wx.OK)
dlg.ShowModal()
dlg.Destroy()
def select_path_type2(self, evt):
"""
修改路径类型用户音乐目录
"""
self.hide_show_button(0)
self.box3.SetLabel('歌曲路径选择(用户音乐目录)')
self.box3.Refresh()
self.unsaved_setting['music_folder_type'] = 1
folder_path = os.path.expanduser('~\Music')
self.path_text_ctrl.SetLabel(folder_path)
dlg = wx.MessageDialog(self, rf'成功设置路径:用户音乐目录({folder_path})', '成功', wx.OK)
dlg.ShowModal()
dlg.Destroy()
def select_path_type3(self, evt):
"""
修改路径类型自定义路径并显示选择路径及确认按钮
"""
self.hide_show_button(1)
self.box3.SetLabel('歌曲路径选择(自定义路径)')
self.box3.Refresh()
def hide_show_button(self, op: int):
"""
是否显示选择路径及确认按钮及路径textctrl是否可修改
"""
if op == 0:
self.path_select_button.Hide()
self.folder_confirm_button.Hide()
self.path_text_ctrl.SetEditable(False)
elif op == 1:
self.path_select_button.Show()
self.folder_confirm_button.Show()
self.path_text_ctrl.SetEditable(True)
def select_path(self, evt):
"""
选择路径将其显示在路径textctrl中
"""
dlg = wx.DirDialog(self, "选择音乐文件夹", defaultPath=os.getcwd(), style=wx.DD_DEFAULT_STYLE)
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
self.path_text_ctrl.SetLabel(path)
dlg.Destroy()
def folder_confirm(self, evt):
"""
确认路径
"""
path = self.path_text_ctrl.GetValue()
if path == 'fly': # fly
dlg = wx.Dialog(self, size=(333, 280), title='fly')
wx.adv.AnimationCtrl(dlg, -1, wx.adv.Animation(r'icon\fly')).Play()
elif os.path.exists(path): # 路径存在
self.unsaved_setting['music_folder_list'][2] = path
dlg = wx.MessageDialog(self, '成功设置路径:'+path, '成功', wx.OK)
self.unsaved_setting['music_folder_type'] = 2
else: # 路径不存在
dlg = wx.MessageDialog(self, '请输入正确的路径', '错误', wx.OK | wx.ICON_ERROR)
dlg.ShowModal()
dlg.Destroy()
class AutoWidthListCtrl(wx.ListCtrl, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin):
def __init__(self, parent, *args, **kw):
# 设置歌曲列表list ctrl的列随sizer的改变而自动改变宽度
wx.ListCtrl.__init__(self, parent, *args, **kw)
wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self)
class MainFrame(wx.Frame):
def __init__(self):
self.setting_dic = get_setting()
wx.Frame.__init__(self, None, -1, self.setting_dic['app_title'], pos=(50, 50))
self.SetSize(1280, 720)
self.SetSizeHints((1080, 600)) # 设置页面最小大小
self.bg_color = self.setting_dic['bg_color']
self.font_size = self.setting_dic['font_size']
self.SetBackgroundColour(self.bg_color)
self.play_png = wx.Image("icon/tiny-start.png", wx.BITMAP_TYPE_PNG).\
Rescale(30, 30).ConvertToBitmap()
self.stop_png = wx.Image("icon/tiny-pause.png", wx.BITMAP_TYPE_PNG).\
Rescale(30, 30).ConvertToBitmap()
# 字体设置
self.user_font = wx.Font(pointSize=self.font_size, family=wx.FONTFAMILY_DEFAULT,
style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_NORMAL)
self.user_font.SetFaceName(self.setting_dic['font'])
self.user_font.SetUnderlined(self.setting_dic['underline'])
# 绑定窗口关闭事件
self.is_running = True
self.Bind(wx.EVT_CLOSE, self.close_window)
# 从设置中读取歌曲路径并读取歌曲
folder_type = self.setting_dic['music_folder_type']
self.local_music_folder = self.setting_dic['music_folder_list'][folder_type]
self.local_music_name_list = []
self.get_local_music_list()
self.lyrics_static_text_list = []
# 音乐播放初始化
mixer.init()
self.volume = self.setting_dic['volume']
self.music = mixer.music
self.music.set_volume(self.volume)
self.current_music_state = 0 # 0表示未播放1表示播放中2表示暂停中
self.current_music_index = 0
# base_sizer将界面分为左右两部分左侧为导航栏sizer右侧初始为主界面
self.base_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.unsaved_setting = self.setting_dic.copy()
navi_sizer = self.draw_navigator()
self.base_sizer.Add(navi_sizer, 0, wx.EXPAND)
self.main_interface_panel = None
self.music_list_ctrl = None
self.lyrics_panel = None
self.setting_panel = None
self.ranking_notebook = None
self.draw_main_interface()
self.SetSizer(self.base_sizer)
self.Show()
def draw_navigator(self):
"""
绘制导航栏按钮切换界面
:return: 导航栏sizer
"""
navi_sizer = wx.BoxSizer(wx.VERTICAL)
local_music_button = wx.Button(self, label="本地音乐", size=(200, 100))
navi_sizer.Add(local_music_button)
local_music_button.SetFont(self.user_font)
local_music_button.SetForegroundColour(self.setting_dic['text_color'])
local_music_button.SetBackgroundColour(self.bg_color)
local_music_button.Bind(wx.EVT_LEFT_DOWN, self.main_interface_button_evt)
navi_sizer.Add((0, 0), 1)
ranking_button = wx.Button(self, label="排行榜", size=(200, 100))
navi_sizer.Add(ranking_button)
ranking_button.SetFont(self.user_font)
ranking_button.SetForegroundColour(self.setting_dic['text_color'])
ranking_button.SetBackgroundColour(self.bg_color)
ranking_button.Bind(wx.EVT_LEFT_DOWN, self.ranking_button_evt)
navi_sizer.Add((0, 0), 1)
setting_button = wx.Button(self, label="设置", size=(200, 100))
navi_sizer.Add(setting_button)
setting_button.SetFont(self.user_font)
setting_button.SetForegroundColour(self.setting_dic['text_color'])
setting_button.SetBackgroundColour(self.bg_color)
setting_button.Bind(wx.EVT_LEFT_DOWN, self.setting_button_evt)
navi_sizer.Add((0, 0), 3)
return navi_sizer
def main_interface_button_evt(self, evt):
"""
当设置界面或排行榜界面存在时将其隐藏在主界面未显示时显示主界面
"""
if self.setting_panel and self.setting_panel.IsShown():
self.setting_panel.Hide()
if self.ranking_notebook and self.ranking_notebook.IsShown():
self.ranking_notebook.Hide()
if not self.main_interface_panel.IsShown():
self.main_interface_panel.Show()
def draw_main_interface(self):
"""
绘制主界面
"""
# 主界面panel设置了main_interface_sizermain_interface_sizer将主界面分为上下两部分下面部分为控制区域
# 高度始终不变上面部分为sizer1大小可变包含歌曲列表及歌词面板部分。
self.main_interface_panel = wx.Panel(self)
self.base_sizer.Add(self.main_interface_panel, 1, wx.EXPAND)
main_interface_sizer = wx.BoxSizer(wx.VERTICAL)
self.main_interface_panel.SetSizer(main_interface_sizer)
sizer1 = wx.BoxSizer(wx.HORIZONTAL)
main_interface_sizer.Add(sizer1, 1, wx.EXPAND)
self.get_local_music_list()
# 歌曲列表list ctrl----------------------------------------------------------------------------------------------
self.music_list_ctrl = AutoWidthListCtrl(self.main_interface_panel, -1,
style=wx.LC_REPORT
# | wx.LC_NO_HEADER
)
self.music_list_ctrl.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.list_ctrl_evt)
self.music_list_ctrl.SetBackgroundColour(self.bg_color)
self.music_list_ctrl.SetFont(self.user_font)
self.music_list_ctrl.SetForegroundColour(self.setting_dic['text_color'])
# TODO: header背景颜色无法设置若使用no header的风格则list ctrl宽度有问题
self.music_list_ctrl.InsertColumn(0, '歌曲列表')
self.music_list_ctrl.SetHeaderAttr(wx.ItemAttr(self.setting_dic['text_color'], None, self.user_font))
sizer1.Add(self.music_list_ctrl, 2, wx.EXPAND)
for music in self.local_music_name_list:
music_full_name = music.replace(".mp3", "") # 删除后缀
self.music_list_ctrl.InsertItem(0, music_full_name, 0)
# 歌词面板-------------------------------------------------------------------------------------------------------
self.lyrics_panel = wx.Panel(self.main_interface_panel)
sizer1.Add(self.lyrics_panel, 9, wx.EXPAND)
# 控制区域-------------------------------------------------------------------------------------------------------
control_panel = wx.Panel(self.main_interface_panel)
control_sizer = wx.BoxSizer(wx.HORIZONTAL)
control_panel.SetSizer(control_sizer)
control_panel.Fit()
main_interface_sizer.Add(control_panel, 0, wx.EXPAND)
# 上一首按钮
last_music_png = wx.Image("icon/media-skip-backward.png", wx.BITMAP_TYPE_PNG).ConvertToBitmap()
next_music_png = wx.Image("icon/media-skip-forward.png", wx.BITMAP_TYPE_PNG).ConvertToBitmap()
last_button = wx.BitmapButton(control_panel, -1, last_music_png, size=(30, 30))
last_button.SetToolTip('上一首音乐')
last_button.Bind(wx.EVT_LEFT_DOWN, self.play_last_music)
# 播放暂停按钮
if self.current_music_state == 1:
current_png = self.stop_png
else:
current_png = self.play_png
self.play_stop_button = wx.BitmapButton(control_panel, -1, current_png, size=(30, 30))
self.play_stop_button.SetToolTip('播放/暂停音乐')
self.play_stop_button.Bind(wx.EVT_LEFT_DOWN, self.play_stop_music)
# 下一首按钮
next_button = wx.BitmapButton(control_panel, -1, next_music_png, size=(30, 30))
next_button.SetToolTip('下一首音乐')
next_button.Bind(wx.EVT_LEFT_DOWN, self.play_next_music)
control_sizer.Add(last_button, 0, wx.EXPAND | wx.ALL, border=10)
control_sizer.Add(self.play_stop_button, 0, wx.EXPAND | wx.ALL, border=10)
control_sizer.Add(next_button, 0, wx.EXPAND | wx.ALL, border=10)
# 正在播放的歌曲名
self.current_music_static_text = wx.StaticText(control_panel, -1, '未选择')
self.current_music_static_text.SetFont(self.user_font)
self.current_music_static_text.SetForegroundColour(self.setting_dic['text_color'])
control_sizer.Add(self.current_music_static_text, 0, wx.ALIGN_CENTER_VERTICAL, border=10)
control_sizer.Add(0, 1, wx.EXPAND) # 增加空白
# 音量滑动条及当前音量大小文字
volume_slider = wx.Slider(control_panel, -1, int(self.volume * 100), 0, 100)
volume_slider.Bind(wx.EVT_SLIDER, self.change_volume)
self.volume_text = wx.StaticText(control_panel, -1, f'音量{int(self.volume * 100)}%')
self.volume_text.SetFont(self.user_font)
self.volume_text.SetForegroundColour(self.setting_dic['text_color'])
control_sizer.Add(self.volume_text, 0, wx.ALIGN_CENTER_VERTICAL, border=10)
control_sizer.Add(volume_slider, 0, wx.EXPAND | wx.ALL, border=10)
self.base_sizer.Layout()
def get_local_music_list(self):
"""
获取歌词列表
"""
self.local_music_name_list.clear()
for local_music_file_name in os.listdir(self.local_music_folder):
if local_music_file_name.endswith(".mp3"):
self.local_music_name_list.append(local_music_file_name)
def list_ctrl_evt(self, evt):
"""
在歌曲列表双击时播放歌曲
"""
self.current_music_name = evt.GetItem().GetText()
self.play_music()
def play_stop_music(self, evt):
"""
播放暂停按钮事件
"""
if 1 == self.current_music_state:
self.music.pause()
self.current_music_state = 2
self.play_stop_button.SetBitmap(self.play_png)
elif 2 == self.current_music_state:
self.music.unpause()
self.current_music_state = 1
self.play_stop_button.SetBitmap(self.stop_png)
elif 0 == self.current_music_state:
self.current_music_name = self.local_music_name_list[0].replace('.mp3', '')
self.play_music()
def play_music(self):
"""
播放歌曲
"""
self.lyrics_panel.DestroyChildren()
full_path = os.path.join(self.local_music_folder, self.current_music_name) + '.mp3'
self.music.load(full_path)
self.music.play()
self.current_music_state = 1
self.current_music_index = self.local_music_name_list.index(self.current_music_name + '.mp3')
self.play_stop_button.SetBitmap(self.stop_png)
lyric_path = os.path.join(self.local_music_folder, self.current_music_name) + '.lrc'
self.lyrics_list = get_lyrics(lyric_path)
self.draw_music_lyrics_panel()
# 歌词刷新线程
lyric_refresh_thread = threading.Thread(target=self.refresh_lyrics)
lyric_refresh_thread.start()
# 当前播放音乐名(控制区域)
current_music_name = self.local_music_name_list[self.current_music_index].replace(".mp3", "")
max_music_name_len = self.setting_dic['max_music_name_len']
if len(current_music_name) > max_music_name_len:
current_music_name = self.current_music_name[0:max_music_name_len] + "..."
self.current_music_static_text.SetLabelText('正在播放:' + current_music_name)
self.current_music_static_text.SetToolTip(self.current_music_name)
def draw_music_lyrics_panel(self):
"""
绘制歌词面板
"""
# 清空歌词static text列表
self.lyrics_static_text_list.clear()
length, width = self.lyrics_panel.GetSize()
total_lyrics_row = int(1080 / 30) # 页面最多歌词行数仅支持1080p及以下屏幕)
max_lyrics_row = int(width / 30)
for lyric_index in range(total_lyrics_row):
if lyric_index < max_lyrics_row and lyric_index < len(self.lyrics_list):
time_lyric_tuple = self.lyrics_list[lyric_index]
else:
time_lyric_tuple = (-1, '') # 时间为-1表示空行
lyric_row = wx.StaticText(self.lyrics_panel, -1, time_lyric_tuple[1],
pos=(int((length - 400) * 0.8 / 2), 30 * lyric_index + 10),
size=(400, 30), style=wx.ALIGN_CENTER_HORIZONTAL)
lyric_row.SetForegroundColour(self.setting_dic['lyrics_bg_color'])
lyric_row.SetFont(self.user_font)
self.lyrics_static_text_list.append((time_lyric_tuple[0], lyric_row))
def refresh_lyrics(self):
"""
刷新歌词线程
"""
music_index = self.current_music_index
length, width = self.lyrics_panel.GetSize()
# 当前显示的歌词行数
current_shown_lyrics_num = len(list(filter(lambda x: x != '',
(i[1].GetLabel() for i in self.lyrics_static_text_list))))
current_start_lyrics_index = 0
while music_index == self.current_music_index and self.is_running:
# 刷新歌词颜色
lyrics_fg_color = self.setting_dic['lyrics_fg_color']
for lyric_time, lyric_row in self.lyrics_static_text_list:
# 当歌词时间大于0-1为空行小于当前时间且颜色不为前景色时将其设置为前景色
if 0 <= lyric_time <= self.music.get_pos()/1000 and \
lyric_row.ForegroundColour != wx.Colour(lyrics_fg_color):
lyric_row.SetForegroundColour(lyrics_fg_color)
lyric_row.Refresh()
# 刷新歌词位置
if length != self.lyrics_panel.GetSize()[0]:
length = self.lyrics_panel.GetSize()[0]
for ind, (_, row) in enumerate(self.lyrics_static_text_list):
row.SetPosition((int((length - 400) * 0.8 / 2), 30 * ind + 10))
# 刷新歌词行数
if width != self.lyrics_panel.GetSize()[1]:
width = self.lyrics_panel.GetSize()[1]
max_lyrics_row = int(width / 30)
# 可显示行数小于已显示行数,将多余行设为空行
if max_lyrics_row < current_shown_lyrics_num:
for ind, (_, lyric_row) in enumerate(self.lyrics_static_text_list[max_lyrics_row:]):
lyric_row.SetLabel('')
self.lyrics_static_text_list[max_lyrics_row + ind] = (-1, lyric_row)
# 可显示行数大于已显示行数,显示不足行
else:
lst = self.lyrics_static_text_list[current_shown_lyrics_num:max_lyrics_row] # 需增加的歌词
for ind, (_, lyric_row) in enumerate(lst):
start_index = current_start_lyrics_index + current_shown_lyrics_num # 需增加的歌词在歌词列表中的起始位置
if start_index + ind < len(self.lyrics_list):
lyric_time, lyric_text = self.lyrics_list[start_index + ind]
lyric_row.SetLabel(lyric_text)
# 当设置完歌曲最后一行歌词后将其余行设置为空
else:
lyric_time = -1
lyric_row.SetLabel('')
self.lyrics_static_text_list[current_shown_lyrics_num+ind] = (lyric_time, lyric_row)
current_shown_lyrics_num = max_lyrics_row # 当前显示的歌词行数
# 歌词到底后刷新下一页
new_page_index = int(width / 30) + current_start_lyrics_index # 下一页起始索引
# 当下一页起始歌词索引小于歌词总行数且离下一页第一句歌词时间小于0.5s时绘制下一页
if new_page_index < len(self.lyrics_list) and \
abs(self.lyrics_list[new_page_index][0] - self.music.get_pos()/1000) < 0.5:
current_start_lyrics_index = new_page_index
max_lyrics_row = int(width / 30)
for ind, (_, lyric_row) in enumerate(self.lyrics_static_text_list):
lyric_row.SetForegroundColour(self.setting_dic['lyrics_bg_color'])
if ind < max_lyrics_row and new_page_index + ind < len(self.lyrics_list):
lyric_time, lyric_text = self.lyrics_list[new_page_index + ind]
else:
lyric_time, lyric_text = (-1, '')
lyric_row.SetLabel(lyric_text)
self.lyrics_static_text_list[ind] = (lyric_time, lyric_row)
time.sleep(0.5)
def play_last_music(self, evt):
"""
上一首按钮事件
"""
if self.current_music_state == 0: # 未开始播放时按钮无效
return
elif self.current_music_index > 1:
self.current_music_index -= 1
else:
self.current_music_index = len(self.local_music_name_list) - 1
self.current_music_name = self.local_music_name_list[self.current_music_index].replace('.mp3', '')
self.play_music()
def play_next_music(self, evt):
"""
下一首按钮事件
"""
if self.current_music_state == 0: # 未开始播放时按钮无效
return
elif self.current_music_index < len(self.local_music_name_list) - 1:
self.current_music_index += 1
else:
self.current_music_index = 0
self.current_music_name = self.local_music_name_list[self.current_music_index].replace('.mp3', '')
self.play_music()
def change_volume(self, evt):
"""
音量滑动条事件
"""
obj = evt.GetEventObject()
val = obj.GetValue()
self.volume = float(val / 100)
self.music.set_volume(self.volume)
self.volume_text.SetLabel(f'音量{int(self.volume * 100)}%')
self.unsaved_setting['volume'] = self.volume
def setting_button_evt(self, evt):
"""
当主界面或排行榜界面显示时将其隐藏在设置界面不存在时绘制界面存在时显示设置界面
"""
if self.main_interface_panel.IsShown():
self.main_interface_panel.Hide()
if self.ranking_notebook and self.ranking_notebook.IsShown():
self.ranking_notebook.Hide()
if not self.setting_panel:
self.draw_setting_interface()
if not self.setting_panel.IsShown():
self.setting_panel.Show()
def ranking_button_evt(self, evt):
"""
当主界面或设置界面显示时将其隐藏在排行榜界面不存在时绘制界面存在时显示排行榜界面
"""
if self.main_interface_panel.IsShown():
self.main_interface_panel.Hide()
if self.setting_panel and self.setting_panel.IsShown():
self.setting_panel.Hide()
if not self.ranking_notebook:
self.draw_ranking_notebook()
if not self.ranking_notebook.IsShown():
self.ranking_notebook.Show()
def draw_setting_interface(self):
"""
绘制设置界面
"""
self.setting_panel = SettingPanel(self)
self.base_sizer.Add(self.setting_panel, 1, wx.EXPAND)
self.base_sizer.Layout()
def draw_ranking_notebook(self):
"""
绘制排行榜界面
"""
self.ranking_notebook = RankListNotebook(self)
self.base_sizer.Add(self.ranking_notebook, 1, wx.EXPAND)
self.base_sizer.Layout()
def close_window(self, evt):
"""
关闭界面事件
"""
self.is_running = False # 刷新歌词线程终止的条件之一
# 退出时保存设置
with open('setting.txt', 'w', encoding='utf-8') as f:
f.writelines(str(self.unsaved_setting))
evt.Skip() # 继续关闭事件
if __name__ == "__main__":
app = wx.App()
frame = MainFrame()
app.MainLoop()
Loading…
Cancel
Save