freezer/lib/api/download/service.dart

147 lines
4.7 KiB
Dart
Raw Normal View History

import 'dart:io';
import 'package:dio/dio.dart';
import 'package:freezer/api/deezer.dart';
import 'package:freezer/api/deezer_audio.dart';
2024-03-31 17:29:06 +00:00
import 'package:freezer/api/definitions.dart' as d;
import 'package:freezer/api/download.dart';
2024-03-31 17:29:06 +00:00
import 'package:freezer/api/download/database.dart';
import 'package:freezer/api/download/service_interface.dart';
import 'package:freezer/main.dart';
2024-02-12 02:37:26 +00:00
import 'package:freezer/settings.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
2024-03-31 17:29:06 +00:00
import 'package:isar/isar.dart';
import 'package:path/path.dart';
2024-03-31 17:29:06 +00:00
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);
2024-02-12 02:37:26 +00:00
AudioQuality? _preferredQuality;
AudioQuality? _downloadQuality;
bool useGetURL = false;
late String downloadFilename;
late DeezerAPI _deezerAPI;
2024-03-31 17:29:06 +00:00
late final Isar _isar;
void run() async {
service.on('addDownloads', (event) {
downloadTrack(DownloadInfo.fromJson(event!));
});
service.on('updateQuality', (event) {
2024-02-12 02:37:26 +00:00
_preferredQuality = AudioQuality.values[event!['q']!];
});
service.on('updateCapabilities', (event) {
2024-02-12 02:37:26 +00:00
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'];
}
});
2024-03-31 17:29:06 +00:00
_isar = await Database.open();
_deezerAPI = DeezerAPI(await getCookieJar());
service.send('ready');
2024-02-12 02:37:26 +00:00
}
void downloadTrack(DownloadInfo info) async {
2024-03-31 17:29:06 +00:00
await _deezerAPI.authorize();
final trackId = info.trackId;
final deezerAudio = DeezerAudio(
deezerAPI: _deezerAPI,
quality: _downloadQuality ?? AudioQuality.MP3_128,
trackId: trackId);
2024-03-31 17:29:06 +00:00
bool offline = info.path == null;
final track = await _deezerAPI.track(trackId);
2024-03-31 17:29:06 +00:00
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;
}
2024-03-31 17:29:06 +00:00
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!');
}
2024-03-31 17:29:06 +00:00
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);
}