147 lines
4.7 KiB
Dart
147 lines
4.7 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:dio/dio.dart';
|
|
import 'package:freezer/api/deezer.dart';
|
|
import 'package:freezer/api/deezer_audio.dart';
|
|
import 'package:freezer/api/definitions.dart' as d;
|
|
import 'package:freezer/api/download.dart';
|
|
import 'package:freezer/api/download/database.dart';
|
|
import 'package:freezer/api/download/service_interface.dart';
|
|
import 'package:freezer/main.dart';
|
|
import 'package:freezer/settings.dart';
|
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
import 'package:isar/isar.dart';
|
|
import 'package:path/path.dart';
|
|
|
|
part 'service.freezed.dart';
|
|
part 'service.g.dart';
|
|
|
|
class DownloadService {
|
|
static const NOTIFICATION_ID = 6969;
|
|
static const NOTIFICATION_CHANNEL_ID = "freezerdownloads";
|
|
|
|
final ServiceInterface service;
|
|
DownloadService(this.service);
|
|
|
|
AudioQuality? _preferredQuality;
|
|
AudioQuality? _downloadQuality;
|
|
bool useGetURL = false;
|
|
|
|
late String downloadFilename;
|
|
|
|
late DeezerAPI _deezerAPI;
|
|
|
|
late final Isar _isar;
|
|
|
|
void run() async {
|
|
service.on('addDownloads', (event) {
|
|
downloadTrack(DownloadInfo.fromJson(event!));
|
|
});
|
|
service.on('updateQuality', (event) {
|
|
_preferredQuality = AudioQuality.values[event!['q']!];
|
|
});
|
|
service.on('updateCapabilities', (event) {
|
|
final bool canStreamHQ = event!['canStreamHQ'];
|
|
final bool canStreamLossless = event['canStreamLossless'];
|
|
|
|
if (canStreamHQ || canStreamLossless) useGetURL = true;
|
|
|
|
_downloadQuality = settings.maxQualityFor(
|
|
_preferredQuality!, canStreamHQ, canStreamLossless);
|
|
});
|
|
service.on('updateSettings', (event) {
|
|
if (event!['downloadFilename'] != null) {
|
|
downloadFilename = event['downloadFilename'];
|
|
}
|
|
|
|
if (event['deezerLanguage'] != null) {
|
|
_deezerAPI.deezerLanguage = event['deezerLanguage'];
|
|
}
|
|
if (event['deezerCountry'] != null) {
|
|
_deezerAPI.deezerCountry = event['deezerCountry'];
|
|
}
|
|
});
|
|
|
|
_isar = await Database.open();
|
|
_deezerAPI = DeezerAPI(await getCookieJar());
|
|
service.send('ready');
|
|
}
|
|
|
|
void downloadTrack(DownloadInfo info) async {
|
|
await _deezerAPI.authorize();
|
|
|
|
final trackId = info.trackId;
|
|
final deezerAudio = DeezerAudio(
|
|
deezerAPI: _deezerAPI,
|
|
quality: _downloadQuality ?? AudioQuality.MP3_128,
|
|
trackId: trackId);
|
|
|
|
bool offline = info.path == null;
|
|
|
|
final track = await _deezerAPI.track(trackId);
|
|
final file = File(fillVariables(track, info.path!));
|
|
print('downloading to ${file.path}');
|
|
final Uri uri;
|
|
if (useGetURL) {
|
|
// TODO: use pipe API to get track token!
|
|
final res = await deezerAudio.getUrl(
|
|
track.trackToken!, track.trackTokenExpiration!);
|
|
uri = res!.$1;
|
|
} else {
|
|
final res = await deezerAudio.fallback(
|
|
md5origin: track.playbackDetails![0],
|
|
mediaVersion: track.playbackDetails![1]);
|
|
uri = res.uri;
|
|
}
|
|
|
|
await _isar.writeTxn(() async {
|
|
await _isar.tracks.put(Track.from(track));
|
|
if (track.album != null) {
|
|
await _isar.albums.put(Album.from(track.album!));
|
|
}
|
|
|
|
if (track.artists != null) {
|
|
await _isar.artists
|
|
.putAll(track.artists!.map(Artist.from).toList(growable: false));
|
|
}
|
|
});
|
|
|
|
// download url and decrypt
|
|
final req = await _deezerAPI.dio.get(uri.toString(),
|
|
options: Options(responseType: ResponseType.bytes));
|
|
final stream =
|
|
DeezerAudio.decryptionStream(req.data, start: 0, trackId: trackId);
|
|
final fWrite = file.openWrite();
|
|
await stream.pipe(fWrite);
|
|
print('download complete!');
|
|
}
|
|
|
|
String pathForOffline(d.Track t) {
|
|
// TODO: implement
|
|
throw Exception();
|
|
}
|
|
|
|
String fillVariables(d.Track t, String path) {
|
|
return path
|
|
.replaceAll('%title%', Database.sanitize(t.title!))
|
|
.replaceAll('%album%', t.album!.title!)
|
|
.replaceAll('%artist%', t.artists?[0].name ?? '')
|
|
.replaceAll('%albumArtist%',
|
|
Database.sanitize(t.album?.artistString ?? t.artistString))
|
|
.replaceAll('%artists%', t.artistString)
|
|
.replaceAll('%feats%',
|
|
t.artists?.sublist(1).map((e) => e.name!).join(', ') ?? '')
|
|
.replaceAll('%trackNumber%', t.trackNumber!.toString())
|
|
.replaceAll('%0trackNumber%', t.trackNumber.toString().padLeft(2, '0'))
|
|
.replaceAll('%year%', '0'); // TODO: find year
|
|
}
|
|
}
|
|
|
|
@freezed
|
|
class DownloadInfo with _$DownloadInfo {
|
|
const factory DownloadInfo({required String trackId, String? path}) =
|
|
_DownloadInfo;
|
|
factory DownloadInfo.fromJson(Map<String, dynamic> json) =>
|
|
_$DownloadInfoFromJson(json);
|
|
}
|