166 lines
4.8 KiB
Dart
166 lines
4.8 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:on_audio_query/on_audio_query.dart';
|
|
import 'package:path/path.dart' as p;
|
|
import 'package:yumi/shared/data_sources/models/sc_music_folder_mode.dart';
|
|
import 'package:yumi/shared/data_sources/models/sc_music_mode.dart';
|
|
import 'package:yumi/shared/tools/sc_permission_utils.dart';
|
|
|
|
class LocalMusicScanner {
|
|
LocalMusicScanner({OnAudioQuery? audioQuery})
|
|
: _audioQuery = audioQuery ?? OnAudioQuery();
|
|
|
|
final OnAudioQuery _audioQuery;
|
|
|
|
Future<List<SCMusicFolderMode>> scanMp3Folders({
|
|
List<SCMusicMode> addedSongs = const [],
|
|
}) async {
|
|
final hasPermission = await SCPermissionUtils.checkAudioPermission();
|
|
if (!hasPermission) {
|
|
return const <SCMusicFolderMode>[];
|
|
}
|
|
|
|
final List<SongModel> songs;
|
|
try {
|
|
songs = await _audioQuery.querySongs(
|
|
sortType: SongSortType.TITLE,
|
|
orderType: OrderType.ASC_OR_SMALLER,
|
|
uriType: UriType.EXTERNAL,
|
|
ignoreCase: true,
|
|
);
|
|
} catch (_) {
|
|
return const <SCMusicFolderMode>[];
|
|
}
|
|
|
|
final addedIds = addedSongs.map((item) => item.id).toSet();
|
|
final addedPaths = addedSongs.map((item) => item.localPath).toSet();
|
|
final byFolder = <String, List<SCMusicMode>>{};
|
|
final seen = <String>{};
|
|
|
|
for (final song in songs) {
|
|
if (!_isMp3(song)) {
|
|
continue;
|
|
}
|
|
final localPath = _songString(song, "_data").trim();
|
|
if (localPath.isEmpty || !_fileExists(localPath)) {
|
|
continue;
|
|
}
|
|
final id = _songString(song, "_id");
|
|
final key = id.isNotEmpty ? id : localPath;
|
|
if (!seen.add(key)) {
|
|
continue;
|
|
}
|
|
final folderPath = p.dirname(localPath);
|
|
final fileName = _safeFileName(song, localPath);
|
|
final title = _safeTitle(song, fileName);
|
|
final model = SCMusicMode(
|
|
id: key,
|
|
title: title,
|
|
artist: _songString(song, "artist"),
|
|
durationMs: _songInt(song, "duration"),
|
|
localPath: localPath,
|
|
contentUri: _songString(song, "_uri"),
|
|
folderPath: folderPath,
|
|
fileName: fileName,
|
|
size: _songInt(song, "_size"),
|
|
addedAt: DateTime.now().millisecondsSinceEpoch,
|
|
);
|
|
byFolder.putIfAbsent(folderPath, () => <SCMusicMode>[]).add(model);
|
|
}
|
|
|
|
final folders =
|
|
byFolder.entries.map((entry) {
|
|
final path = entry.key;
|
|
final folderSongs =
|
|
entry.value..sort(
|
|
(a, b) =>
|
|
a.title.toLowerCase().compareTo(b.title.toLowerCase()),
|
|
);
|
|
final addedCount =
|
|
folderSongs.where((song) {
|
|
return addedIds.contains(song.id) ||
|
|
addedPaths.contains(song.localPath);
|
|
}).length;
|
|
return SCMusicFolderMode(
|
|
id: path,
|
|
name: p.basename(path).isEmpty ? path : p.basename(path),
|
|
path: path,
|
|
songs: List<SCMusicMode>.unmodifiable(folderSongs),
|
|
addedCount: addedCount,
|
|
);
|
|
}).toList()
|
|
..sort(
|
|
(a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()),
|
|
);
|
|
|
|
return List<SCMusicFolderMode>.unmodifiable(folders);
|
|
}
|
|
|
|
bool _isMp3(SongModel song) {
|
|
final extension = _songString(
|
|
song,
|
|
"file_extension",
|
|
).toLowerCase().replaceFirst(".", "");
|
|
if (extension == "mp3") {
|
|
return true;
|
|
}
|
|
return _songString(song, "_display_name").toLowerCase().endsWith(".mp3") ||
|
|
_songString(song, "_data").toLowerCase().endsWith(".mp3");
|
|
}
|
|
|
|
bool _fileExists(String path) {
|
|
try {
|
|
return File(path).existsSync();
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
String _safeFileName(SongModel song, String localPath) {
|
|
final displayName = _songString(song, "_display_name").trim();
|
|
if (displayName.isNotEmpty) {
|
|
return displayName;
|
|
}
|
|
return p.basename(localPath);
|
|
}
|
|
|
|
String _safeTitle(SongModel song, String fileName) {
|
|
final title = _songString(song, "title").trim();
|
|
if (title.isNotEmpty) {
|
|
return title;
|
|
}
|
|
final displayNameWOExt = _songString(song, "_display_name_wo_ext").trim();
|
|
if (displayNameWOExt.isNotEmpty) {
|
|
return displayNameWOExt;
|
|
}
|
|
return p.basenameWithoutExtension(fileName);
|
|
}
|
|
|
|
String _songString(SongModel song, String key) {
|
|
final value = _songValue(song, key);
|
|
if (value == null) {
|
|
return "";
|
|
}
|
|
return value.toString();
|
|
}
|
|
|
|
int _songInt(SongModel song, String key) {
|
|
final value = _songValue(song, key);
|
|
if (value is int) {
|
|
return value;
|
|
}
|
|
if (value is num) {
|
|
return value.toInt();
|
|
}
|
|
return int.tryParse(value?.toString() ?? "") ?? 0;
|
|
}
|
|
|
|
Object? _songValue(SongModel song, String key) {
|
|
try {
|
|
return song.getMap[key];
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|