From 6197d258c2a779060fb51665b99dbb2f7641056d Mon Sep 17 00:00:00 2001 From: Moritz Weber Date: Mon, 24 Oct 2022 21:28:30 +0200 Subject: [PATCH] replaced audiotagger with on_audio_query --- src/lib/injection_container.dart | 4 +- .../datasources/local_music_fetcher_impl.dart | 66 +++++++------------ src/lib/system/models/album_model.dart | 19 ++++++ src/lib/system/models/song_model.dart | 52 ++++++++++++++- src/pubspec.lock | 28 ++++++++ src/pubspec.yaml | 1 + 6 files changed, 126 insertions(+), 44 deletions(-) diff --git a/src/lib/injection_container.dart b/src/lib/injection_container.dart index a8d6f2e..78d3b34 100644 --- a/src/lib/injection_container.dart +++ b/src/lib/injection_container.dart @@ -1,7 +1,7 @@ import 'package:audio_service/audio_service.dart'; -import 'package:audiotagger/audiotagger.dart'; import 'package:get_it/get_it.dart'; import 'package:just_audio/just_audio.dart'; +import 'package:on_audio_query/on_audio_query.dart'; import 'domain/actors/audio_player_actor.dart'; import 'domain/actors/persistence_actor.dart'; @@ -341,7 +341,7 @@ Future setupGetIt() async { getIt.registerFactory(() => AudioPlayer()); - getIt.registerLazySingleton(() => Audiotagger()); + getIt.registerLazySingleton(() => OnAudioQuery()); // actors getIt.registerSingleton( diff --git a/src/lib/system/datasources/local_music_fetcher_impl.dart b/src/lib/system/datasources/local_music_fetcher_impl.dart index 28e0bdf..45c072a 100644 --- a/src/lib/system/datasources/local_music_fetcher_impl.dart +++ b/src/lib/system/datasources/local_music_fetcher_impl.dart @@ -1,8 +1,8 @@ import 'dart:io'; -import 'package:audiotagger/audiotagger.dart'; import 'package:collection/collection.dart'; import 'package:flutter_fimber/flutter_fimber.dart'; +import 'package:on_audio_query/on_audio_query.dart' as aq; import 'package:path_provider/path_provider.dart'; import '../models/album_model.dart'; @@ -14,11 +14,11 @@ import 'music_data_source_contract.dart'; import 'settings_data_source.dart'; class LocalMusicFetcherImpl implements LocalMusicFetcher { - LocalMusicFetcherImpl(this._settingsDataSource, this._audiotagger, this._musicDataSource); + LocalMusicFetcherImpl(this._settingsDataSource, this._musicDataSource, this._onAudioQuery); static final _log = FimberLog('LocalMusicFetcher'); - final Audiotagger _audiotagger; + final aq.OnAudioQuery _onAudioQuery; final SettingsDataSource _settingsDataSource; final MusicDataSource _musicDataSource; @@ -30,14 +30,10 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher { final musicDirectories = await _settingsDataSource.libraryFoldersStream.first; final libDirs = musicDirectories.map((e) => Directory(e)); - final List files = []; + final List aqSongs = []; for (final libDir in libDirs) { - await for (final entity in libDir.list(recursive: true, followLinks: false)) { - if (entity is File && entity.path.endsWith('.mp3')) { - files.add(entity); - } - } + aqSongs.addAll(await _onAudioQuery.querySongs(path: libDir.path)); } final List songs = []; @@ -58,14 +54,15 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher { final Directory dir = await getApplicationSupportDirectory(); - for (final file in files.toSet()) { - final fileStats = file.statSync(); + for (final aqSong in aqSongs.toSet()) { + if (aqSong.data.toLowerCase().endsWith('.wma')) continue; + 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 = _dateMax(fileStats.modified, fileStats.changed); - final song = await _musicDataSource.getSongByPath(file.path); + final lastModified = DateTime.fromMillisecondsSinceEpoch((aqSong.dateModified ?? 0) * 1000); + final song = await _musicDataSource.getSongByPath(aqSong.data); int? albumId; String albumString; @@ -97,30 +94,25 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher { } } // completely new song -> new album ids should start after existing ones - final tags = await _audiotagger.readTags(path: file.path); - final audioFile = await _audiotagger.readAudioFile(path: file.path); - - if (tags == null || audioFile == null) { - _log.w('Could not read ${file.path}'); - continue; - } // this is new information // is the album ID still correct or do we find another album with the same properties? - albumString = '${tags.album}___${tags.albumArtist}__${tags.year}'; + final String albumArtist = data['album_artist'] as String? ?? ''; + final String year = data['year'] as String? ?? ''; + albumString = '${aqSong.album}___${albumArtist}__$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( - tags.album, - tags.albumArtist, - int.tryParse(tags.year ?? ''), + aqSong.album, + albumArtist, + int.tryParse(year), ) ?? newAlbumId++; albumIdMap[albumString] = albumId; - final albumArt = await _audiotagger.readArtwork(path: file.path); + final albumArt = await _onAudioQuery.queryArtwork(aqSong.albumId ?? -1, aq.ArtworkType.ALBUM, size: 640); if (albumArt != null && albumArt.isNotEmpty) { albumArtPath = '${dir.path}/$albumId'; @@ -129,9 +121,9 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher { albumArtMap[albumString] = albumArtPath; } - final String albumArtist = tags.albumArtist ?? ''; - final String songArtist = tags.artist ?? ''; - final String artistName = albumArtist != '' ? albumArtist : (songArtist != '' ? songArtist : DEF_ARTIST); + final String songArtist = aqSong.artist ?? ''; + final String artistName = + albumArtist != '' ? albumArtist : (songArtist != '' ? songArtist : DEF_ARTIST); final artist = artistsInDb.firstWhereOrNull((a) => a.name == artistName); if (artist != null) { @@ -141,11 +133,9 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher { artistSet.add(ArtistModel(name: artistName, id: newArtistId++)); } - - - albums.add( - AlbumModel.fromAudiotagger(albumId: albumId, tag: tags, albumArtPath: albumArtPath), + AlbumModel.fromOnAudioQuery( + albumId: albumId, songModel: aqSong, albumArtPath: albumArtPath), ); } else { // an album with the same properties is already stored in the list @@ -155,10 +145,9 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher { } songs.add( - SongModel.fromAudiotagger( - path: file.path, - tag: tags, - audioFile: audioFile, + SongModel.fromOnAudioQuery( + path: aqSong.data, + songModel: aqSong, albumId: albumId, albumArtPath: albumArtPath, lastModified: lastModified, @@ -173,8 +162,3 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher { }; } } - -DateTime _dateMax(DateTime a, DateTime b) { - if (a.isAfter(b)) return a; - return b; -} diff --git a/src/lib/system/models/album_model.dart b/src/lib/system/models/album_model.dart index 8be67ed..5af7798 100644 --- a/src/lib/system/models/album_model.dart +++ b/src/lib/system/models/album_model.dart @@ -1,5 +1,6 @@ import 'package:audiotagger/models/tag.dart'; import 'package:drift/drift.dart'; +import 'package:on_audio_query/on_audio_query.dart' as aq; import '../../domain/entities/album.dart'; import '../datasources/moor_database.dart'; @@ -39,6 +40,24 @@ class AlbumModel extends Album { ); } + factory AlbumModel.fromOnAudioQuery({ + required aq.SongModel songModel, + required int albumId, + String? albumArtPath, + }) { + final data = songModel.getMap; + final albumArtist = data['album_artist'] as String? ?? ''; + final artist = albumArtist != '' ? albumArtist : songModel.artist; + + return AlbumModel( + id: albumId, + title: songModel.album ?? DEF_ALBUM, + artist: artist ?? DEF_ARTIST, + albumArtPath: albumArtPath, + pubYear: data['year'] == null ? null : parseYear(data['year'] as String?), + ); + } + @override String toString() { return '$title'; diff --git a/src/lib/system/models/song_model.dart b/src/lib/system/models/song_model.dart index 7e6d412..f2cbc54 100644 --- a/src/lib/system/models/song_model.dart +++ b/src/lib/system/models/song_model.dart @@ -2,6 +2,7 @@ import 'package:audio_service/audio_service.dart'; import 'package:audiotagger/models/audiofile.dart'; import 'package:audiotagger/models/tag.dart'; import 'package:drift/drift.dart'; +import 'package:on_audio_query/on_audio_query.dart' as aq; import '../../domain/entities/song.dart'; import '../datasources/moor_database.dart'; @@ -98,6 +99,39 @@ class SongModel extends Song { ); } + factory SongModel.fromOnAudioQuery({ + required String path, + required aq.SongModel songModel, + String? albumArtPath, + 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, + albumId: albumId, + path: path, + duration: Duration(milliseconds: songModel.duration ?? DEF_DURATION), + blockLevel: 0, + discNumber: trackNumber[0], + trackNumber: trackNumber[1], + albumArtPath: albumArtPath, + next: false, + previous: false, + likeCount: 0, + playCount: 0, + skipCount: 0, + year: parseYear(data['year'] as String?), + timeAdded: DateTime.fromMillisecondsSinceEpoch(0), + lastModified: lastModified, + ); + } + final int albumId; final DateTime lastModified; @@ -141,7 +175,7 @@ class SongModel extends Song { trackNumber: trackNumber ?? this.trackNumber, likeCount: likeCount ?? this.likeCount, skipCount: skipCount ?? this.skipCount, - playCount: playCount ?? this.playCount, + playCount: playCount ?? this.playCount, timeAdded: timeAdded ?? this.timeAdded, lastModified: lastModified ?? this.lastModified, year: year ?? this.year, @@ -210,6 +244,22 @@ class SongModel extends Song { } return int.parse(numberString); } + + static List _parseTrackNumber(int? number) { + if (number == null) return [1, 1]; + + final numString = number.toString(); + final firstZero = numString.indexOf('0'); + + if (firstZero < 0 || firstZero == numString.length - 1) { + return [1, number]; + } + + final disc = numString.substring(0, firstZero); + final track = numString.substring(firstZero + 1); + + return [int.parse(disc), int.parse(track)]; + } } // TODO: maybe move to another file diff --git a/src/pubspec.lock b/src/pubspec.lock index c77318d..a2a9cc4 100644 --- a/src/pubspec.lock +++ b/src/pubspec.lock @@ -373,6 +373,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.1" + id3: + dependency: transitive + description: + name: id3 + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" intl: dependency: transitive description: @@ -485,6 +492,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + on_audio_query: + dependency: "direct main" + description: + name: on_audio_query + url: "https://pub.dartlang.org" + source: hosted + version: "2.6.1" + on_audio_query_platform_interface: + dependency: transitive + description: + name: on_audio_query_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + on_audio_query_web: + dependency: transitive + description: + name: on_audio_query_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.2+2" package_config: dependency: transitive description: diff --git a/src/pubspec.yaml b/src/pubspec.yaml index cac75cb..4feb11a 100644 --- a/src/pubspec.yaml +++ b/src/pubspec.yaml @@ -26,6 +26,7 @@ dependencies: get_it: ^7.1.3 # MIT just_audio: ^0.9.18 # MIT mobx: ^2.0.1 # MIT + on_audio_query: ^2.6.1 # BSD 3 path: ^1.8.0 # BSD 3 path_provider: ^2.0.2 # BSD 3 permission_handler: ^8.3.0 # MIT