Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use metadata_god for getting song metadata instead of on_audio_query #101

Merged
merged 2 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/localizations.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:get_it/get_it.dart';
import 'package:metadata_god/metadata_god.dart';
import 'package:path_provider/path_provider.dart';

import 'domain/actors/persistence_actor.dart';
Expand All @@ -22,8 +23,8 @@ Future<void> main() async {
Fimber.plantTree(TimedRollingFileTree(
filenamePrefix: '${dir?.path}/logs/',
));
// Fimber.plantTree(DebugTree());

MetadataGod.initialize();
await setupGetIt();

final session = await AudioSession.instance;
Expand Down
95 changes: 63 additions & 32 deletions src/lib/system/datasources/local_music_fetcher_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import 'dart:io';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_fimber/flutter_fimber.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge.dart';
import 'package:metadata_god/metadata_god.dart';
import 'package:on_audio_query/on_audio_query.dart' as aq;
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';

import '../models/album_model.dart';
import '../models/artist_model.dart';
Expand Down Expand Up @@ -35,18 +39,28 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
final allowedExtensions = getExtensionSet(extString);
final blockedPaths = await _musicDataSource.blockedFilesStream.first;

final List<aq.SongModel> aqSongs = [];

final permissions = await _onAudioQuery.permissionsStatus();
if (!permissions) {
await _onAudioQuery.permissionsRequest();

final hasStorageAccess = await Permission.storage.isGranted;
if(!hasStorageAccess) {
await Permission.storage.request();
if (!await Permission.storage.isGranted) {
return {};
}
}


final List<File> songFiles = [];

for (final libDir in libDirs) {
await _onAudioQuery.scanMedia(libDir.path);
aqSongs.addAll(await _onAudioQuery.querySongs(path: libDir.path));
final List<File> files = await Directory(libDir.path)
.list(recursive: true, followLinks: false)
.where((item) => FileSystemEntity.isFileSync(item.path))
.asyncMap((item) => File(item.path)).toList();
songFiles.addAll(files);
}
_log.d('Found ${aqSongs.length} songs');

_log.d('Found ${songFiles.length} songs');

final List<SongModel> songs = [];
final List<AlbumModel> albums = [];
Expand All @@ -69,18 +83,18 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {

final Directory dir = await getApplicationSupportDirectory();

for (final aqSong in aqSongs.toSet()) {
if (!allowedExtensions.contains(aqSong.fileExtension.toLowerCase())) continue;
if (blockedPaths.contains(aqSong.data)) continue;
_log.d('Checking song: ${aqSong.data}');
for (final songFile in songFiles.toSet()) {
final String extension = p.extension(songFile.path).toLowerCase().substring(1);
if (!allowedExtensions.contains(extension)) continue;
if (blockedPaths.contains(songFile.path)) continue;
_log.d('Checking song: ${songFile.path}');

final data = aqSong.getMap;
// changed includes the creation time
// => also update, when the file was created later (and wasn't really changed)
// this is used as a workaround because android
// doesn't seem to return the correct modification time
final lastModified = DateTime.fromMillisecondsSinceEpoch((aqSong.dateModified ?? 0) * 1000);
final song = await _musicDataSource.getSongByPath(aqSong.data);
final lastModified = await songFile.lastModified();
final song = await _musicDataSource.getSongByPath(songFile.path);

int? albumId;
String albumString;
Expand Down Expand Up @@ -127,42 +141,46 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
albumId = song.albumId;
}
}

final Metadata songData;
try {
songData = await MetadataGod.readMetadata(file: songFile.path);
} on FfiException {
continue;
}

// completely new song -> new album ids should start after existing ones
// this is new information
// is the album ID still correct or do we find another album with the same properties?
final String albumArtist = data['album_artist'] as String? ?? '';
final String year = data['year'] as String? ?? '';
albumString = '${aqSong.album}___${albumArtist}__$year';
final String albumArtist = songData.albumArtist ?? '';
albumString = '${songData.album}___${albumArtist}__${songData.year}';

String? albumArtPath;
if (!albumIdMap.containsKey(albumString)) {
// we haven't seen an album with these properties in the files yet, but there might be an entry in the database
// in this case, we should use the corresponding ID
albumId ??= await _musicDataSource.getAlbumId(
aqSong.album,
songData.album,
albumArtist,
int.tryParse(year),
songData.year,
) ??
newAlbumId++;
albumIdMap[albumString] = albumId;

final albumArt = await _onAudioQuery.queryArtwork(
aqSong.albumId ?? -1,
aq.ArtworkType.ALBUM,
size: 600,
);
final albumArt = songData.picture;
_log.d('has picture: ${albumArt != null}');

if (albumArt != null && albumArt.isNotEmpty) {
if (albumArt != null) {
albumArtPath = '${dir.path}/$albumId';
final file = File(albumArtPath);
file.writeAsBytesSync(albumArt);
file.writeAsBytesSync(albumArt.data);
albumArtMap[albumId] = albumArtPath;

color = await getBackgroundColor(FileImage(file));
colorMap[albumId] = color;
}

final String songArtist = aqSong.artist ?? '';
final String songArtist = songData.artist ?? '';
final String artistName =
albumArtist != '' ? albumArtist : (songArtist != '' ? songArtist : DEF_ARTIST);

Expand All @@ -175,9 +193,9 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
}

albums.add(
AlbumModel.fromOnAudioQuery(
AlbumModel.fromMetadata(
albumId: albumId,
songModel: aqSong,
songData: songData,
albumArtPath: albumArtPath,
color: color,
),
Expand All @@ -191,9 +209,9 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
}

songs.add(
SongModel.fromOnAudioQuery(
path: aqSong.data,
songModel: aqSong,
SongModel.fromMetadata(
path: songFile.path,
songData: songData,
albumId: albumId,
albumArtPath: albumArtPath,
color: color,
Expand All @@ -215,4 +233,17 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
extensions = extensions.whereNot((element) => element.isEmpty).toList();
return Set<String>.from(extensions);
}

Future<List<File>> getAllFilesRecursively(String path) async {
final List<File> files = [];
if (await FileSystemEntity.isDirectory(path)) {
final dir = Directory(path);
await for (var entity in dir.list(recursive: true, followLinks: false)) {
files.addAll(await getAllFilesRecursively(entity.path));
}
} else if (await FileSystemEntity.isFile(path)) {
files.add(File(path));
}
return files;
}
}
15 changes: 7 additions & 8 deletions src/lib/system/models/album_model.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'dart:ui';

import 'package:drift/drift.dart';
import 'package:on_audio_query/on_audio_query.dart' as aq;
import 'package:metadata_god/metadata_god.dart';

import '../../domain/entities/album.dart';
import '../datasources/drift_database.dart';
Expand Down Expand Up @@ -32,23 +32,22 @@ class AlbumModel extends Album {
);
}

factory AlbumModel.fromOnAudioQuery({
required aq.SongModel songModel,
factory AlbumModel.fromMetadata({
required Metadata songData,
required int albumId,
String? albumArtPath,
Color? color,
}) {
final data = songModel.getMap;
final albumArtist = data['album_artist'] as String? ?? '';
final artist = albumArtist != '' ? albumArtist : songModel.artist;
final albumArtist = songData.albumArtist ?? '';
final artist = albumArtist != '' ? albumArtist : songData.artist;

return AlbumModel(
id: albumId,
title: songModel.album ?? DEF_ALBUM,
title: songData.album ?? DEF_ALBUM,
artist: artist ?? DEF_ARTIST,
albumArtPath: albumArtPath,
color: color,
pubYear: data['year'] == null ? null : parseYear(data['year'] as String?),
pubYear: songData.year,
);
}

Expand Down
39 changes: 11 additions & 28 deletions src/lib/system/models/song_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import 'dart:ui';

import 'package:audio_service/audio_service.dart';
import 'package:drift/drift.dart';
import 'package:metadata_god/metadata_god.dart';
import 'package:on_audio_query/on_audio_query.dart' as aq;
import 'package:path/path.dart' as p;

import '../../domain/entities/song.dart';
import '../datasources/drift_database.dart';
Expand Down Expand Up @@ -75,34 +77,31 @@ class SongModel extends Song {
);
}

factory SongModel.fromOnAudioQuery({
factory SongModel.fromMetadata({
required String path,
required aq.SongModel songModel,
required Metadata songData,
String? albumArtPath,
Color? color,
required int albumId,
required DateTime lastModified,
}) {
final data = songModel.getMap;
final trackNumber = parseTrackNumber(songModel.track);

return SongModel(
title: songModel.title,
artist: songModel.artist ?? DEF_ARTIST,
album: songModel.album ?? DEF_ALBUM,
title: songData.title ?? p.basenameWithoutExtension(path),
artist: songData.artist ?? DEF_ARTIST,
album: songData.album ?? DEF_ALBUM,
albumId: albumId,
path: path,
duration: Duration(milliseconds: songModel.duration ?? DEF_DURATION),
duration: songData.duration ?? const Duration(milliseconds: DEF_DURATION),
blockLevel: 0,
discNumber: trackNumber[0],
trackNumber: trackNumber[1],
discNumber: songData.discNumber ?? 1,
trackNumber: songData.trackNumber ?? 1,
albumArtPath: albumArtPath,
color: color,
next: false,
previous: false,
likeCount: 0,
playCount: 0,
year: parseYear(data['year'] as String?),
year: songData.year,
timeAdded: DateTime.fromMillisecondsSinceEpoch(0),
lastModified: lastModified,
);
Expand Down Expand Up @@ -213,22 +212,6 @@ class SongModel extends Song {
'timeAdded': timeAdded.millisecondsSinceEpoch,
'color': color?.value,
});

static List<int> parseTrackNumber(int? number) {
if (number == null) return [1, 1];

final numString = number.toString();

if (numString.length < 4) {
// does not contain a disc number
return [1, number];
}

final disc = numString.substring(0, numString.length - 3);
final track = numString.substring(numString.length - 3);

return [int.parse(disc), int.parse(track)];
}
}

// TODO: maybe move to another file
Expand Down
Loading