代码拉取完成,页面将自动刷新
同步操作将从 Nagisa/LrcMusicPlayer 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
import os
import re
import pykakasi
from mutagen.flac import FLAC
from mutagen.mp3 import MP3
from mutagen.dsdiff import DSDIFF
from mutagen.dsf import DSF
from mutagen.wave import WAVE
from mutagen.monkeysaudio import MonkeysAudio
from mutagen.mp4 import MP4
from mutagen import File
class Song:
# 格式限定
SONG_FORMAT_LIMIT = ['flac', 'mp3', 'wav', 'wave', 'dsf', 'dff', 'dsdiff', 'ape', 'm4a']
def __init__(self, path):
self.path: str = path
# Tag信息
self.title = '暂无'
self.artist = '暂无'
self.album = '暂无'
self.date = '暂无'
self.genre = '暂无'
self.lyrics = ''
self.cover = b''
# 音频信息
self.sample_rate = 0 # 采样率
self.bits_per_sample = 0 # 位深
self.bitrate = 0 # 比特率
self.channels = 0 # 声道
self.length = 0 # 持续时间
self.audio_type = '' # 音频类型
self.is_hr = False # 是否达到Hi-Res
try:
self.load_metadata()
except Exception as e:
print(e)
def load_metadata(self):
"""读取歌曲元数据"""
if not self.path:
return
# 获取文件后缀
filetype = self.path.split('.')[-1].lower()
if filetype == 'flac':
audio = FLAC(self.path)
self.audio_type = 'FLAC'
self.parse_audio_info(audio.info)
self.parse_flac_tag(audio)
elif filetype == 'mp3':
self.audio_type = 'MP3'
audio = MP3(self.path)
self.parse_audio_info(audio.info)
self.parse_id3_tag(audio)
elif filetype in ['wav', 'wave']:
self.audio_type = 'WAV'
audio = WAVE(self.path)
self.parse_audio_info(audio.info)
self.parse_id3_tag(audio)
elif filetype == 'dsf':
self.audio_type = 'DSF'
audio = DSF(self.path)
self.parse_audio_info(audio.info)
self.parse_id3_tag(audio)
elif filetype in ['dsdiff', 'dff']:
self.audio_type = 'DFF'
audio = DSDIFF(self.path)
self.parse_audio_info(audio.info)
self.parse_id3_tag(audio)
elif filetype in ['ape']:
self.audio_type = 'APE'
audio = MonkeysAudio(self.path)
self.parse_audio_info(audio.info)
self.parse_ape_tag(audio)
elif filetype in ['m4a']:
self.audio_type = 'AAC'
audio = MP4(self.path)
self.parse_audio_info(audio.info)
self.parse_mp4_tag(audio)
else:
self.audio_type = 'UNKNOWN'
audio = File(self.path)
self.parse_audio_info(audio.info)
self.parse_id3_tag(audio)
if self.lyrics == '' and os.path.exists(os.path.splitext(self.path)[0] + '.lrc'):
with open(os.path.splitext(self.path)[0] + '.lrc', "r", encoding='utf-8', errors='ignore') as f: # 打开文件
self.lyrics = f.read() # 读取本地歌词
def get_lrc_dict(self):
"""根据时间戳将歌词转为字典"""
if not self.lyrics:
return {}
lrc_list = self.lyrics.splitlines()
# 时间戳正则
func = re.compile("\\[.*?]") # 为符合PEP8规范,反斜杠双写,实际使用一个也可以
# 根据时间戳,转换成字典
lrc_dict = {}
for item in lrc_list:
searched = func.search(item)
if not searched:
continue
lrc_time = searched.group()
time_str_list = lrc_time[1:-1].split(":")
if not time_str_list[0].isdigit():
continue
lrc_time_int = int(time_str_list[0]) * 60000 + int(float(time_str_list[1]) * 1000)
lrc_text = func.sub('', item)
lrc_text = ' '.join(lrc_text.split()) # 去除多余空格
if lrc_dict.get(lrc_time_int):
lrc_dict[lrc_time_int].append(lrc_text)
else:
lrc_dict[lrc_time_int] = [lrc_text]
return lrc_dict
def parse_id3_tag(self, audio):
"""解析 ID3 Tag"""
for item in audio:
# 部分可能携带信息的FrameID
if 'APIC' in item:
self.cover = audio.get(item).data
if 'USLT' in item:
self.lyrics = str(audio.get(item))
if audio.get('TIT2'):
self.title = str(audio.get('TIT2'))
if audio.get('TPE1'):
self.artist = str(audio.get('TPE1'))
if audio.get('TALB'):
self.album = str(audio.get('TALB'))
if audio.get('TDRC'):
self.date = str(audio.get('TDRC'))
if audio.get('TCON'):
self.genre = str(audio.get('TCON'))
def parse_flac_tag(self, audio):
"""解析 FLAC Tag"""
if not audio.get('title') is None:
self.title = audio.get('title')[0]
if not audio.get('artist') is None:
self.artist = audio.get('artist')[0]
if not audio.get('album') is None:
self.album = audio.get('album')[0]
if not audio.get('date') is None:
self.date = audio.get('date')[0]
if not audio.get('genre') is None:
self.genre = audio.get('genre')[0]
if not audio.get('lyrics') is None:
self.lyrics = audio.get('lyrics')[0]
if audio.pictures:
self.cover = audio.pictures[0].data
def parse_mp4_tag(self, audio):
"""解析 MP4 Tag"""
if not audio.get('©nam') is None:
self.title = audio.get('©nam')[0]
if not audio.get('©ART') is None:
self.artist = audio.get('©ART')[0]
if not audio.get('©alb') is None:
self.album = audio.get('©alb')[0]
if not audio.get('©day') is None:
self.date = audio.get('©day')[0]
if not audio.get('©gen') is None:
self.genre = audio.get('©gen')[0]
if not audio.get('©lyr') is None:
self.lyrics = audio.get('©lyr')[0]
if not audio.get('covr') is None:
self.cover = audio.get('covr')[0]
def parse_ape_tag(self, audio):
"""解析 APEv2 Tag"""
if audio.get('COVER ART (FRONT)'):
b_value = audio.get('COVER ART (FRONT)')
if b'\0' in b_value.value:
self.cover = b_value.value.split(b'\0', 1)[-1]
if audio.get('TITLE'):
self.title = str(audio.get('TITLE'))
if audio.get('ARTIST'):
self.artist = str(audio.get('ARTIST'))
if audio.get('ALBUM'):
self.album = str(audio.get('ALBUM'))
if audio.get('YEAR'):
self.date = str(audio.get('YEAR'))
if audio.get('GENRE'):
self.genre = str(audio.get('GENRE'))
for item in audio:
if 'LYRICS' in item:
self.lyrics = str(audio.get(item))
def parse_audio_info(self, audio_info):
"""解析音频信息"""
self.sample_rate = audio_info.sample_rate
self.channels = audio_info.channels
self.length = audio_info.length
if self.audio_type != 'APE':
self.bitrate = audio_info.bitrate
if self.audio_type != 'MP3':
self.bits_per_sample = audio_info.bits_per_sample
if self.audio_type in ['DSF', 'DFF'] and self.bits_per_sample == 1:
self.is_hr = True
elif self.bits_per_sample > 16 and self.sample_rate > 44100:
self.is_hr = True
def __str__(self):
"""重写打印信息"""
if self.audio_type == 'MP3':
return f'采样率:{self.sample_rate}, 比特率:{self.bitrate}, ' \
f'声道:{self.channels}, 持续时间:{self.length},文件类型:{self.audio_type},Hi-Res:{self.is_hr}'
else:
return f'采样率:{self.sample_rate}, 位深:{self.bits_per_sample}, 比特率:{self.bitrate}, ' \
f'声道:{self.channels}, 持续时间:{self.length},文件类型:{self.audio_type},Hi-Res:{self.is_hr}'
@staticmethod
def get_romaji(text):
"""获取罗马音"""
def get_all_str(t):
kks = pykakasi.kakasi()
t = ' '.join(t) # 插入空格
result = kks.convert(t)
_text = ''
for _item in result:
_text += _item['hepburn']
return f' {_text} '
# 匹配日文字符的正则表达式模式,包括平假名、片假名和CJK统一汉字,以及特殊的日文汉字字符"々"和"〇"
pattern = re.compile(r'([\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u3005\u3007]+)')
# 将日文字符转换为罗马音,其他部分保持原样
parts = re.split(pattern, text)
processed_parts = [get_all_str(part) if re.match(pattern, part) else part for part in parts]
processed_text = ''.join(processed_parts)
processed_text = ' '.join(processed_text.split()) # 去除多余空格
return processed_text
@staticmethod
def contains_japanese(text):
"""判断日文字符"""
# Unicode范围:平假名[\u3040-\u309F]、片假名[\u30A0-\u30FF]、CJK统一汉字[\u4E00-\u9FFF]、々[\u3005]、〇[\u3007]
pattern = re.compile(r'[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\u3005\u3007]')
return bool(re.search(pattern, text))
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。