diff --git a/.gitignore b/.gitignore
index 28d2c1c..696a2f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,4 @@ src/server/core/yt-dlp
*.ytdl
*.part
*.db
-*.DS_Store
\ No newline at end of file
+.DS_Store
\ No newline at end of file
diff --git a/README.md b/README.md
index c084805..12ae432 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,8 @@ Refactoring and JSDoc.
04/01/22: Background jobs now are retrieved!! It's still rudimentary but it leverages on yt-dlp resume feature
05/05/22: Material UI update
+
+03/06/22: The most requested feature finally implemented: Format Selection!!
```
@@ -49,15 +51,30 @@ The avaible settings are currently only:
- Switch theme
- Extract audio
- Switch language
+- Optional format selection
+## Format selection
+
+
+
+This feature is disabled by default as this WebUI/Wrapper/Software/Bunch of Code is intended to be used to retrieve the best quality automatically.
+
+To enable it go to the settings page:
+
+
+
+And set it :D
+
Future releases will have:
- ~~Multi download~~ *done*
- ~~Exctract audio~~ *done*
-- Format selection *in-progess*
+- ~~Format selection~~ *done*
+- Download archive
+- ARM Build
## Troubleshooting
- **It says that it isn't connected/ip in the footer is not defined.**
@@ -105,8 +122,3 @@ node dist/main.js
- Well, yes (until now).
- **Why is it so slow to start a download?**
- I genuinely don't know. I know that standalone yt-dlp is slow to start up even on my M1 Mac, so....
-
-## Todo list
-- ~~retrieve background tasks~~
-- format selection
-- better ui/ux
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index f9d05b4..1ebd00e 100755
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -237,8 +237,8 @@ function AppContent() {
>
- }>
- }>
+ }>
+ }>
}>
diff --git a/frontend/src/Home.tsx b/frontend/src/Home.tsx
index cdeeca1..e67949a 100644
--- a/frontend/src/Home.tsx
+++ b/frontend/src/Home.tsx
@@ -1,18 +1,16 @@
-import { Backdrop, Button, CircularProgress, Container, Grid, Paper, Snackbar, TextField, } from "@mui/material";
+import { Backdrop, Button, ButtonGroup, CircularProgress, Container, Grid, Paper, Skeleton, Snackbar, TextField, Typography, } from "@mui/material";
import React, { Fragment, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
-import { Socket } from "socket.io-client";
+import { io, Socket } from "socket.io-client";
import { StackableResult } from "./components/StackableResult";
import { connected, disconnected, downloading, finished } from "./features/status/statusSlice";
import { IDLInfo, IDLInfoBase, IDownloadInfo, IMessage } from "./interfaces";
import { RootState } from "./stores/store";
-import { updateInStateMap, } from "./utils";
+import { toFormatArgs, updateInStateMap, } from "./utils";
-type Props = {
- socket: Socket
-}
+let socket: Socket;
-export default function Home({ socket }: Props) {
+export default function Home() {
// redux state
const settings = useSelector((state: RootState) => state.settings)
const status = useSelector((state: RootState) => state.status)
@@ -22,11 +20,23 @@ export default function Home({ socket }: Props) {
const [progressMap, setProgressMap] = useState(new Map());
const [messageMap, setMessageMap] = useState(new Map());
const [downloadInfoMap, setDownloadInfoMap] = useState(new Map());
+ const [downloadFormats, setDownloadFormats] = useState();
+ const [pickedVideoFormat, setPickedVideoFormat] = useState('');
+ const [pickedAudioFormat, setPickedAudioFormat] = useState('');
+ const [pickedBestFormat, setPickedBestFormat] = useState('');
const [url, setUrl] = useState('');
+ const [workingUrl, setWorkingUrl] = useState('');
const [showBackdrop, setShowBackdrop] = useState(false);
/* -------------------- Effects -------------------- */
+ useEffect(() => {
+ socket = io(`http://${localStorage.getItem('server-addr') || 'localhost'}:3022`);
+ return () => {
+ socket.disconnect()
+ };
+ }, [])
+
/* WebSocket connect event handler*/
useEffect(() => {
socket.on('connect', () => {
@@ -53,6 +63,7 @@ export default function Home({ socket }: Props) {
socket.on('available-formats', (data: IDownloadInfo) => {
setShowBackdrop(false)
console.log(data)
+ setDownloadFormats(data);
})
}, [])
@@ -89,11 +100,37 @@ export default function Home({ socket }: Props) {
* Retrive url from input, cli-arguments from checkboxes and emits via WebSocket
*/
const sendUrl = () => {
+ const codes = new Array();
+ if (pickedVideoFormat !== '') codes.push(pickedVideoFormat);
+ if (pickedAudioFormat !== '') codes.push(pickedAudioFormat);
+ if (pickedBestFormat !== '') codes.push(pickedBestFormat);
+
socket.emit('send-url', {
- url: url,
- params: settings.cliArgs.toString(),
+ url: url || workingUrl,
+ params: settings.cliArgs.toString() + toFormatArgs(codes),
})
setUrl('')
+ setWorkingUrl('')
+ setTimeout(() => {
+ const input = document.getElementById('urlInput') as HTMLInputElement;
+ input.value = '';
+ setShowBackdrop(true);
+ setDownloadFormats(null);
+ }, 250);
+ }
+
+ /**
+ * Retrive url from input and display the formats selection view
+ */
+ const sendUrlFormatSelection = () => {
+ socket.emit('send-url-format-selection', {
+ url: url,
+ })
+ setWorkingUrl(url)
+ setUrl('')
+ setPickedAudioFormat('');
+ setPickedVideoFormat('');
+ setPickedBestFormat('');
setTimeout(() => {
const input = document.getElementById('urlInput') as HTMLInputElement;
input.value = '';
@@ -120,6 +157,7 @@ export default function Home({ socket }: Props) {
socket.emit('abort', { pid: id })
return
}
+ setDownloadFormats(null)
socket.emit('abort-all')
}
@@ -145,13 +183,14 @@ export default function Home({ socket }: Props) {
label={settings.i18n.t('urlInput')}
variant="outlined"
onChange={handleUrlChange}
+ disabled={settings.formatSelection && downloadFormats != null}
/>
@@ -168,6 +207,110 @@ export default function Home({ socket }: Props) {
+ {/* Format Selection grid */}
+ {downloadFormats ?
+
+
+
+
+
+ {downloadFormats.title}
+
+ {/* */}
+
+
+
+
+ {/* video only */}
+
+
+ Best quality
+
+
+
+
+
+ {/* video only */}
+
+
+ Video data
+
+
+ {downloadFormats.formats
+ .filter(format => format.acodec === 'none' && format.vcodec !== 'none')
+ .map((format, idx) => (
+
+
+
+ ))
+ }
+
+
+ Audio data
+
+
+ {downloadFormats.formats
+ .filter(format => format.acodec !== 'none' && format.vcodec === 'none')
+ .map((format, idx) => (
+
+
+
+ ))
+ }
+
+
+
+
+
+
+
+
+
+ : null}
{ /*Super big brain flatMap moment*/
Array
diff --git a/frontend/src/Settings.tsx b/frontend/src/Settings.tsx
index df0f612..2012767 100644
--- a/frontend/src/Settings.tsx
+++ b/frontend/src/Settings.tsx
@@ -19,17 +19,15 @@ import {
} from "@mui/material";
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
-import { Socket } from "socket.io-client";
-import { LanguageUnion, setCliArgs, setLanguage, setServerAddr, setTheme, ThemeUnion } from "./features/settings/settingsSlice";
+import { io } from "socket.io-client";
+import { LanguageUnion, setCliArgs, setFormatSelection, setLanguage, setServerAddr, setTheme, ThemeUnion } from "./features/settings/settingsSlice";
import { alreadyUpdated, updated } from "./features/status/statusSlice";
import { RootState } from "./stores/store";
import { validateDomain, validateIP } from "./utils";
-type Props = {
- socket: Socket
-}
+const socket = io(`http://${localStorage.getItem('server-addr') || 'localhost'}:3022`)
-export default function Settings({ socket }: Props) {
+export default function Settings() {
const settings = useSelector((state: RootState) => state.settings)
const status = useSelector((state: RootState) => state.status)
const dispatch = useDispatch()
@@ -155,6 +153,15 @@ export default function Settings({ socket }: Props) {
}
label={settings.i18n.t('extractAudioCheckbox')}
/>
+ dispatch(setFormatSelection(!settings.formatSelection))}
+ />
+ }
+ label={settings.i18n.t('formatSelectionEnabler')}
+ />