mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-19 14:07:26 +02:00
216 lines
6.7 KiB
Dart
216 lines
6.7 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:ffmpeg_kit_flutter_new_audio/ffmpeg_kit_config.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:spotiflac_android/services/ffmpeg_decryption.dart';
|
|
import 'package:spotiflac_android/services/ffmpeg_models.dart';
|
|
|
|
void main() {
|
|
late Directory directory;
|
|
setUp(() async {
|
|
directory = await Directory.systemTemp.createTemp('decryption-output-');
|
|
});
|
|
tearDown(() async => directory.delete(recursive: true));
|
|
|
|
DownloadDecryptionDescriptor descriptor(String extension) =>
|
|
DownloadDecryptionDescriptor(
|
|
strategy: 'ffmpeg.mov_key',
|
|
key: '00112233445566778899aabbccddeeff',
|
|
outputExtension: extension,
|
|
);
|
|
|
|
String outputPath(String command) =>
|
|
FFmpegKitConfig.parseArguments(command).reversed.skip(1).first;
|
|
|
|
Future<FFmpegResult> succeed(String command) async {
|
|
await File(outputPath(command)).writeAsString('decrypted');
|
|
return FFmpegResult(success: true, returnCode: 0, output: '');
|
|
}
|
|
|
|
for (final name in [
|
|
'Song.flac',
|
|
'Song.m4a',
|
|
'Song.mp4',
|
|
'Song_converted.flac',
|
|
]) {
|
|
test('decryption retains the original name $name', () async {
|
|
final source = await File(
|
|
'${directory.path}/$name',
|
|
).writeAsString('encrypted');
|
|
final result = await FFmpegDecryption.decrypt(
|
|
inputPath: source.path,
|
|
descriptor: descriptor(
|
|
'.${source.uri.pathSegments.last.split('.').last}',
|
|
),
|
|
execute: (command) async {
|
|
expect(outputPath(command), isNot(source.path));
|
|
expect(await source.readAsString(), 'encrypted');
|
|
return succeed(command);
|
|
},
|
|
);
|
|
expect(result, source.path);
|
|
expect(await source.readAsString(), 'decrypted');
|
|
expect(await directory.list().length, 1);
|
|
});
|
|
}
|
|
|
|
test('failed FLAC attempt falls back to the original M4A name', () async {
|
|
final source = await File(
|
|
'${directory.path}/Song.m4a',
|
|
).writeAsString('encrypted');
|
|
String? failedPath;
|
|
final result = await FFmpegDecryption.decrypt(
|
|
inputPath: source.path,
|
|
descriptor: descriptor('.flac'),
|
|
execute: (command) async {
|
|
expect(await source.readAsString(), 'encrypted');
|
|
if (outputPath(command).endsWith('.flac')) {
|
|
failedPath = outputPath(command);
|
|
await File(failedPath!).writeAsString('partial');
|
|
return FFmpegResult(
|
|
success: false,
|
|
returnCode: 1,
|
|
output: 'unsupported codec',
|
|
);
|
|
}
|
|
expect(await File(failedPath!).exists(), isFalse);
|
|
return succeed(command);
|
|
},
|
|
);
|
|
expect(result, source.path);
|
|
expect(await source.readAsString(), 'decrypted');
|
|
expect(await directory.list().length, 1);
|
|
});
|
|
|
|
test(
|
|
'decryption failures and exceptions retain the source and clean outputs',
|
|
() async {
|
|
final source = await File(
|
|
'${directory.path}/Song.flac',
|
|
).writeAsString('encrypted');
|
|
for (final execute in <Future<FFmpegResult> Function(String)>[
|
|
(command) async {
|
|
await File(outputPath(command)).writeAsString('partial');
|
|
return FFmpegResult(success: false, returnCode: 1, output: 'bad key');
|
|
},
|
|
(_) async => throw StateError('execution failed'),
|
|
(_) async => FFmpegResult(success: true, returnCode: 0, output: ''),
|
|
]) {
|
|
expect(
|
|
await FFmpegDecryption.decrypt(
|
|
inputPath: source.path,
|
|
descriptor: descriptor('.flac'),
|
|
execute: execute,
|
|
),
|
|
isNull,
|
|
);
|
|
expect(await source.readAsString(), 'encrypted');
|
|
expect(await directory.list().length, 1);
|
|
}
|
|
},
|
|
);
|
|
|
|
test(
|
|
'decryption preserves an existing output and a requested original',
|
|
() async {
|
|
final source = await File(
|
|
'${directory.path}/Song.m4a',
|
|
).writeAsString('encrypted');
|
|
final sibling = await File(
|
|
'${directory.path}/Song.flac',
|
|
).writeAsString('existing');
|
|
final result = await FFmpegDecryption.decrypt(
|
|
inputPath: source.path,
|
|
descriptor: descriptor('.flac'),
|
|
deleteOriginal: false,
|
|
execute: succeed,
|
|
);
|
|
expect(result, '${directory.path}/Song (2).flac');
|
|
expect(await source.readAsString(), 'encrypted');
|
|
expect(await sibling.readAsString(), 'existing');
|
|
expect(await File(result!).readAsString(), 'decrypted');
|
|
},
|
|
);
|
|
|
|
test(
|
|
'container preparation completes while the original is available',
|
|
() async {
|
|
final source = await File(
|
|
'${directory.path}/Song.mp4',
|
|
).writeAsString('encrypted');
|
|
final result = await FFmpegDecryption.decrypt(
|
|
inputPath: source.path,
|
|
descriptor: descriptor('.mp4'),
|
|
execute: succeed,
|
|
prepareOutput: (path) async {
|
|
expect(await source.readAsString(), 'encrypted');
|
|
await File(path).writeAsString('repaired');
|
|
},
|
|
);
|
|
expect(result, source.path);
|
|
expect(await source.readAsString(), 'repaired');
|
|
expect(await directory.list().length, 1);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'encrypted audio round trip keeps its name and remains decodable',
|
|
() async {
|
|
final source = File('${directory.path}/Song.m4a');
|
|
final key = descriptor('.m4a').key;
|
|
ProcessResult generated;
|
|
try {
|
|
generated = await Process.run('ffmpeg', [
|
|
'-v',
|
|
'error',
|
|
'-f',
|
|
'lavfi',
|
|
'-i',
|
|
'sine=frequency=440:duration=0.2',
|
|
'-c:a',
|
|
'aac',
|
|
'-encryption_scheme',
|
|
'cenc-aes-ctr',
|
|
'-encryption_key',
|
|
key,
|
|
'-encryption_kid',
|
|
'ffeeddccbbaa99887766554433221100',
|
|
source.path,
|
|
]);
|
|
} on ProcessException {
|
|
markTestSkipped('Host FFmpeg is unavailable');
|
|
return;
|
|
}
|
|
expect(generated.exitCode, 0, reason: '${generated.stderr}');
|
|
final result = await FFmpegDecryption.decrypt(
|
|
inputPath: source.path,
|
|
descriptor: descriptor('.m4a'),
|
|
execute: (command) async {
|
|
final result = await Process.run(
|
|
'ffmpeg',
|
|
FFmpegKitConfig.parseArguments(command),
|
|
);
|
|
return FFmpegResult(
|
|
success: result.exitCode == 0,
|
|
returnCode: result.exitCode,
|
|
output: '${result.stderr}',
|
|
);
|
|
},
|
|
);
|
|
expect(result, source.path);
|
|
final decoded = await Process.run('ffmpeg', [
|
|
'-v',
|
|
'error',
|
|
'-xerror',
|
|
'-i',
|
|
source.path,
|
|
'-f',
|
|
'null',
|
|
'-',
|
|
]);
|
|
expect(decoded.exitCode, 0, reason: '${decoded.stderr}');
|
|
expect(await directory.list().length, 1);
|
|
},
|
|
);
|
|
}
|