Skip to content

Commit

Permalink
Added transcoded downloads. This is mostly just a port of the code in j…
Browse files Browse the repository at this point in the history
…mshrv#496 into the new downloads system.
  • Loading branch information
Komodo5197 committed Feb 9, 2024
1 parent 458738d commit 84c21a5
Show file tree
Hide file tree
Showing 17 changed files with 1,146 additions and 110 deletions.
157 changes: 132 additions & 25 deletions lib/components/AlbumScreen/download_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'dart:async';

import 'package:file_sizes/file_sizes.dart';
import 'package:finamp/models/jellyfin_models.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:get_it/get_it.dart';
Expand All @@ -8,17 +10,24 @@ import '../../models/finamp_models.dart';
import '../../services/finamp_settings_helper.dart';
import '../../services/finamp_user_helper.dart';
import '../../services/isar_downloads.dart';
import '../../services/jellyfin_api_helper.dart';
import '../global_snackbar.dart';

class DownloadDialog extends StatefulWidget {
const DownloadDialog._build({
super.key,
required this.item,
required this.viewId,
required this.needDirectory,
required this.needTranscode,
required this.children,
});

final DownloadStub item;
final String viewId;
final bool needDirectory;
final bool needTranscode;
final List<BaseItemDto>? children;

@override
State<DownloadDialog> createState() => _DownloadDialogState();
Expand All @@ -29,62 +38,150 @@ class DownloadDialog extends StatefulWidget {
final finampUserHelper = GetIt.instance<FinampUserHelper>();
viewId = finampUserHelper.currentUser!.currentViewId;
}
if (FinampSettingsHelper.finampSettings.downloadLocationsMap.values
bool needTranscode =
FinampSettingsHelper.finampSettings.shouldTranscodeDownloads ==
TranscodeDownloadsSetting.ask;
bool needDownload = FinampSettingsHelper
.finampSettings.downloadLocationsMap.values
.where((element) =>
element.baseDirectory != DownloadLocationType.internalDocuments)
.length ==
1) {
.length !=
1;
if (!needTranscode && !needDownload) {
final isarDownloads = GetIt.instance<IsarDownloads>();
unawaited(isarDownloads
.addDownload(
stub: item,
viewId: viewId!,
downloadLocation:
FinampSettingsHelper.finampSettings.internalSongDir)
FinampSettingsHelper.finampSettings.internalSongDir,
transcodeProfile: FinampSettingsHelper
.finampSettings.shouldTranscodeDownloads ==
TranscodeDownloadsSetting.always
? FinampSettingsHelper
.finampSettings.downloadTranscodingProfile
: null)
.then((value) => GlobalSnackbar.message(
(scaffold) => AppLocalizations.of(scaffold)!.downloadsAdded)));
} else {
JellyfinApiHelper jellyfinApiHelper = GetIt.instance<JellyfinApiHelper>();
List<BaseItemDto>? children;
if (item.baseItemType == BaseItemDtoType.album ||
item.baseItemType == BaseItemDtoType.playlist) {
children = await jellyfinApiHelper.getItems(
parentItem: item.baseItem!,
includeItemTypes: "Audio",
fields: "${jellyfinApiHelper.defaultFields},MediaSources");
}
if (!context.mounted) return;
await showDialog(
context: context,
builder: (context) => DownloadDialog._build(
item: item,
viewId: viewId!,
),
item: item,
viewId: viewId!,
needDirectory: needDownload,
needTranscode: needTranscode,
children: children),
);
}
}
}

