我对编码很陌生。我在车里有一个随机的想法来制作这个应用程序,以便首先深入学习代码。
这是一个应用程序,获取一个MP3文件,将其“分解”成块(临时创建1秒mp3文件),然后随机化这些片段,然后以随机顺序编译一个包含所有“碎片”的新MP3文件。为了完成这个任务,我使用了我有限的知识,所以我的代码可能写得不好。
关于我的代码你有什么可以改变的吗?任何批评都会有帮助。我在大约10个小时内学会并组装了这些东西。(我真的很享受!)
我的未来目标是让这个功能可以通过网络访问,这样我的朋友们就可以不用下载可执行代码就可以使用它。
from pydub import AudioSegment
from mutagen.mp3 import MP3
import random
import os
from glob import glob
from string import ascii_lowercase
from random import choice, randint, random
import sys
import time
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5 import QtCore
from PyQt5 import QtWidgets
class shredder(QWidget):
def __init__(self, parent = None):
super(shredder, self).__init__(parent)
self.setMinimumSize(350, 100)
#self.setFixedWidth(350)
#self.setFixedHeight(100)
layout = QVBoxLayout()
self.btn = QPushButton("Load MP3")
self.btn.clicked.connect(self.getfile)
layout.addWidget(self.btn)
self.le = QLabel("File Location:")
self.le.setAlignment(QtCore.Qt.AlignCenter)
layout.addWidget(self.le)
self.btn1 = QPushButton("Shred")
self.btn1.clicked.connect(self.shred)
layout.addWidget(self.btn1)
self.setLayout(layout)
self.setWindowTitle("MP3Shredder")
def getfile(self):
global filepath
filepath, _filter = QFileDialog.getOpenFileName(self, 'Open MP3 File',
'',"MP3 (*.mp3)")
if filepath:
self.le.setText("File Location: "+filepath)
def shred(self):
global filepath
try:
filepath
except NameError:
self.le.setText("You Must First Load A MP3 File To Shred!")
else:
#importing file with pydub from location by giving its path
song = AudioSegment.from_mp3(filepath)
#creates an MP3 object with mutagen from file location and saves it as variable
audio = MP3(filepath)
audio_info = audio.info
length = int(audio_info.length)
hours, mins, seconds = audio_duration(length)
print('Total Duration Of Song: {}:{}:{}'.format(hours, mins, seconds))
print("In Seconds: " + str(audio.info.length))
print("Creating " + str((audio.info.length//1)+1) +" shreds.")
os.makedirs(os.path.join(r"C:\mp3shredder"), exist_ok=True)
os.makedirs(os.path.join(r"C:\mp3shredder\temp"), exist_ok=True)
print("Shredding...")
#split sound in 1-second slices and export
for i, chunk in enumerate(song[::1000]):
with open(r"C:\mp3shredder\temp\piece%s.mp3" % i, "wb") as f:
chunk.export(f, format="mp3")
path = (r"C:\mp3shredder\temp")
files = os.listdir(path)
for index, file in enumerate(files):
os.rename(os.path.join(path, file), os.path.join(path, ''.join([choice(ascii_lowercase) for _ in range(randint(5, 8))])+".mp3"))
print("Shredding Complete!")
print("Gluing...")
shredded_pieces = [AudioSegment.from_mp3(mp3_file) for mp3_file in glob(r"C:\mp3shredder\temp\*.mp3")]
glue_it_back = AudioSegment.empty()
for song in shredded_pieces:
glue_it_back += song
filedir = os.path.dirname(filepath)
filename = os.path.basename(filepath)
glue_it_back.export(filedir + "/" + "SHREDDED_" + filename, format="mp3")
dir = (r"C:\mp3shredder\temp")
for f in os.listdir(dir):
os.remove(os.path.join(dir, f))
os.rmdir(dir)
print("Gluing Complete!")
print("Created: " + filedir + "/" + "SHREDDED_" + filename)
self.le.setText("Created: " + filedir + "/" + "SHREDDED_" + filename)
os.startfile(filedir)
# function to convert the information into
# some readable format
def audio_duration(length):
hours = length // 3600 # calculate in hours
length %= 3600
mins = length // 60 # calculate in minutes
length %= 60
seconds = length # calculate in seconds
return hours, mins, seconds # returns the duration
def main():
app = QApplication(sys.argv)
ex = shredder()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()发布于 2022-08-16 10:37:51
filepath将被设置为""。标签不会更改,但是如果用户按下“碎片”按钮,我们将尝试导入一个名为""的文件(因为存在filepath变量,所以我们没有得到该NameError),该文件可能不会运行得很好glued_song = AudioSegment.empty()
for chunk in random.sample(song[::1000], song.duration_seconds):
glued_song += chunktempfile模块以更安全和可移植的方式在标准位置创建它。mutagen来获得原始轨道的持续时间。由于pydub支持获得AudioSegments的持续时间(使用song.duration_seconds),因此只为了这个目的而承担整个其他依赖关系似乎是不必要的。shredder类的属性可能比全局变量更整洁--您可以在__init__中像self.filepath = None一样设置它,然后在getfile和shred中以self.filepath的形式访问它。shredder作为类名,通常是像Shredder那样编写的。btn和btn1很少告诉我这些按钮是什么。例如,load_button和shred_button将更具描述性。le是什么意思。我可能会选择类似path_label或selection_label之类的东西getfile是两个词-- get_file。或者可能是select_file,或者pick_file,以更清楚地表示正在做出选择。song在相同的方法中被用于不同的目的,这可能会引起混淆。glue_it_back听起来不像一件事,而更像你做的事情--它是函数的好名字,但对于一个对象,我更喜欢glued_song,甚至是result发布于 2022-08-16 12:42:58
在总体架构和风格以及您使用的逻辑方面,都有很大的改进空间:
分离
只有一个类处理逻辑(“分解”音频文件)和表示( PyQt小部件)。这使得测试变得更加困难,因为如果不构建GUI,就无法从测试运行程序调用shred,并且查看比应该更痛苦的内容:我不愿意仅仅为了检查而安装一个250+ MiB包(在本例中是PyQt5)。
在您的示例中,shred可以是一个独立的方法,以一个AudioFragment作为参数并返回另一个AudioFragment。
此外,在处理音频文件时,您会将大量内容打印到控制台,不仅要混合逻辑和表示,还要混合多种形式的表示-- GUI和控制台。我知道它在开发和调试时可能很有用,但是您应该考虑使用记录器。
filepath使用全局变量。全球通常被认为是不好的做法,虽然他们可以有合法的用途,但这不是一个。它目前可能有效,但这是一种“脚枪”:您可以从代码中的任何其他地方更改文件路径,包括GUI类之外的文件路径,并使GUI和内部状态不同步,从而为用户带来问题。考虑到泛型变量名,这一点尤其正确。
在这种情况下,可以使用实例参数(self.filepath)跟踪路径,也可以将其作为参数传递给方法。
使用有意义的变量名。您通常是这样做的,但是UI小部件的名称不包含任何信息,例如,请参见btn和btn1。load_file_button和shred_button将是描述性更强的名称。
想象一下,在稍微复杂一点的UI上工作,您可能会丢失小部件在当前命名过程中快速完成的任务。
您的代码不包含任何文档。Docstring对于理解程序所做的工作非常有用,特别是对于shred方法,该方法对您的程序非常独特。
如果可以避免,
在您的情况下,没有理由将歌曲块写入磁盘。相反,您可以将歌曲分割成块,并将它们放入数组中,用random.shuffle对它们进行洗牌,并将它们拼接到一个新的AudioFragment中。
如果用户在一个不幸名为C:\mp3shedder\temp的目录中有一些个人数据,该怎么办?在运行你的程序时它会被核爆。
如果您确实必须将其写入磁盘并从磁盘中删除(在您的情况下,您不需要这样做),那么您可能应该更加小心您的操作方式。
绝对路径上的
您使用硬编码的绝对路径,这更可能在读取、写入和删除磁盘时带来副作用。
发布于 2022-08-16 15:13:40
首先,对于初学者来说,这是一个非常有趣的项目,做得很好。其他人提到了很多关于我们的代码风格的问题,但是要在以下几点上进行扩展:
佩普-8是Python的标准样式推荐。其中包括空行数、缩进数、变量命名建议和大小写等。
有一些指针(Flake8、皮林特和其他)可以根据PEP-8检查您的代码并发出有用的警告。在你的例子中,有几件简单而明显的事情。
PEP-8推荐缩进的4个空格的倍数,顶层函数/类之间的2空行,内部函数之间的1空行。
操作符被空格包围。
x = a + b关键字参数不被空格包围。
def func(a, b=None):逗号后面跟着空格。
('A', 'B', 'C')导入random两次,一次作为普通库
import random以及random中的特定元素
from random import choice, randint, random这实际上会导致random、库和random.random函数之间的名称冲突。显然不太好。
将Python标准库导入放在一般库之前也是很好的实践。
导入*通常被认为是不好的做法,因为它可能会污染您的命名空间,从而使您调用函数,这是您没有预料到的。
想象一下你这样做的情况
from numpy import *
from random import *numpy和random都提供了一个名为random的函数,这意味着当您调用random()时可能不知道正在调用的是哪个
相反,将库导入命名空间或只导入您想要使用的内容(指针将告诉您未使用的导入)。就您的情况而言,您要导入:
在您的代码中没有任何一个显式使用。
PEP-8建议将UPPER_SNAKE用于顶级/全局变量,PascalCase用于类,lower_snake用于变量和函数。
shredder,filepath和其他人不符合这一点。
您有一个名为shredder的对象,它不仅仅是一个分解器,它是执行分解的GUI。我们应该将它分开,并允许它与可能包含Shredder的GUI分开存在。
您还应该有一些函数,它们被命名来表示它们所做的事情。例如,audio_duration实际上并不关心audio,它只是将一个时间(以秒为单位)转换为小时、分钟、秒。您可以这样做两件事之一,要么将函数重命名为split_time,或者使用类似的名称来描述它实际做的事情(用docstring解释它所做的事情),或者让它按您说的那样做。
def audio_duration(audio: MP3):
"Gets the duration of an audio track in hours, minutes and seconds"
length = audio.info.length
hours = length // 3600 # calculate in hours
length %= 3600
mins = length // 60 # calculate in minutes
length %= 60
seconds = length # calculate in seconds
return hours, mins, seconds # returns the duration(注:存在一个divmod函数来执行此操作)
注释和文档字符串是python中帮助其他人理解正在发生的事情的有用工具,然而,好的注释并不容易编写。您的一些评论只描述了代码已经告诉我的内容:
hours = length // 3600 # calculate in hours诚实地说,这是相当不言自明的,但是说它“以小时计算”的评论是因为我们将它分配给一个名为时数的变量。评论没有告诉我的是我们是从什么算出来的。一个更好的评论(人们会不同意,这都是关于品味的问题)可能是解释你实际上在做什么计算。
hours = length // 3600 # Seconds to hours同样,docstring是其他人使用您的软件的主要工具。你打过字吗
>>> import random
>>> help(random.randint)help返回docstring,并告诉您函数应该做什么、需要什么参数、返回什么以及在某些情况下如何达到结果。良好的文档字符串在代码中也很有用,可以提醒自己函数正在做什么。同样,一个好的docstring很难编写,但是像sphinx这样的工具有一些标准格式,可以帮助指导您如何编写一个好的,以及能够自动为您的代码生成API文档(而不必手工编写)。一个简单的docstring可能看起来像
def audio_duration(length):
"""Converts duration in seconds to hours, minutes, seconds
:param length: duration in seconds
:returns: duration in hours, minutes, seconds
:rtype: 3-tuple of ints
"""虽然类型提示在Python中是可选的,但它们只是工具箱中的另一个工具,可以帮助解释您的代码正在做什么,并且常常有助于您自己和其他人查看您的代码正在做什么。
您正在使用旧的字符串格式(%格式和.formats)的混合样式,现代风格是使用f-字符串来明确代码中正在发生的事情。
print('Total Duration Of Song: {}:{}:{}'.format(hours, mins, seconds))
print("In Seconds: " + str(audio.info.length))
print("Creating " + str((audio.info.length//1)+1) + " shreds.")
with open(r"C:\mp3shredder\temp\piece%s.mp3" % i, "wb") as f:成为
print(f'Total Duration Of Song: {hours}:{mins}:{seconds}')
print(f"In Seconds: {audio.info.length}")
print(f"Creating {audio.info.length//1)+1} shreds.")
with open(rf"C:\mp3shredder\temp\piece{i}.mp3", "wb") as f:避免使用全局值,它们会导致许多问题,特别是在较大的代码中。如果您使用了一个具有公共名称的全局(如filepath ),而其他人也在他们的库中,会发生什么情况呢?谁知道会发生什么?到处都是问题。
相反,只要把事情保持在可行的地方范围内就行了。将参数传递到函数中并在本地使用。那么,对该参数的更改不会影响全局状态(情况并不总是如此,请注意您更改了虚拟参数),它可以从任何名称传入。假设我有100个想要shred的文件:
global filename
for my_file in files_list:
filename = my_file
Shredder.shred()或
for my_file in filelist:
Shredder.shred(my_file)这就使得意图更明显了?
我认为这在语法和结构方面已经足够了。
https://codereview.stackexchange.com/questions/278877
复制相似问题