2020-10-21 18:05:33 +08:00
|
|
|
|
package com.yutou.tools.utils;
|
|
|
|
|
|
2020-11-23 18:31:09 +08:00
|
|
|
|
import com.alibaba.fastjson.JSON;
|
2020-11-23 16:14:50 +08:00
|
|
|
|
import com.alibaba.fastjson.JSONObject;
|
2020-10-21 18:05:33 +08:00
|
|
|
|
import com.yutou.tools.home.nas.Data.MusicData;
|
2020-10-26 16:50:16 +08:00
|
|
|
|
import com.yutou.tools.home.nas.MusicController;
|
2020-10-23 17:55:56 +08:00
|
|
|
|
import ealvatag.audio.AudioFile;
|
|
|
|
|
import ealvatag.audio.AudioFileIO;
|
2020-11-23 16:14:50 +08:00
|
|
|
|
import ealvatag.audio.AudioHeader;
|
2020-10-23 17:55:56 +08:00
|
|
|
|
import ealvatag.audio.exceptions.CannotReadException;
|
|
|
|
|
import ealvatag.tag.FieldKey;
|
|
|
|
|
import ealvatag.tag.NullTag;
|
|
|
|
|
import ealvatag.tag.Tag;
|
2020-11-23 16:14:50 +08:00
|
|
|
|
import ealvatag.tag.flac.FlacTag;
|
2020-10-21 23:25:29 +08:00
|
|
|
|
import org.springframework.util.StringUtils;
|
2020-10-21 18:05:33 +08:00
|
|
|
|
|
|
|
|
|
import java.io.*;
|
2020-10-26 23:43:20 +08:00
|
|
|
|
import java.text.SimpleDateFormat;
|
2020-10-23 17:55:56 +08:00
|
|
|
|
import java.util.*;
|
2020-10-21 18:05:33 +08:00
|
|
|
|
|
|
|
|
|
public class MusicTools {
|
|
|
|
|
public static final int FIND_TITLE = 1;
|
|
|
|
|
public static final int FIND_ARTIST = 2;
|
|
|
|
|
|
|
|
|
|
private static MusicTools tools;
|
2020-10-27 17:20:31 +08:00
|
|
|
|
private String musicPath = "/media/yutou/4t/public/音乐";
|
2020-10-21 18:05:33 +08:00
|
|
|
|
private final List<MusicData> musicList = new ArrayList<>();
|
2020-10-26 18:32:35 +08:00
|
|
|
|
private HashMap<String, List<MusicData>> musicMap = new HashMap<String, List<MusicData>>();
|
2020-10-21 18:05:33 +08:00
|
|
|
|
private boolean isScan = false;
|
|
|
|
|
|
|
|
|
|
public static MusicTools getInstance() {
|
|
|
|
|
if (tools == null) {
|
|
|
|
|
tools = new MusicTools();
|
|
|
|
|
}
|
|
|
|
|
return tools;
|
|
|
|
|
}
|
2020-10-26 18:32:35 +08:00
|
|
|
|
|
|
|
|
|
private MusicTools() {
|
2020-10-23 17:55:56 +08:00
|
|
|
|
scanMusic();
|
2020-10-26 23:43:20 +08:00
|
|
|
|
new Timer().schedule(new TimerTask() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
2020-11-23 16:14:50 +08:00
|
|
|
|
String time = new SimpleDateFormat("HH:mm").format(new Date());
|
|
|
|
|
if (time.equals("00:00")) {
|
2020-10-26 23:43:20 +08:00
|
|
|
|
System.out.println("零点刷新列表");
|
|
|
|
|
scanMusic();
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-23 16:14:50 +08:00
|
|
|
|
}, 0, 10 * 1000);
|
2020-10-23 17:55:56 +08:00
|
|
|
|
}
|
2020-10-21 18:05:33 +08:00
|
|
|
|
|
2020-10-21 23:25:29 +08:00
|
|
|
|
public synchronized void scanMusic() {
|
2020-10-26 18:32:35 +08:00
|
|
|
|
if (isScan) {
|
2020-10-23 17:55:56 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
2020-10-21 23:25:29 +08:00
|
|
|
|
System.out.println("执行扫描");
|
|
|
|
|
musicList.clear();
|
2020-10-27 17:20:31 +08:00
|
|
|
|
musicMap.clear();
|
2020-10-21 18:05:33 +08:00
|
|
|
|
new Thread(() -> {
|
|
|
|
|
isScan = true;
|
|
|
|
|
scan(new File(musicPath));
|
|
|
|
|
isScan = false;
|
2020-10-21 23:25:29 +08:00
|
|
|
|
System.out.println("扫描完成");
|
2020-10-21 18:05:33 +08:00
|
|
|
|
}).start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public List<MusicData> scan(File path, boolean isScanDir) {
|
|
|
|
|
if (isScanDir) {
|
|
|
|
|
scan(path);
|
|
|
|
|
return musicList;
|
|
|
|
|
} else {
|
|
|
|
|
if (path.exists() && path.isDirectory()) {
|
|
|
|
|
List<MusicData> list = new ArrayList<>();
|
|
|
|
|
for (File file : path.listFiles()) {
|
|
|
|
|
list.add(getMetadata(file));
|
|
|
|
|
}
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void scan(File path) {
|
|
|
|
|
if (path.isFile()) {
|
|
|
|
|
add(path);
|
|
|
|
|
} else if (path.isDirectory()) {
|
|
|
|
|
for (File file : Objects.requireNonNull(path.listFiles())) {
|
|
|
|
|
if (file.isDirectory()) {
|
|
|
|
|
scan(file);
|
|
|
|
|
} else {
|
|
|
|
|
add(file);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-10-26 18:32:35 +08:00
|
|
|
|
|
|
|
|
|
public void getPathOrDir(String path, List<MusicData> list) {
|
|
|
|
|
File files = new File(path);
|
|
|
|
|
for (File file : files.listFiles()) {
|
|
|
|
|
if (file.isFile()) {
|
|
|
|
|
list.add(getMetadata(file));
|
|
|
|
|
} else {
|
|
|
|
|
getPathOrDir(file.getAbsolutePath(), list);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-26 23:19:20 +08:00
|
|
|
|
/**
|
|
|
|
|
* 获取指定目录下的音乐
|
2020-11-23 16:14:50 +08:00
|
|
|
|
*
|
|
|
|
|
* @param path 指定目录
|
2020-10-26 23:19:20 +08:00
|
|
|
|
* @param isDir 是否扫描目录下的所有文件,false则仅为当前目录
|
|
|
|
|
* @return 音乐列表
|
|
|
|
|
*/
|
2020-11-23 16:14:50 +08:00
|
|
|
|
public List<MusicData> getPath(String path, boolean isDir) {
|
2020-10-26 18:32:35 +08:00
|
|
|
|
List<MusicData> list = new ArrayList<>();
|
2020-11-23 16:14:50 +08:00
|
|
|
|
if (isDir) {
|
2020-10-26 23:19:20 +08:00
|
|
|
|
if (new File(path).isDirectory() && !path.equals(MusicController.defaultMusicPath)) {
|
|
|
|
|
for (String key : musicMap.keySet()) {
|
|
|
|
|
if (key.startsWith(path)) {
|
|
|
|
|
list.addAll(musicMap.get(key));
|
|
|
|
|
}
|
2020-10-26 18:32:35 +08:00
|
|
|
|
}
|
2020-10-26 23:19:20 +08:00
|
|
|
|
return list;
|
2020-10-26 18:32:35 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (musicMap.containsKey(path)) {
|
|
|
|
|
MusicData tmp = musicMap.get(path).isEmpty() ? null : musicMap.get(path).get(0);
|
|
|
|
|
if (tmp != null) {
|
|
|
|
|
if (!tmp.getFile().getParent().equals(MusicController.defaultMusicPath)) {
|
2020-10-26 16:50:16 +08:00
|
|
|
|
MusicData t2 = new MusicData();
|
|
|
|
|
t2.setTitle("返回");
|
|
|
|
|
t2.setFile(new File(tmp.getLastDir()));
|
|
|
|
|
list.add(t2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
getDirList(path, list);
|
|
|
|
|
list.addAll(musicMap.get(path));
|
|
|
|
|
return list;
|
2020-10-26 18:32:35 +08:00
|
|
|
|
} else {
|
|
|
|
|
if (path.contains(MusicController.defaultMusicPath)) {
|
2020-10-26 16:50:16 +08:00
|
|
|
|
MusicData t2 = new MusicData();
|
|
|
|
|
t2.setTitle("返回");
|
|
|
|
|
t2.setFile(new File(path).getParentFile());
|
2020-10-26 18:32:35 +08:00
|
|
|
|
if (!path.equals(MusicController.defaultMusicPath)) {
|
|
|
|
|
list.add(t2);
|
|
|
|
|
}
|
2020-10-26 16:50:16 +08:00
|
|
|
|
getDirList(path, list);
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-23 17:55:56 +08:00
|
|
|
|
}
|
|
|
|
|
return new ArrayList<>();
|
|
|
|
|
}
|
2020-10-21 18:05:33 +08:00
|
|
|
|
|
2020-10-26 16:50:16 +08:00
|
|
|
|
private void getDirList(String path, List<MusicData> list) {
|
2020-10-26 18:32:35 +08:00
|
|
|
|
File file = new File(path);
|
2020-10-26 16:50:16 +08:00
|
|
|
|
for (File listFile : file.listFiles()) {
|
2020-10-26 18:32:35 +08:00
|
|
|
|
if (listFile.isDirectory()) {
|
|
|
|
|
MusicData data = new MusicData();
|
2020-10-26 16:50:16 +08:00
|
|
|
|
data.setTitle(listFile.getName());
|
|
|
|
|
data.setFile(listFile);
|
|
|
|
|
list.add(data);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-21 18:05:33 +08:00
|
|
|
|
private void add(File file) {
|
|
|
|
|
MusicData data = getMetadata(file);
|
|
|
|
|
if (data != null) {
|
|
|
|
|
musicList.add(data);
|
2020-10-26 18:32:35 +08:00
|
|
|
|
String path = file.getAbsolutePath().replace(File.separator + file.getName(), "");
|
2020-10-23 17:55:56 +08:00
|
|
|
|
List<MusicData> list;
|
2020-10-26 18:32:35 +08:00
|
|
|
|
if (musicMap.containsKey(path)) {
|
|
|
|
|
list = musicMap.get(path);
|
|
|
|
|
} else {
|
|
|
|
|
list = new ArrayList<>();
|
2020-10-23 17:55:56 +08:00
|
|
|
|
}
|
|
|
|
|
list.add(data);
|
2020-10-26 18:32:35 +08:00
|
|
|
|
musicMap.put(path, list);
|
2020-10-21 18:05:33 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public MusicData getMetadata(File file) {
|
|
|
|
|
try {
|
2020-10-26 16:50:16 +08:00
|
|
|
|
if (file.getName().endsWith(".lrc")
|
|
|
|
|
|| file.getName().endsWith(".jpg")
|
|
|
|
|
|| file.getName().endsWith(".ini")
|
2020-10-26 23:43:20 +08:00
|
|
|
|
|| file.getName().endsWith(".png")
|
|
|
|
|
|| file.getName().endsWith(".torrent")
|
2020-11-23 18:31:09 +08:00
|
|
|
|
|| file.getName().endsWith(".log")
|
|
|
|
|
|| file.getName().endsWith(".mkv")
|
2020-11-23 18:32:14 +08:00
|
|
|
|
|| file.getName().endsWith(".dff")
|
|
|
|
|
|| file.getName().endsWith(".cue")
|
2020-10-26 16:50:16 +08:00
|
|
|
|
) {
|
2020-10-21 23:25:29 +08:00
|
|
|
|
return null;
|
|
|
|
|
}
|
2020-10-21 18:05:33 +08:00
|
|
|
|
AudioFile audioFile = AudioFileIO.read(file);
|
2020-10-23 17:55:56 +08:00
|
|
|
|
Tag tag = audioFile.getTag().or(NullTag.INSTANCE);
|
2020-10-21 18:05:33 +08:00
|
|
|
|
MusicData data = new MusicData();
|
2020-10-21 23:25:29 +08:00
|
|
|
|
try {
|
|
|
|
|
data.setAlbum(tag.getFirst(FieldKey.ALBUM));
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setArtist(tag.getFirst(FieldKey.ARTIST));
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setArtist_sort(tag.getFirst(FieldKey.ARTIST_SORT));
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setComment(tag.getFirst(FieldKey.COMMENT));
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setComposer(tag.getFirst(FieldKey.COMPOSER));
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setDisc_no(tag.getFirst(FieldKey.DISC_NO));
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
if (StringUtils.isEmpty(tag.getFirst(FieldKey.TITLE))) {
|
|
|
|
|
data.setTitle(file.getName());
|
|
|
|
|
} else {
|
|
|
|
|
data.setTitle(tag.getFirst(FieldKey.TITLE));
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
data.setTitle(file.getName());
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setTrack(tag.getFirst(FieldKey.TRACK));
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setYear(tag.getFirst(FieldKey.YEAR));
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
}
|
|
|
|
|
data.setFile(file);
|
2020-11-23 16:14:50 +08:00
|
|
|
|
|
|
|
|
|
AudioHeader header = audioFile.getAudioHeader();
|
|
|
|
|
data.setBitRate(header.getBitRate());
|
|
|
|
|
data.setSampleRate(header.getSampleRate());
|
|
|
|
|
data.setNoOfSamples(header.getNoOfSamples());
|
|
|
|
|
data.setChannelCount(header.getChannelCount());
|
|
|
|
|
data.setEncodingType(header.getEncodingType());
|
|
|
|
|
data.setDurationAsDouble(header.getDurationAsDouble());
|
|
|
|
|
data.setLossless(header.isLossless());
|
|
|
|
|
data.setVariableBitRate(header.isVariableBitRate());
|
|
|
|
|
try {
|
|
|
|
|
data.setMd5(header.getClass().getMethod("getMd5").invoke(header).toString());
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
data.setMd5(Base64.getEncoder().encodeToString(file.getAbsolutePath().getBytes()));
|
|
|
|
|
}
|
2020-10-21 23:25:29 +08:00
|
|
|
|
return data;
|
|
|
|
|
} catch (CannotReadException e) {
|
2020-11-23 18:31:09 +08:00
|
|
|
|
MusicData data = getMetadata_jthink(file);
|
|
|
|
|
if (data == null)
|
|
|
|
|
data = new MusicData();
|
2020-10-21 23:25:29 +08:00
|
|
|
|
data.setTitle(file.getName());
|
2020-10-21 18:05:33 +08:00
|
|
|
|
data.setFile(file);
|
|
|
|
|
return data;
|
|
|
|
|
} catch (Exception e) {
|
2020-10-21 23:25:29 +08:00
|
|
|
|
e.printStackTrace();
|
2020-10-21 18:05:33 +08:00
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-23 18:31:09 +08:00
|
|
|
|
public MusicData getMetadata_jthink(File file) {
|
|
|
|
|
try {
|
|
|
|
|
org.jaudiotagger.audio.AudioFile audioFile = org.jaudiotagger.audio.AudioFileIO.read(file);
|
|
|
|
|
org.jaudiotagger.tag.Tag tag = audioFile.getTag();
|
|
|
|
|
MusicData data = new MusicData();
|
|
|
|
|
try {
|
|
|
|
|
data.setAlbum(tag.getFirst(org.jaudiotagger.tag.FieldKey.ALBUM));
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setArtist(tag.getFirst(org.jaudiotagger.tag.FieldKey.ARTIST));
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setArtist_sort(tag.getFirst(org.jaudiotagger.tag.FieldKey.ARTIST_SORT));
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setComment(tag.getFirst(org.jaudiotagger.tag.FieldKey.COMMENT));
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setComposer(tag.getFirst(org.jaudiotagger.tag.FieldKey.COMPOSER));
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setDisc_no(tag.getFirst(org.jaudiotagger.tag.FieldKey.DISC_NO));
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
if (StringUtils.isEmpty(tag.getFirst(org.jaudiotagger.tag.FieldKey.TITLE))) {
|
|
|
|
|
data.setTitle(file.getName());
|
|
|
|
|
} else {
|
|
|
|
|
data.setTitle(tag.getFirst(org.jaudiotagger.tag.FieldKey.TITLE));
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
data.setTitle(file.getName());
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setTrack(tag.getFirst(org.jaudiotagger.tag.FieldKey.TRACK));
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
data.setYear(tag.getFirst(org.jaudiotagger.tag.FieldKey.YEAR));
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
}
|
|
|
|
|
data.setFile(file);
|
|
|
|
|
|
|
|
|
|
org.jaudiotagger.audio.AudioHeader header = audioFile.getAudioHeader();
|
|
|
|
|
data.setBitRate(Integer.parseInt(header.getBitRate()));
|
|
|
|
|
data.setSampleRate(Integer.parseInt(header.getSampleRate()));
|
|
|
|
|
data.setNoOfSamples(header.getSampleRateAsNumber());
|
|
|
|
|
data.setChannelCount(Integer.parseInt(header.getChannels()));
|
|
|
|
|
data.setEncodingType(header.getEncodingType() + "");
|
|
|
|
|
data.setDurationAsDouble(header.getTrackLength());
|
|
|
|
|
data.setLossless(header.isLossless());
|
|
|
|
|
data.setVariableBitRate(header.isVariableBitRate());
|
|
|
|
|
try {
|
|
|
|
|
data.setMd5(header.getClass().getMethod("getMd5").invoke(header).toString());
|
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
|
data.setMd5(Base64.getEncoder().encodeToString(file.getAbsolutePath().getBytes()));
|
|
|
|
|
}
|
|
|
|
|
return data;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2020-10-21 18:05:33 +08:00
|
|
|
|
|
|
|
|
|
public List<MusicData> findOfTitle(String title) {
|
|
|
|
|
return find(title, FIND_TITLE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public List<MusicData> findOfArtist(String by) {
|
|
|
|
|
return find(by, FIND_ARTIST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public List<MusicData> getMusicList() {
|
|
|
|
|
return musicList;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int getLength() {
|
|
|
|
|
return tools.musicList.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public boolean isScan() {
|
|
|
|
|
return isScan;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<MusicData> find(String title, int type) {
|
|
|
|
|
List<MusicData> list = new ArrayList<>();
|
|
|
|
|
for (MusicData data : musicList) {
|
|
|
|
|
if (data.getTitle().contains(title)) {
|
|
|
|
|
list.add(data);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-21 23:25:29 +08:00
|
|
|
|
|
2020-10-21 18:05:33 +08:00
|
|
|
|
public String getMusicPath() {
|
|
|
|
|
return musicPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setMusicPath(String musicPath) {
|
|
|
|
|
this.musicPath = musicPath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void saveImage(MusicData data) {
|
|
|
|
|
try {
|
|
|
|
|
File file = new File(data.getTitle() + ".jpg");
|
|
|
|
|
if (!file.exists()) {
|
|
|
|
|
if (!file.createNewFile()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
OutputStream outputStream = new FileOutputStream(file);
|
|
|
|
|
outputStream.write(data.readImage());
|
|
|
|
|
outputStream.flush();
|
|
|
|
|
outputStream.close();
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-26 18:32:35 +08:00
|
|
|
|
public static void main(String[] args) throws Exception {
|
2020-11-26 18:21:48 +08:00
|
|
|
|
File file = new File("Z:\\音乐\\总之就是非常酸\\ED\\カノエラナ - 月と星空\\カノエラナ.wav");
|
|
|
|
|
System.out.println(new MusicTools().getMetadata(file));
|
2020-10-21 18:05:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-21 23:25:29 +08:00
|
|
|
|
|
2020-10-21 18:05:33 +08:00
|
|
|
|
}
|