replaced audiotagger with on_audio_query
This commit is contained in:
parent
b1e2855308
commit
6197d258c2
6 changed files with 126 additions and 44 deletions
|
@ -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>(
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue