freezer/lib/api/importer.dart

185 lines
4.6 KiB
Dart
Raw Normal View History

2021-04-05 20:22:32 +00:00
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/definitions.dart';
import 'package:freezer/api/download.dart';
Importer importer = Importer();
class Importer {
//Options
bool download = false;
//Preserve context
2021-09-01 12:38:32 +00:00
BuildContext? context;
String? title;
String? description;
late List<ImporterTrack> tracks;
String? playlistId;
Playlist? playlist;
2021-04-05 20:22:32 +00:00
bool done = false;
bool busy = false;
2021-09-01 12:38:32 +00:00
Future? _future;
late StreamController _streamController;
2021-04-05 20:22:32 +00:00
Stream get updateStream => _streamController.stream;
2021-09-01 12:38:32 +00:00
int get ok =>
tracks.fold(0, (v, t) => (t.state == TrackImportState.OK) ? v + 1 : v);
int get error =>
tracks.fold(0, (v, t) => (t.state == TrackImportState.ERROR) ? v + 1 : v);
2021-04-05 20:22:32 +00:00
Importer();
//Start importing wrapper
2021-09-01 12:38:32 +00:00
Future<void> start(BuildContext context, String? title, String? description,
List<ImporterTrack> tracks) async {
2021-04-05 20:22:32 +00:00
//Save variables
this.playlist = null;
this.context = context;
this.title = title;
2021-09-01 12:38:32 +00:00
this.description = description ?? '';
this.tracks = tracks.map((t) {
t.state = TrackImportState.NONE;
return t;
}).toList();
2021-04-05 20:22:32 +00:00
//Create playlist
2021-09-01 12:38:32 +00:00
playlistId =
await deezerAPI.createPlaylist(title, description: description);
2021-04-05 20:22:32 +00:00
busy = true;
done = false;
_streamController = StreamController.broadcast();
_future = _start();
}
//Start importer
Future _start() async {
2021-09-01 12:38:32 +00:00
for (int i = 0; i < tracks.length; i++) {
2021-04-05 20:22:32 +00:00
try {
2021-09-01 12:38:32 +00:00
String? id = await _searchTrack(tracks[i]);
2021-04-05 20:22:32 +00:00
//Not found
if (id == null) {
tracks[i].state = TrackImportState.ERROR;
_streamController.add(tracks[i]);
continue;
}
//Add to playlist
await deezerAPI.addToPlaylist(id, playlistId);
tracks[i].state = TrackImportState.OK;
} catch (_) {
//Error occurred, mark as error
tracks[i].state = TrackImportState.ERROR;
}
_streamController.add(tracks[i]);
}
//Get full playlist
playlist = await deezerAPI.playlist(playlistId, nb: 10000);
2021-09-01 12:38:32 +00:00
playlist!.library = true;
2021-04-05 20:22:32 +00:00
//Download
if (download) {
2021-09-01 12:38:32 +00:00
await downloadManager.addOfflinePlaylist(playlist,
private: false, context: context);
2021-04-05 20:22:32 +00:00
}
//Mark as done
done = true;
busy = false;
//To update UI
_streamController.add(null);
_streamController.close();
}
//Find track on Deezer servers
2021-09-01 12:38:32 +00:00
Future<String?> _searchTrack(ImporterTrack track) async {
2021-04-05 20:22:32 +00:00
//Try by ISRC
2021-09-01 12:38:32 +00:00
if (track.isrc != null && track.isrc!.length == 12) {
Map deezer = (await deezerAPI.callPublicApi('track/isrc:' + track.isrc!));
2021-04-05 20:22:32 +00:00
if (deezer["id"] != null) {
return deezer["id"].toString();
}
}
//Search
2021-09-01 12:38:32 +00:00
String cleanedTitle = track.title!
.trim()
.toLowerCase()
.replaceAll("-", "")
.replaceAll("&", "")
.replaceAll("+", "");
SearchResults results =
await deezerAPI.search("${track.artists![0]} $cleanedTitle");
for (Track t in results.tracks!) {
2021-04-05 20:22:32 +00:00
//Match title
2021-09-01 12:38:32 +00:00
if (_cleanMatching(t.title!) == _cleanMatching(track.title!)) {
2021-04-05 20:22:32 +00:00
//Match artist
2021-09-01 12:38:32 +00:00
if (_matchArtists(
track.artists!, t.artists!.map((a) => a.name) as List<String?>)) {
2021-04-05 20:22:32 +00:00
return t.id;
}
}
}
}
//Clean title for matching
String _cleanMatching(String t) {
2021-09-01 12:38:32 +00:00
return t
.toLowerCase()
.replaceAll(",", "")
.replaceAll("-", "")
.replaceAll(" ", "")
.replaceAll("&", "")
.replaceAll("+", "")
.replaceAll("/", "");
2021-04-05 20:22:32 +00:00
}
2021-09-01 12:38:32 +00:00
String _cleanArtist(String? a) {
return a!.toLowerCase().replaceAll(" ", "").replaceAll(",", "");
2021-04-05 20:22:32 +00:00
}
//Match at least 1 artist
2021-09-01 12:38:32 +00:00
bool _matchArtists(List<String?> a, List<String?> b) {
2021-04-05 20:22:32 +00:00
//Clean
List<String> _a = a.map(_cleanArtist).toList();
List<String> _b = b.map(_cleanArtist).toList();
for (String artist in _a) {
if (_b.contains(artist)) {
return true;
}
}
return false;
}
}
class ImporterTrack {
2021-09-01 12:38:32 +00:00
String? title;
List<String?>? artists;
String? isrc;
2021-04-05 20:22:32 +00:00
TrackImportState state;
2021-09-01 12:38:32 +00:00
ImporterTrack(this.title, this.artists,
{this.isrc, this.state = TrackImportState.NONE});
2021-04-05 20:22:32 +00:00
}
2021-09-01 12:38:32 +00:00
enum TrackImportState { NONE, ERROR, OK }
2021-04-05 20:22:32 +00:00
extension TrackImportStateExtension on TrackImportState {
Widget get icon {
switch (this) {
case TrackImportState.ERROR:
2021-09-01 12:38:32 +00:00
return Icon(
Icons.error,
color: Colors.red,
);
2021-04-05 20:22:32 +00:00
case TrackImportState.OK:
return Icon(Icons.done, color: Colors.green);
default:
return Container(width: 0, height: 0);
}
}
2021-09-01 12:38:32 +00:00
}