class _DownloadDialogState extends State<DownloadDialog> {
DownloadLocation? selectedDownloadLocation;
bool? transcode;

@override
Widget build(BuildContext context) {
String originalDescription = "null";
String transcodeDescription = "null";
var profile =
FinampSettingsHelper.finampSettings.downloadTranscodingProfile;

if (widget.children != null) {
final originalFileSize = widget.children!
.map((e) => e.mediaSources?.first.size ?? 0)
.fold(0, (a, b) => a + b);

final transcodedFileSize = widget.children!
.map((e) => e.mediaSources?.first.transcodedSize(FinampSettingsHelper
.finampSettings.downloadTranscodingProfile.bitrateChannels))
.fold(0, (a, b) => a + (b ?? 0));

final originalFileSizeFormatted = FileSize.getSize(
originalFileSize,
precision: PrecisionValue.None,
);

final formats = widget.children!
.map((e) => e.mediaSources?.first.mediaStreams.first.codec)
.toSet();

transcodeDescription = FileSize.getSize(
transcodedFileSize,
precision: PrecisionValue.None,
);

originalDescription =
"$originalFileSizeFormatted ${formats.length == 1 ? formats.first!.toUpperCase() : "null"}";
}

return AlertDialog(
title: Text(AppLocalizations.of(context)!.addDownloads),
content: DropdownButton<DownloadLocation>(
hint: Text(AppLocalizations.of(context)!.location),
isExpanded: true,
onChanged: (value) => setState(() {
selectedDownloadLocation = value;
}),
value: selectedDownloadLocation,
items: FinampSettingsHelper.finampSettings.downloadLocationsMap.values
.where((element) =>
element.baseDirectory !=
DownloadLocationType.internalDocuments)
.map((e) => DropdownMenuItem<DownloadLocation>(
value: e,
child: Text(e.name),
))
.toList()),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.needDirectory)
DropdownButton<DownloadLocation>(
hint: Text(AppLocalizations.of(context)!.location),
isExpanded: true,
onChanged: (value) => setState(() {
selectedDownloadLocation = value;
}),
value: selectedDownloadLocation,
items: FinampSettingsHelper
.finampSettings.downloadLocationsMap.values
.where((element) =>
element.baseDirectory !=
DownloadLocationType.internalDocuments)
.map((e) => DropdownMenuItem<DownloadLocation>(
value: e,
child: Text(e.name),
))
.toList()),
if (widget.needTranscode)
DropdownButton<bool>(
hint: Text(AppLocalizations.of(context)!.transcodeHint),
isExpanded: true,
onChanged: (value) => setState(() {
transcode = value;
}),
value: transcode,
items: [
DropdownMenuItem<bool>(
value: true,
child: Text(AppLocalizations.of(context)!.doTranscode(
profile.bitrateKbps,
profile.codec.name.toUpperCase(),
transcodeDescription)),
),
DropdownMenuItem<bool>(
value: false,
child: Text(AppLocalizations.of(context)!
.dontTranscode(originalDescription)),
)
]),
],
),
actions: [
TextButton(
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
onPressed: selectedDownloadLocation == null
onPressed: (selectedDownloadLocation == null &&
widget.needDirectory) ||
(transcode == null && widget.needTranscode)
? null
: () async {
Navigator.of(context).pop();
Expand All @@ -93,7 +190,17 @@ class _DownloadDialogState extends State<DownloadDialog> {
.addDownload(
stub: widget.item,
viewId: widget.viewId,
downloadLocation: selectedDownloadLocation!)
downloadLocation: (widget.needDirectory
? selectedDownloadLocation
: FinampSettingsHelper
.finampSettings.internalSongDir)!,
transcodeProfile: (widget.needTranscode
? transcode
: FinampSettingsHelper.finampSettings
.shouldTranscodeDownloads ==
TranscodeDownloadsSetting.always)!
? profile
: null)
.onError(
(error, stackTrace) => GlobalSnackbar.error(error));

Expand Down
5 changes: 1 addition & 4 deletions lib/components/AlbumScreen/item_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ class ItemInfo extends StatelessWidget {
),
IconAndText(
iconData: Icons.timer,
text: printDuration(Duration(
microseconds:
item.runTimeTicks == null ? 0 : item.runTimeTicks! ~/ 10,
)),
text: printDuration(item.runTimeTicksDuration()),
),
if (item.type != "Playlist")
IconAndText(iconData: Icons.event, text: item.productionYearString)
Expand Down
13 changes: 5 additions & 8 deletions lib/components/AlbumScreen/song_list_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,7 @@ class _SongListTileState extends ConsumerState<SongListTile>
alignment: PlaceholderAlignment.top,
),
TextSpan(
text: printDuration(Duration(
microseconds: (widget.item.runTimeTicks == null
? 0
: widget.item.runTimeTicks! ~/ 10))),
text: printDuration(widget.item.runTimeTicksDuration()),
style: TextStyle(
color: Theme.of(context)
.textTheme
Expand Down Expand Up @@ -215,10 +212,10 @@ class _SongListTileState extends ConsumerState<SongListTile>
: (details) async {
unawaited(Feedback.forLongPress(context));
await showModalSongMenu(
context: context,
item: widget.item,
isInPlaylist: widget.isInPlaylist,
onRemoveFromList: widget.onRemoveFromList,
context: context,
item: widget.item,
isInPlaylist: widget.isInPlaylist,
onRemoveFromList: widget.onRemoveFromList,
);
},
onTap: () async {
Expand Down
2 changes: 1 addition & 1 deletion lib/components/DownloadsScreen/downloaded_items_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class _DownloadedItemsListState extends State<DownloadedItemsList> {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
DownloadStub album = snapshot.data!.elementAt(index);
DownloadItem album = snapshot.data!.elementAt(index);
return ExpansionTile(
key: PageStorageKey(album.id),
leading: AlbumImage(item: album.baseItem),
Expand Down
16 changes: 9 additions & 7 deletions lib/components/DownloadsScreen/item_file_size.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import '../../services/isar_downloads.dart';
class ItemFileSize extends ConsumerWidget {
const ItemFileSize({super.key, required this.item});

final DownloadStub item;
final DownloadItem item;

@override
Widget build(BuildContext context, WidgetRef ref) {
Expand All @@ -32,12 +32,14 @@ class ItemFileSize extends ConsumerWidget {
case DownloadItemState.failed:
case DownloadItemState.complete:
if (item.type == DownloadItemType.song) {
var mediaSourceInfo = item.baseItem?.mediaSources?[0];
if (mediaSourceInfo == null) {
return "??? MB Unknown";
} else {
return "${FileSize.getSize(mediaSourceInfo.size)} ${mediaSourceInfo.container?.toUpperCase()}";
}
var codec = item.transcodingProfile?.codec.name ??
item.baseItem?.mediaSources?[0].container ??
"";
return isarDownloader.getFileSize(item).then((value) =>
AppLocalizations.of(context)!.downloadInfo(
item.transcodingProfile?.bitrateKbps ?? "null",
codec.toUpperCase(),
FileSize.getSize(value)));
} else {
return isarDownloader
.getFileSize(item)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:hive/hive.dart';

import '../../services/finamp_settings_helper.dart';
import '../../models/finamp_models.dart';
import '../../services/finamp_settings_helper.dart';

class BitrateSelector extends StatelessWidget {
const BitrateSelector({Key? key}) : super(key: key);
Expand Down
3 changes: 1 addition & 2 deletions lib/components/album_list_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ class _AlbumListTileState extends State<AlbumListTile> {
?.withOpacity(0.7)),
),
TextSpan(
text:
" · ${printDuration(Duration(microseconds: (widget.item.runTimeTicks == null ? 0 : widget.item.runTimeTicks! ~/ 10)))}",
text: " · ${printDuration(widget.item.runTimeTicksDuration())}",
style: TextStyle(color: Theme.of(context).disabledColor),
),
],
Expand Down
Loading

0 comments on commit 84c21a5

Please sign in to comment.