首发于 https://donlex.cn
现在的移动互联网时代,大部分人可能都不会刻意地留意关于歌曲的一些属性,直接在手机上下载或者在线听就行了。当然,这篇文章肯定会带你了解歌曲更加深入一点的内容,拓展一下知识面。
本文从两个方面进行探秘:
首先,从歌词方面入手,这部分的知识也是我在爬取各音乐网站的时候发现的。
常见的歌词格式有 LRC、TRC(天天动听歌词)、KRC(KuGou ResourCe,酷狗资源文件)和 QRC(QQ音乐歌词)。
目前就通过爬虫测试发现大部分音乐网站都是使用LRC格式的歌词。
LRC 歌词格式是世界上最通用的歌词格式,没有之一。它是最基本的歌词格式,几乎没有支持其他歌词格式而不支持这个歌词格式的播放器。LRC 歌词格式的特性也是——简单。
[ti:奔跑在我孤傲的路上 (Live)]
[ar:旅行团]
[al:乐人·Live:旅行团乐队“永远都会在”巡演杭州站(Live)]
[by:]
[offset:0]
[00:00.00]奔跑在我孤傲的路上 (Live) - 旅行团乐队 (The LifeJourney)
[00:05.81]词:孔阳/子君
[00:11.62]曲:孔阳/子君
[00:17.43]太阳走在天上
[00:18.79]
[00:19.41]我们走在路上
[00:20.77]
[00:21.53]走在各自走的方向
以上是一个示例LRC
文件的开头部分。
可以看到,首先有一些记录歌曲及歌词信息的东东,我们将其称作“ID 标签”(ID Tags),它可以包含歌曲名(ti)、专集(al)、歌手(ar)、歌词创建者(by)、歌词延迟调整(offset)等信息。
一眼就可看出,LRC 格式为每行歌词指定起始时刻,格式为 [分钟:秒.百分秒]。
通过Postman
请求QQ音乐可以发现,现在使用的也是这一种格式的歌词
其实之前在写NexT主题添加音乐 这篇的博客的时候使用的就是网易云的歌词接口,得到的歌词信息也是LRC格式的,有兴趣可以点开看看
TRC 格式是由天天动听制定的一种歌词格式,可以看作是对 LRC 格式的扩展——为什么我这样说呢?请看下面我从一 TRC 文件中从头摘取的文本。
[ar:胡彦斌]
[ti:当你要离开的时候]
[al:]
[total:243000]
[offset:0]
[by:ttpod]
[00:16.54]<250>当<300>你<1852>要<249>离<452>开<201>的<451>时<3801>候
[00:24.32]<200>我<200>们<1201>走<250>过<250>了<251>无<350>数<350>个<600>路<3851>口
因此,我们可以下结论,TRC 格式在 LRC 格式基础上,歌词正文中每个字的前面增加了时间标记<毫秒数>,每字连续解析,支持了逐字精准。或者,上文中“字”可理解为词,组合在一起当作一个“字”(以下称作“词看作字”),这在遇到英文时尤其有用。
当然,天天动听老黄历了,被阿里收购之后,现在也就无从探寻了。不过现在好像虾米音乐还是使用这种格式的歌词哈
在电脑上下载一首歌曲,查看文件信息,会发现一个我们平常不留意的普通文件竟然会包含这么多的信息。
通过KMPlayer查看媒体信息可以看到以下信息,当然也包含了歌曲的封面图片
其实这些信息都是通过在MP3文件末尾额外的数据空间来保存。说到这里就不得不说一下音频文件的ID3标签了
ID3,一般是位于一个mp3文件的开头或末尾的若干字节内,附加了关于该mp3的歌手,标题,专辑名称,年代,风格等信息,该信息就被称为ID3信息,ID3信息分为两个版本,v1和v2版。 其中:v1版的ID3在mp3文件的末尾128字节,以TAG三个字符开头,后面跟上歌曲信息。 v2版一般位于mp3的开头,可以存储歌词,该专辑的图片等大容量的信息. ——百度百科
既然知道了MP3文件中的信息是ID3标签,那我们能否自己通过代码读写ID3标签呢?当然是可以的。
主要通过使用 JAudiotagger
对MP3文件进行操作就可以完成。
一、Maven直接引入Jaudiotagger
或者使用jar包
<!-- https://mvnrepository.com/artifact/org/jaudiotagger -->
<dependency>
<groupId>org</groupId>
<artifactId>jaudiotagger</artifactId>
<version>2.0.3</version>
</dependency>
二、使用代码操作
这里以Java实现了一个简单的例子:
import entity.CopyrightInfoSave;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.mp3.MP3File;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.datatype.Artwork;
import org.jaudiotagger.tag.id3.AbstractID3v2Tag;
import org.jaudiotagger.tag.id3.ID3v23Tag;
import java.io.File;
import java.io.FileInputStream;
/**
*@author donlex
*@since 2019/08/18 13:35
*/
public class demo {
/**
*读取MP3文件的ID3标签信息
*/
public static void readId3Tag() throws TagException, ReadOnlyFileException, CannotReadException, InvalidAudioFrameException, IOException {
String url = "F:/Easier/QQDownload/660027.mp3";
File sourceFile = new File(url);
MP3File file = (MP3File) AudioFileIO.read(sourceFile);
Tag tag = file.getTag();
MP3AudioHeader mp3AudioHeader = file.getMP3AudioHeader();
//歌曲码率
String bitRate = mp3AudioHeader.getBitRate();
//获取MP3时长
int trackLength = mp3AudioHeader.getTrackLength();
int min = trackLength / 60;
int second = trackLength % 60;
String length = min + ":" + second;
System.out.println(bitRate +" -- "+length);
}
/**
*设置ID3标签进入MP3文件中
*copyrightInfoSave是一个实体
*/
public static void structAudioTag(CopyrightInfoSave copyrightInfoSave,String filePath,String lyricPath,String coverPath) throws Exception{
MP3File f = (MP3File) AudioFileIO.read(new File(filePath));
ID3v23Tag tag;
AbstractID3v2Tag tagV2=f.getID3v2Tag();
if(tagV2 instanceof ID3v23Tag){
tag=(ID3v23Tag)tagV2;
}else{
tag = new ID3v23Tag(tagV2);
}
tag.setField(FieldKey.ARTIST, copyrightInfoSave.getSingername());
tag.setField(FieldKey.ALBUM, copyrightInfoSave.getAlbumsname());
tag.setField(FieldKey.TITLE, copyrightInfoSave.getSongname());
tag.setField(FieldKey.COMMENT, "");
String lyric = readToString(lyricPath);
tag.setField(FieldKey.LYRICS,lyric);
// 设置歌曲封面图片
tag.deleteArtworkField();
Artwork newArtwork=Artwork.createArtworkFromFile(new File(coverPath));
tag.setField(newArtwork);
tag.addField(newArtwork);
f.setID3v2Tag(tag);
f.save();
}
/**
*读取歌词文件,并返回string类型
*/
public static String readToString(String fileName) {
String encoding = "UTF-8";
File file = new File(fileName);
Long filelength = file.length();
byte[] filecontent = new byte[filelength.intValue()];
try {
FileInputStream in = new FileInputStream(file);
in.read(filecontent);
in.close();
return new String(filecontent, encoding);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
更多例子可以查看官方的Example
参考资料: