mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-02 17:18:36 +02:00
182 lines
5.0 KiB
Dart
182 lines
5.0 KiB
Dart
import 'package:ffmpeg_kit_flutter_new_full/ffmpeg_session.dart';
|
|
|
|
/// Describes an extension-requested decryption step without coupling callers
|
|
/// to the FFmpeg execution service.
|
|
class DownloadDecryptionDescriptor {
|
|
final String strategy;
|
|
final String key;
|
|
final String? iv;
|
|
final String? inputFormat;
|
|
final String? outputExtension;
|
|
final Map<String, dynamic> options;
|
|
|
|
const DownloadDecryptionDescriptor({
|
|
required this.strategy,
|
|
required this.key,
|
|
this.iv,
|
|
this.inputFormat,
|
|
this.outputExtension,
|
|
this.options = const {},
|
|
});
|
|
|
|
factory DownloadDecryptionDescriptor.fromJson(Map<String, dynamic> json) {
|
|
final rawOptions = json['options'];
|
|
return DownloadDecryptionDescriptor(
|
|
strategy: (json['strategy'] as String? ?? '').trim(),
|
|
key: (json['key'] as String? ?? '').trim(),
|
|
iv: (json['iv'] as String?)?.trim(),
|
|
inputFormat: (json['input_format'] as String?)?.trim(),
|
|
outputExtension: (json['output_extension'] as String?)?.trim(),
|
|
options: rawOptions is Map
|
|
? Map<String, dynamic>.from(rawOptions)
|
|
: const {},
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
final json = <String, dynamic>{'strategy': strategy, 'key': key};
|
|
if (iv != null && iv!.isNotEmpty) {
|
|
json['iv'] = iv;
|
|
}
|
|
if (inputFormat != null && inputFormat!.isNotEmpty) {
|
|
json['input_format'] = inputFormat;
|
|
}
|
|
if (outputExtension != null && outputExtension!.isNotEmpty) {
|
|
json['output_extension'] = outputExtension;
|
|
}
|
|
if (options.isNotEmpty) {
|
|
json['options'] = options;
|
|
}
|
|
return json;
|
|
}
|
|
|
|
static DownloadDecryptionDescriptor? fromDownloadResult(
|
|
Map<String, dynamic> result,
|
|
) {
|
|
final rawDecryption = result['decryption'];
|
|
if (rawDecryption is Map) {
|
|
final descriptorJson = Map<String, dynamic>.from(rawDecryption);
|
|
descriptorJson['output_extension'] ??= result['output_extension'];
|
|
final descriptor = DownloadDecryptionDescriptor.fromJson(descriptorJson);
|
|
if (descriptor.normalizedStrategy == 'ffmpeg.mov_key' &&
|
|
descriptor.key.isNotEmpty) {
|
|
return descriptor;
|
|
}
|
|
}
|
|
|
|
final legacyKey = (result['decryption_key'] as String?)?.trim() ?? '';
|
|
if (legacyKey.isEmpty) {
|
|
return null;
|
|
}
|
|
|
|
return DownloadDecryptionDescriptor(
|
|
strategy: 'ffmpeg.mov_key',
|
|
key: legacyKey,
|
|
inputFormat: 'mov',
|
|
outputExtension: (result['output_extension'] as String?)?.trim(),
|
|
);
|
|
}
|
|
|
|
String get normalizedStrategy {
|
|
switch (strategy.trim().toLowerCase()) {
|
|
case '':
|
|
case 'ffmpeg.mov_key':
|
|
case 'ffmpeg_mov_key':
|
|
case 'mov_decryption_key':
|
|
case 'mp4_decryption_key':
|
|
case 'ffmpeg.mp4_decryption_key':
|
|
return 'ffmpeg.mov_key';
|
|
default:
|
|
return strategy.trim();
|
|
}
|
|
}
|
|
|
|
String? get normalizedOutputExtension {
|
|
final trimmed = (outputExtension ?? '').trim().toLowerCase();
|
|
if (trimmed.isEmpty) return null;
|
|
return trimmed.startsWith('.') ? trimmed : '.$trimmed';
|
|
}
|
|
}
|
|
|
|
class CueSplitTrackInfo {
|
|
final int number;
|
|
final String title;
|
|
final String artist;
|
|
final String isrc;
|
|
final String composer;
|
|
final double startSec;
|
|
final double endSec;
|
|
|
|
CueSplitTrackInfo({
|
|
required this.number,
|
|
required this.title,
|
|
required this.artist,
|
|
this.isrc = '',
|
|
this.composer = '',
|
|
required this.startSec,
|
|
required this.endSec,
|
|
});
|
|
|
|
factory CueSplitTrackInfo.fromJson(Map<String, dynamic> json) {
|
|
return CueSplitTrackInfo(
|
|
number: json['number'] as int? ?? 0,
|
|
title: json['title'] as String? ?? '',
|
|
artist: json['artist'] as String? ?? '',
|
|
isrc: json['isrc'] as String? ?? '',
|
|
composer: json['composer'] as String? ?? '',
|
|
startSec: (json['start_sec'] as num?)?.toDouble() ?? 0.0,
|
|
endSec: (json['end_sec'] as num?)?.toDouble() ?? -1.0,
|
|
);
|
|
}
|
|
}
|
|
|
|
class FFmpegResult {
|
|
final bool success;
|
|
final int returnCode;
|
|
final String output;
|
|
|
|
FFmpegResult({
|
|
required this.success,
|
|
required this.returnCode,
|
|
required this.output,
|
|
});
|
|
}
|
|
|
|
class LiveDecryptedStreamResult {
|
|
final String localUrl;
|
|
final String format;
|
|
final FFmpegSession session;
|
|
|
|
LiveDecryptedStreamResult({
|
|
required this.localUrl,
|
|
required this.format,
|
|
required this.session,
|
|
});
|
|
}
|
|
|
|
/// Result of an EBU R128 loudness scan, used to compute ReplayGain tags.
|
|
class ReplayGainResult {
|
|
/// Track gain in dB, e.g. "-6.50 dB".
|
|
final String trackGain;
|
|
|
|
/// Track peak as a linear ratio, e.g. "0.988831".
|
|
final String trackPeak;
|
|
|
|
/// Raw integrated loudness in LUFS (needed for album gain computation).
|
|
final double integratedLufs;
|
|
|
|
/// Raw true peak as linear ratio (needed for album peak computation).
|
|
final double truePeakLinear;
|
|
|
|
const ReplayGainResult({
|
|
required this.trackGain,
|
|
required this.trackPeak,
|
|
required this.integratedLufs,
|
|
required this.truePeakLinear,
|
|
});
|
|
|
|
@override
|
|
String toString() =>
|
|
'ReplayGainResult(trackGain: $trackGain, trackPeak: $trackPeak)';
|
|
}
|