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: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<void> setupGetIt() async {
|
|||
|
||||
getIt.registerFactory<AudioPlayer>(() => AudioPlayer());
|
||||
|
||||
getIt.registerLazySingleton<Audiotagger>(() => Audiotagger());
|
||||
getIt.registerLazySingleton<OnAudioQuery>(() => OnAudioQuery());
|
||||
|
||||
// actors
|
||||
getIt.registerSingleton<PlatformIntegrationActor>(
|
||||
|
|
|
@ -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<File> files = [];
|
||||
final List<aq.SongModel> 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<SongModel> 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;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue