replaced audiotagger with on_audio_query

This commit is contained in:
Moritz Weber 2022-10-24 21:28:30 +02:00
parent b1e2855308
commit 6197d258c2
6 changed files with 126 additions and 44 deletions

View file

@ -1,7 +1,7 @@
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
import 'package:audiotagger/audiotagger.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:just_audio/just_audio.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/audio_player_actor.dart';
import 'domain/actors/persistence_actor.dart'; import 'domain/actors/persistence_actor.dart';
@ -341,7 +341,7 @@ Future<void> setupGetIt() async {
getIt.registerFactory<AudioPlayer>(() => AudioPlayer()); getIt.registerFactory<AudioPlayer>(() => AudioPlayer());
getIt.registerLazySingleton<Audiotagger>(() => Audiotagger()); getIt.registerLazySingleton<OnAudioQuery>(() => OnAudioQuery());
// actors // actors
getIt.registerSingleton<PlatformIntegrationActor>( getIt.registerSingleton<PlatformIntegrationActor>(

View file

@ -1,8 +1,8 @@
import 'dart:io'; import 'dart:io';
import 'package:audiotagger/audiotagger.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter_fimber/flutter_fimber.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 'package:path_provider/path_provider.dart';
import '../models/album_model.dart'; import '../models/album_model.dart';
@ -14,11 +14,11 @@ import 'music_data_source_contract.dart';
import 'settings_data_source.dart'; import 'settings_data_source.dart';
class LocalMusicFetcherImpl implements LocalMusicFetcher { class LocalMusicFetcherImpl implements LocalMusicFetcher {
LocalMusicFetcherImpl(this._settingsDataSource, this._audiotagger, this._musicDataSource); LocalMusicFetcherImpl(this._settingsDataSource, this._musicDataSource, this._onAudioQuery);
static final _log = FimberLog('LocalMusicFetcher'); static final _log = FimberLog('LocalMusicFetcher');
final Audiotagger _audiotagger; final aq.OnAudioQuery _onAudioQuery;
final SettingsDataSource _settingsDataSource; final SettingsDataSource _settingsDataSource;
final MusicDataSource _musicDataSource; final MusicDataSource _musicDataSource;
@ -30,14 +30,10 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
final musicDirectories = await _settingsDataSource.libraryFoldersStream.first; final musicDirectories = await _settingsDataSource.libraryFoldersStream.first;
final libDirs = musicDirectories.map((e) => Directory(e)); final libDirs = musicDirectories.map((e) => Directory(e));
final List<File> files = []; final List<aq.SongModel> aqSongs = [];
for (final libDir in libDirs) { for (final libDir in libDirs) {
await for (final entity in libDir.list(recursive: true, followLinks: false)) { aqSongs.addAll(await _onAudioQuery.querySongs(path: libDir.path));
if (entity is File && entity.path.endsWith('.mp3')) {
files.add(entity);
}
}
} }
final List<SongModel> songs = []; final List<SongModel> songs = [];
@ -58,14 +54,15 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
final Directory dir = await getApplicationSupportDirectory(); final Directory dir = await getApplicationSupportDirectory();
for (final file in files.toSet()) { for (final aqSong in aqSongs.toSet()) {
final fileStats = file.statSync(); if (aqSong.data.toLowerCase().endsWith('.wma')) continue;
final data = aqSong.getMap;
// changed includes the creation time // changed includes the creation time
// => also update, when the file was created later (and wasn't really changed) // => also update, when the file was created later (and wasn't really changed)
// this is used as a workaround because android // this is used as a workaround because android
// doesn't seem to return the correct modification time // doesn't seem to return the correct modification time
final lastModified = _dateMax(fileStats.modified, fileStats.changed); final lastModified = DateTime.fromMillisecondsSinceEpoch((aqSong.dateModified ?? 0) * 1000);
final song = await _musicDataSource.getSongByPath(file.path); final song = await _musicDataSource.getSongByPath(aqSong.data);
int? albumId; int? albumId;
String albumString; String albumString;
@ -97,30 +94,25 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
} }
} }
// completely new song -> new album ids should start after existing ones // 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 // this is new information
// is the album ID still correct or do we find another album with the same properties? // 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; String? albumArtPath;
if (!albumIdMap.containsKey(albumString)) { 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 // 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 // in this case, we should use the corresponding ID
albumId ??= await _musicDataSource.getAlbumId( albumId ??= await _musicDataSource.getAlbumId(
tags.album, aqSong.album,
tags.albumArtist, albumArtist,
int.tryParse(tags.year ?? ''), int.tryParse(year),
) ?? ) ??
newAlbumId++; newAlbumId++;
albumIdMap[albumString] = albumId; 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) { if (albumArt != null && albumArt.isNotEmpty) {
albumArtPath = '${dir.path}/$albumId'; albumArtPath = '${dir.path}/$albumId';
@ -129,9 +121,9 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
albumArtMap[albumString] = albumArtPath; albumArtMap[albumString] = albumArtPath;
} }
final String albumArtist = tags.albumArtist ?? ''; final String songArtist = aqSong.artist ?? '';
final String songArtist = tags.artist ?? ''; final String artistName =
final String artistName = albumArtist != '' ? albumArtist : (songArtist != '' ? songArtist : DEF_ARTIST); albumArtist != '' ? albumArtist : (songArtist != '' ? songArtist : DEF_ARTIST);
final artist = artistsInDb.firstWhereOrNull((a) => a.name == artistName); final artist = artistsInDb.firstWhereOrNull((a) => a.name == artistName);
if (artist != null) { if (artist != null) {
@ -141,11 +133,9 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
artistSet.add(ArtistModel(name: artistName, id: newArtistId++)); artistSet.add(ArtistModel(name: artistName, id: newArtistId++));
} }
albums.add( albums.add(
AlbumModel.fromAudiotagger(albumId: albumId, tag: tags, albumArtPath: albumArtPath), AlbumModel.fromOnAudioQuery(
albumId: albumId, songModel: aqSong, albumArtPath: albumArtPath),
); );
} else { } else {
// an album with the same properties is already stored in the list // an album with the same properties is already stored in the list
@ -155,10 +145,9 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
} }
songs.add( songs.add(
SongModel.fromAudiotagger( SongModel.fromOnAudioQuery(
path: file.path, path: aqSong.data,
tag: tags, songModel: aqSong,
audioFile: audioFile,
albumId: albumId, albumId: albumId,
albumArtPath: albumArtPath, albumArtPath: albumArtPath,
lastModified: lastModified, lastModified: lastModified,
@ -173,8 +162,3 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
}; };
} }
} }
DateTime _dateMax(DateTime a, DateTime b) {
if (a.isAfter(b)) return a;
return b;
}

View file

@ -1,5 +1,6 @@
import 'package:audiotagger/models/tag.dart'; import 'package:audiotagger/models/tag.dart';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:on_audio_query/on_audio_query.dart' as aq;
import '../../domain/entities/album.dart'; import '../../domain/entities/album.dart';
import '../datasources/moor_database.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 @override
String toString() { String toString() {
return '$title'; return '$title';

View file

@ -2,6 +2,7 @@ import 'package:audio_service/audio_service.dart';
import 'package:audiotagger/models/audiofile.dart'; import 'package:audiotagger/models/audiofile.dart';
import 'package:audiotagger/models/tag.dart'; import 'package:audiotagger/models/tag.dart';
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:on_audio_query/on_audio_query.dart' as aq;
import '../../domain/entities/song.dart'; import '../../domain/entities/song.dart';
import '../datasources/moor_database.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 int albumId;
final DateTime lastModified; final DateTime lastModified;
@ -210,6 +244,22 @@ class SongModel extends Song {
} }
return int.parse(numberString); return int.parse(numberString);
} }
static List<int> _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 // TODO: maybe move to another file

View file

@ -373,6 +373,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.1" version: "4.0.1"
id3:
dependency: transitive
description:
name: id3
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
intl: intl:
dependency: transitive dependency: transitive
description: description:
@ -485,6 +492,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" 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: package_config:
dependency: transitive dependency: transitive
description: description:

View file

@ -26,6 +26,7 @@ dependencies:
get_it: ^7.1.3 # MIT get_it: ^7.1.3 # MIT
just_audio: ^0.9.18 # MIT just_audio: ^0.9.18 # MIT
mobx: ^2.0.1 # MIT mobx: ^2.0.1 # MIT
on_audio_query: ^2.6.1 # BSD 3
path: ^1.8.0 # BSD 3 path: ^1.8.0 # BSD 3
path_provider: ^2.0.2 # BSD 3 path_provider: ^2.0.2 # BSD 3
permission_handler: ^8.3.0 # MIT permission_handler: ^8.3.0 # MIT