Skip to content

Commit

Permalink
Allow downloading all manually set overrides
Browse files Browse the repository at this point in the history
  • Loading branch information
falko17 committed Nov 19, 2024
1 parent 37681c5 commit e823815
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 14 deletions.
24 changes: 17 additions & 7 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,26 @@ def entry_to_info(entry):
"thumbnail": entry["thumbnail"],
}

async def single_yt_url(self, id: str):
def local_match(self, id: str) -> str | None:
local_matches = [
x for x in glob.glob(f"{self.music_path}/{id}.*") if os.path.isfile(x)
]
if len(local_matches) > 0:
assert (
len(local_matches) == 1
), "More than one downloaded audio with same ID found."
if len(local_matches) == 0:
return None

assert (
len(local_matches) == 1
), "More than one downloaded audio with same ID found."
return local_matches[0]

async def single_yt_url(self, id: str):
local_match = self.local_match(id)
if local_match is not None:
# The audio has already been downloaded, so we can just use that one.
# However, we cannot use local paths in the <audio> elements, so we'll
# convert this to a base64-encoded data URL first.
extension = local_matches[0].split(".")[-1]
with open(local_matches[0], "rb") as file:
extension = local_match.split(".")[-1]
with open(local_match, "rb") as file:
return f"data:audio/{extension};base64,{base64.b64encode(file.read()).decode()}"
result = await asyncio.create_subprocess_exec(
f"{decky.DECKY_PLUGIN_DIR}/bin/yt-dlp",
Expand All @@ -106,6 +113,9 @@ async def single_yt_url(self, id: str):
return entry["url"]

async def download_yt_audio(self, id: str):
if self.local_match(id) is not None:
# Already downloaded—there's nothing we need to do.
return
process = await asyncio.create_subprocess_exec(
f"{decky.DECKY_PLUGIN_DIR}/bin/yt-dlp",
f"{id}",
Expand Down
5 changes: 4 additions & 1 deletion src/actions/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ class InvidiousAudioResolver extends AudioResolver {

async downloadAudio(video: YouTubeVideo): Promise<boolean> {
if (!video.url) {
return false;
video.url = await this.getAudioUrlFromVideo(video);
if (!video.url) {
return false;
}
}
try {
await call<[string, string]>('download_url', video.url, video.id)
Expand Down
10 changes: 7 additions & 3 deletions src/cache/musicCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@ export async function updateCache(appId: number, newData: GameThemeMusicCache) {
return newCache
}

export async function exportCache() {
export async function getFullCache(): Promise<GameThemeMusicCacheMapping> {
let fullCache: GameThemeMusicCacheMapping = {};
await localforage.iterate((value: GameThemeMusicCache, key: string, _) => fullCache[key] = value)
await call<[GameThemeMusicCacheMapping]>('export_cache', fullCache)
await localforage.iterate((value: GameThemeMusicCache, key: string, _) => { fullCache[key] = value })
return fullCache;
}

export async function exportCache() {
await call<[GameThemeMusicCacheMapping]>('export_cache', await getFullCache())
}

export async function importCache(name: string) {
Expand Down
66 changes: 63 additions & 3 deletions src/components/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,28 @@ import {
DropdownItem,
Menu,
MenuItem,
ModalRoot,
ModalRootProps,
PanelSection,
PanelSectionRow,
ProgressBar,
ProgressBarWithInfo,
showContextMenu,
showModal,
ShowModalResult,
SimpleModal,
SingleDropdownOption,
SliderField,
ToggleField
} from '@decky/ui'
import React, { useMemo } from 'react'
import React, { useMemo, useState } from 'react'
import { useSettings } from '../../hooks/useSettings'
import useTranslations from '../../hooks/useTranslations'
import { FaDownload, FaUndo, FaSave, FaVolumeMute, FaVolumeUp, FaYoutube } from 'react-icons/fa'
import { clearCache, clearDownloads, exportCache, importCache, listCacheBackups } from '../../cache/musicCache'
import { clearCache, clearDownloads, exportCache, getCache, getFullCache, importCache, listCacheBackups } from '../../cache/musicCache'
import useInvidiousInstances from '../../hooks/useInvidiousInstances'
import { toaster } from '@decky/api'
import { getResolver } from '../../actions/audio'

export default function Index() {
const {
Expand Down Expand Up @@ -65,6 +71,17 @@ export default function Index() {
);
}

const confirmRestoreDownloads = async () => {
const num = Object.values(await getFullCache()).length;
const modal = showModal(
<ConfirmModal
strTitle={t('restoreDownloadsConfirm')}
strDescription={t('restoreDownloadsConfirmDescription', { num })}
onOK={() => restoreDownloads(modal)}
/>
);
}

function restoreCache(backup: string) {
showModal(
<ConfirmModal
Expand All @@ -78,6 +95,39 @@ export default function Index() {
)
}

async function restoreDownloads(modal: ShowModalResult) {

function getProgressModal(index: number, total: number) {
const current = index + 1;
const progress = current * 100 / total;
return <ConfirmModal bHideCloseIcon={true} bOKDisabled={true}
onCancel={modal.Close}
strCancelButtonText={t('close')}
strTitle={
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', width: '100%' }}>
{t('restoreDownloadsOperationTitle')}
<div style={{ marginLeft: 'auto' }}>
<ProgressBarWithInfo nProgress={progress} sOperationText={t('restoreDownloadsOperation', { current, total })} />
</div>
</div>
}>
</ConfirmModal>
}

const cached = Object.values(await getFullCache());
const resolver = getResolver(settings.useYtDlp);

for (let index = 0; index < cached.length; index++) {
const element = cached[index];
if (element.videoId !== undefined) {
modal.Update(getProgressModal(index, cached.length));
await resolver.downloadAudio({ id: element.videoId });
}
}
modal.Close();
toaster.toast({ title: t('downloadRestoreSuccessful'), body: t('downloadRestoreSuccessfulDetails'), icon: <FaDownload />, duration: 1500 })
}

return (
<div>
<PanelSection title={t('settings')}>
Expand Down Expand Up @@ -150,13 +200,23 @@ export default function Index() {
<ButtonItem
label={t('deleteDownloadsLabel')}
description={t('deleteDownloadsDescription')}
bottomSeparator="none"
layout="below"
onClick={() => confirmClearDownloads()}
>
{t('deleteDownloads')}
</ButtonItem>
</PanelSectionRow>
<PanelSectionRow>
<ButtonItem
label={t('restoreDownloadsLabel')}
description={t('restoreDownloadsDescription')}
bottomSeparator="none"
layout="below"
onClick={() => confirmRestoreDownloads()}
>
{t('restoreDownloads')}
</ButtonItem>
</PanelSectionRow>
</PanelSection>
<PanelSection title={t('overrides')}>
<PanelSectionRow>
Expand Down
10 changes: 10 additions & 0 deletions src/localisation/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"backupSuccessfulDetails": "Überschreibungen wurden gesichert.",
"changeThemeMusic": "Ändere das Titellied",
"clear": "Löschen",
"close": "Schließen",
"defaultMuted": "Stummschalten",
"defaultMutedDescription": "Musik nicht abspielen, außer manuell aktiviert",
"deleteDownloads": "Downloads löschen",
Expand All @@ -23,6 +24,8 @@
"downloadAudioDescription": "Lade die ausgewählte Musik herunter anstatt sie zu streamen. Gilt nur für neu ausgewählte Musik.",
"downloadFailed": "Herunterladen fehlgeschlagen",
"downloadFailedDetail": "Konnte diese Musik nicht herunterladen.",
"downloadRestoreSuccessful": "Downloads erfolgreich",
"downloadRestoreSuccessfulDetails": "Alle fehlenden Lieder wurden heruntergeladen.",
"gameSettings": "Spiel-Einstellungen",
"gameVolumeDescription": "Musik-Lautstärke für dieses Spiel anpassen",
"noMusicLabel": "Keine Musik",
Expand All @@ -32,6 +35,13 @@
"play": "Abspielen",
"reset": "Zurücksetzen",
"resetVolume": "Zurücksetzen",
"restoreDownloads": "Downloads wiederherstellen",
"restoreDownloadsConfirm": "Wirklich alle fehlenden Lieder herunterladen?",
"restoreDownloadsConfirmDescription": "Dies wird bis zu {num} Lieder herunterladen und kann eine Weile dauern.",
"restoreDownloadsDescription": "Lädt alle Lieder aus Überschreibungen herunter, die noch nicht heruntergeladen wurden.",
"restoreDownloadsLabel": "Fehlende Downloads wiederherstellen",
"restoreDownloadsOperationTitle": "Lade fehlende Lieder...",
"restoreDownloadsOperation": "Lade {current}/{total} herunter...",
"restoreOverrides": "Backup wählen",
"restoreOverridesConfirm": "Dieses Backup wiederherstellen?",
"restoreOverridesConfirmDetails": "Dies wird deine aktuellen Überschreibungen verwerfen!",
Expand Down
10 changes: 10 additions & 0 deletions src/localisation/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"backupSuccessful": "Backup successful",
"backupSuccessfulDetails": "Overrides have been backed up.",
"changeThemeMusic": "Change Theme Music",
"close": "Close",
"defaultMuted": "Default Muted",
"defaultMutedDescription": "Don't play music unless manually set",
"deleteDownloads": "Delete downloads",
Expand All @@ -17,6 +18,8 @@
"deleteOverridesConfirm": "Are you sure you want to delete all overrides?",
"deleteOverridesDescription": "This will also delete all backups of your overrides!",
"deleteOverridesLabel": "Delete all overrides",
"downloadRestoreSuccessful": "Downloads successful",
"downloadRestoreSuccessfulDetails": "All missing songs have been downloaded.",
"download": "Download",
"downloadAudio": "Download Music",
"downloadAudioDescription": "Download selected music instead of streaming it. Only applies to newly made selections.",
Expand All @@ -31,6 +34,13 @@
"play": "Play",
"reset": "Reset",
"resetVolume": "Reset",
"restoreDownloads": "Restore downloads",
"restoreDownloadsConfirm": "Download all missing songs?",
"restoreDownloadsConfirmDescription": "This will download up to {num} songs and might take a while.",
"restoreDownloadsDescription": "Download all songs from manually set overrides that haven't been downloaded yet.",
"restoreDownloadsLabel": "Restore missing downloads",
"restoreDownloadsOperationTitle": "Downloading missing songs...",
"restoreDownloadsOperation": "Downloading {current}/{total}...",
"restoreOverrides": "Select backup",
"restoreOverridesConfirm": "Restore this backup?",
"restoreOverridesConfirmDetails": "This will overwrite all your current overrides!",
Expand Down

0 comments on commit e823815

Please sign in to comment.