yt-grabber 1.8.6 → 1.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,8 +28,10 @@ Each download can be customized to your needs for easy workflow automation.
28
28
  - [Running](#running)
29
29
  - [Packaging](#packaging)
30
30
  - [Testing](#testing)
31
+ - [Debugging](#debugging)
31
32
  - [License](#license)
32
33
  - [Legal Disclaimer](#legal-disclaimer)
34
+ - [Support Disclaimer](#support-disclaimer)
33
35
 
34
36
 
35
37
  ## Features
@@ -111,6 +113,12 @@ To execute unit tests run `npm test` or `yarn test` command (unit tests are also
111
113
 
112
114
  To execute end2end tests run `npm playwright test` or `yarn playwright test` command.
113
115
 
116
+ ## Debugging
117
+
118
+ Logs are written to `init.log` and `application.log` files.
119
+ By default only errors are logged.
120
+ To get more detailed logs run the application with `--debug-mode` arguments.
121
+
114
122
  ## License
115
123
 
116
124
  This project is licensed under the [MIT License](LICENSE).
@@ -118,3 +126,9 @@ This project is licensed under the [MIT License](LICENSE).
118
126
  ## Legal Disclaimer
119
127
 
120
128
  All music files downloaded through this software must be legally owned and purchased by the user. By downloading music via this software, you represent that you have purchased and fully own the rights to any downloaded content or an active subscription to YouTube Music. Downloading or distributing pirated or illegal music copies is strictly prohibited. I claim no ownership rights to any downloaded music files - all such rights remain with the content owner. I accept no liability for the illegal use of any files downloaded through this software.
129
+
130
+ ## Support Disclaimer
131
+
132
+ In the era of greedy services like Spotify, omnipresent AI slop and other bs it is very difficult, especially for younger, less recognizable creators to make money off of their creations. There are a lot of talented people who struggle with that (and their number is growing).
133
+ If you like certain artists and value their content please show them your support.
134
+ Thank You!
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yt-grabber",
3
- "version": "1.8.6",
3
+ "version": "1.8.7",
4
4
  "description": "Youtube Grabber - robust desktop application designed to retrieve multimedia from YouTube and YouTube Music services",
5
5
  "keywords": [
6
6
  "youtube",
@@ -121,6 +121,7 @@
121
121
  "react-router-dom": "^7.9.4",
122
122
  "usehooks-ts": "^3.1.1",
123
123
  "win-version-info": "^6.0.1",
124
+ "winston": "^3.18.3",
124
125
  "yt-dlp-wrap": "^2.3.12"
125
126
  },
126
127
  "devDependencies": {
@@ -1,10 +1,11 @@
1
1
  import ElectronStore from "electron-store";
2
2
 
3
+ import {ILogger} from "../common/Logger";
3
4
  import {IStore} from "../common/Store";
4
5
 
5
6
  declare global {
6
7
  var store: ElectronStore<IStore>;
7
-
8
+ var logger: ILogger;
8
9
  interface Global {
9
10
  [key: string]: any;
10
11
  }
@@ -1,4 +1,5 @@
1
- import {groupBy, includes, indexOf, isNumber, keys, map, replace} from "lodash-es";
1
+ import {App} from "electron";
2
+ import {forEach, groupBy, includes, indexOf, isNumber, keys, map, replace} from "lodash-es";
2
3
 
3
4
  import {VideoType} from "./Media";
4
5
  import {TrackInfo, UrlType, YoutubeInfoResult} from "./Youtube";
@@ -11,6 +12,30 @@ type NonDataAttributes<T> = Omit<T, keyof DataAttributes<T>>;
11
12
 
12
13
  export const isDev = () => process.env.NODE_ENV === "development";
13
14
 
15
+ export const isDevApplication = (app: App) => !app.isPackaged;
16
+
17
+ export const isDebugMode = () => {
18
+ const args = getProcessArgs();
19
+
20
+ return args["debug-mode"] === true;
21
+ };
22
+
23
+ export const getProcessArgs = () => {
24
+ const args = process.argv.slice(1);
25
+ const filteredArgs: Record<string, string | boolean> = {};
26
+
27
+ forEach(args, (arg) => {
28
+ if (arg.startsWith("--")) {
29
+ const argWithoutPrefix = arg.slice(2);
30
+ const [key, value] = argWithoutPrefix.split("=");
31
+
32
+ filteredArgs[key] = value !== undefined ? value : true;
33
+ }
34
+ });
35
+
36
+ return filteredArgs;
37
+ };
38
+
14
39
  export const formatFileSize = (sizeInBytes: number, decimals = 2) => {
15
40
  if (!isNumber(sizeInBytes)) return "";
16
41
  if (sizeInBytes === 0) return "0 Bytes";
@@ -0,0 +1,73 @@
1
+ import {
2
+ createLogger as createWinston, format, LeveledLogMethod, Logger, LoggerOptions, transport,
3
+ transports
4
+ } from "winston";
5
+
6
+ export interface ILoggerOptions extends LoggerOptions {
7
+ logFile?: boolean;
8
+ logFilePath?: string;
9
+ };
10
+
11
+ export interface ILogger extends Logger {
12
+ success: LeveledLogMethod;
13
+ };
14
+
15
+ const levels = {
16
+ error: 0,
17
+ warn: 1,
18
+ success: 1,
19
+ info: 3,
20
+ verbose: 4,
21
+ debug: 5,
22
+ };
23
+
24
+ const colorizer = format.colorize({
25
+ colors: {
26
+ error: "red",
27
+ warn: "yellow",
28
+ success: "green",
29
+ info: "blue",
30
+ verbose: "grey",
31
+ debug: "magenta",
32
+ },
33
+ });
34
+
35
+ const timeFormat = format.timestamp({format: "HH:mm:ss:SSS"});
36
+ const fileFormat = format.printf(({level, message, timestamp, ...meta}) => {
37
+ const metaString = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : "";
38
+
39
+ return `${timestamp} [${level.toUpperCase()}]: ${message} ${metaString}`;
40
+ });
41
+ const consoleFormat = format.printf(({level, message, timestamp, ...meta}) => {
42
+ const metaString = Object.keys(meta).length ? ` ${JSON.stringify(meta)}` : "";
43
+ const symbols = Object.getOwnPropertySymbols(meta);
44
+ const splatSymbol = symbols.find(sym => sym.description === "splat");
45
+
46
+ if (splatSymbol && typeof message === "string") {
47
+ const splat = meta[splatSymbol] as any[];
48
+
49
+ for (const item of splat) {
50
+ message = (message as string).replace(item, (match) => colorizer.colorize("verbose", match));
51
+ }
52
+ }
53
+
54
+ return `${timestamp} [${colorizer.colorize(level, level.toUpperCase())}]: ${message}${colorizer.colorize("verbose", metaString)}`;
55
+ });
56
+
57
+ export const createLogger = (options: ILoggerOptions): ILogger => {
58
+ const {level = "error", logFile, logFilePath = "log.log"} = options;
59
+ const transportsArray: transport[] = [
60
+ new transports.Console({format: format.combine(format.splat(), timeFormat, consoleFormat), level})
61
+ ];
62
+
63
+ if (logFile) {
64
+ transportsArray.push(new transports.File({filename: logFilePath, level, format: format.combine(format.splat(), timeFormat, fileFormat)}));
65
+ }
66
+
67
+ return createWinston({
68
+ level,
69
+ levels,
70
+ format: format.combine(timeFormat, format.json()),
71
+ transports: transportsArray,
72
+ }) as ILogger;
73
+ };
@@ -1,5 +1,5 @@
1
1
  import classnames from "classnames";
2
- import $_ from "lodash";
2
+ import {defaultTo, get, isEqual, map, without} from "lodash-es";
3
3
  import moment from "moment";
4
4
  import path from "path";
5
5
  import React, {HTMLAttributes, useState} from "react";
@@ -38,9 +38,9 @@ export const LanguagePicker = (props: LanguagePickerProps) => {
38
38
  const { i18n } = useTranslation();
39
39
  const [loading, setLoading] = useState(false);
40
40
  const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
41
- const langs = $_.get(i18n, "options.supportedLngs");
42
- const availableLocales: string[] = !langs ? [] : $_.without(langs, "cimode").sort();
43
- const displayMode = $_.defaultTo(
41
+ const langs = get(i18n, "options.supportedLngs");
42
+ const availableLocales: string[] = !langs ? [] : without(langs, "cimode").sort();
43
+ const displayMode = defaultTo(
44
44
  mode,
45
45
  useMediaQuery((theme: Theme) => theme.breakpoints.down("md"))
46
46
  ? ComponentDisplayMode.Minimal
@@ -58,7 +58,7 @@ export const LanguagePicker = (props: LanguagePickerProps) => {
58
58
  const onTriggerClick = (event: React.MouseEvent<HTMLButtonElement>) => setAnchorEl(event.currentTarget);
59
59
 
60
60
  const onItemClick = async (lang: string) => {
61
- if (!$_.isEqual(lang, i18n.language)) {
61
+ if (!isEqual(lang, i18n.language)) {
62
62
  setLoading(true);
63
63
  i18n.changeLanguage(lang, () => setLoading(false));
64
64
  moment.locale(lang);
@@ -81,7 +81,7 @@ export const LanguagePicker = (props: LanguagePickerProps) => {
81
81
  open={Boolean(anchorEl)}
82
82
  onClose={onClose}
83
83
  >
84
- {$_.map(availableLocales, (item) => (
84
+ {map(availableLocales, (item) => (
85
85
  <LanguagePickerItem
86
86
  key={item}
87
87
  lang={item}
@@ -1,4 +1,4 @@
1
- import $_ from "lodash";
1
+ import {defaultTo, isUndefined, max as _max, min as _min, toNumber} from "lodash-es";
2
2
  import React, {useEffect, useState} from "react";
3
3
  import {NumberFormatValues, NumericFormat, NumericFormatProps} from "react-number-format";
4
4
  import {useInterval} from "usehooks-ts";
@@ -53,7 +53,7 @@ export const NumberField = (props: INumberFieldProps) => {
53
53
  () => {
54
54
  if (decreasePressed) onDecreaseClick();
55
55
  if (increasePressed) onIncreaseClick();
56
- setDelay($_.max([50, delay - 50]));
56
+ setDelay(_max([50, delay - 50]));
57
57
  },
58
58
  decreasePressed || increasePressed ? delay : null,
59
59
  );
@@ -63,15 +63,15 @@ export const NumberField = (props: INumberFieldProps) => {
63
63
  };
64
64
 
65
65
  const onDecreaseClick = () => {
66
- const predicted = $_.toNumber(value) - $_.toNumber(step);
66
+ const predicted = toNumber(value) - toNumber(step);
67
67
 
68
- setText(loop && predicted < min ? max : $_.max([predicted, min]));
68
+ setText(loop && predicted < min ? max : _max([predicted, min]));
69
69
  };
70
70
 
71
71
  const onIncreaseClick = () => {
72
- const predicted = $_.toNumber(value) + $_.toNumber(step);
72
+ const predicted = toNumber(value) + toNumber(step);
73
73
 
74
- setText(loop && predicted > max ? min : $_.min([predicted, max]));
74
+ setText(loop && predicted > max ? min : _min([predicted, max]));
75
75
  };
76
76
 
77
77
  const onDecreaseMouseDown = () => {
@@ -93,7 +93,7 @@ export const NumberField = (props: INumberFieldProps) => {
93
93
  };
94
94
 
95
95
  const isAllowed = (values: NumberFormatValues) => {
96
- if ($_.isUndefined(values.floatValue) && !allowEmpty) {
96
+ if (isUndefined(values.floatValue) && !allowEmpty) {
97
97
  return false;
98
98
  };
99
99
 
@@ -101,7 +101,7 @@ export const NumberField = (props: INumberFieldProps) => {
101
101
  };
102
102
 
103
103
  useEffect(() => {
104
- const value = $_.toNumber(text) ?? 0;
104
+ const value = toNumber(text) ?? 0;
105
105
 
106
106
  if (onChange) {
107
107
  onChange(value);
@@ -146,7 +146,7 @@ export const NumberField = (props: INumberFieldProps) => {
146
146
  label,
147
147
  style: {textAlign: "center", width},
148
148
  },
149
- inputLabel: $_.defaultTo(inputLabelProps, {className: "upperfirst"})
149
+ inputLabel: defaultTo(inputLabelProps, {className: "upperfirst"})
150
150
  }}
151
151
  customInput={TextField}
152
152
  variant="outlined"
@@ -40,7 +40,7 @@ const useHelp = () => {
40
40
  try {
41
41
  dest.style[key as any] = computedStyle.getPropertyValue(key);
42
42
  } catch (error) {
43
- console.log(error);
43
+ logger.error("Error copying style property '%s': %O", key, error);
44
44
  }
45
45
  }
46
46
  };
package/src/index.ts CHANGED
@@ -4,24 +4,32 @@ import electronReload from "electron-reload";
4
4
  import Store from "electron-store";
5
5
  import path from "path";
6
6
 
7
+ import {isDebugMode, isDevApplication} from "./common/Helpers";
8
+ import {createLogger} from "./common/Logger";
7
9
  import {MessagingService} from "./messaging/MessagingService";
8
10
 
9
- const isDev = () => !app.isPackaged;
11
+ const logger = createLogger({
12
+ level: isDebugMode() ? "debug" : "error",
13
+ logFile: true,
14
+ logFilePath: "init.log",
15
+ });
10
16
 
11
- if (isDev()) {
17
+ if (isDevApplication(app)) {
12
18
  electronReload(__dirname, {
13
19
  electron: path.join(__dirname, "..", "node_modules", "electron", "dist", "electron.exe"),
14
20
  interval: 2000,
15
21
  });
16
- }
17
22
 
18
- /* Alternative reload using different electron binary */
23
+ /* Alternative reload using different electron binary */
19
24
 
20
- // require("electron-reload")(__dirname, {
21
- // electron: path.join(__dirname, "..", "node_modules", ".bin", "electron.cmd"),
22
- // hardReset: true,
23
- // livenessThreshold: 2000,
24
- // });
25
+ // require("electron-reload")(__dirname, {
26
+ // electron: path.join(__dirname, "..", "node_modules", ".bin", "electron.cmd"),
27
+ // hardReset: true,
28
+ // livenessThreshold: 2000,
29
+ // });
30
+
31
+ logger.debug("Electron reload initialized.");
32
+ }
25
33
 
26
34
  let mainWindow: BrowserWindow | null;
27
35
 
@@ -41,9 +49,11 @@ const createWindow = async () => {
41
49
  },
42
50
  });
43
51
  mainWindow.loadFile(path.join(__dirname, "index.html"));
52
+ logger.debug("Main window created.");
44
53
 
45
- if (isDev()) {
46
- mainWindow.webContents.openDevTools({mode: "detach",});
54
+ if (isDevApplication(app)) {
55
+ mainWindow.webContents.openDevTools({mode: "detach"});
56
+ logger.debug("DevTools opened.");
47
57
  } else {
48
58
  mainWindow.removeMenu();
49
59
  mainWindow.setMenu(null);
@@ -51,9 +61,12 @@ const createWindow = async () => {
51
61
 
52
62
  mainWindow.on("closed", () => {
53
63
  mainWindow = null;
64
+ logger.debug("Main window closed.");
54
65
  });
55
66
 
67
+
56
68
  const messaggingService = new MessagingService(ipcMain, mainWindow);
69
+ logger.debug("Messaging service initialized.");
57
70
  };
58
71
 
59
72
  app.on("ready", createWindow);
@@ -77,9 +90,9 @@ app.on("before-quit", () => {
77
90
  });
78
91
 
79
92
  app.whenReady().then(() => {
80
- if (isDev()) {
93
+ if (isDevApplication(app)) {
81
94
  installExtension(REACT_DEVELOPER_TOOLS)
82
- .then((name) => console.log(`Added Extension: ${name}`))
83
- .catch((err) => console.log("An error occurred: ", err));
95
+ .then((name) => logger.debug("Added extension: %s", name))
96
+ .catch((err) => logger.error("An error occurred: %s", err));
84
97
  }
85
98
  });
package/src/renderer.tsx CHANGED
@@ -3,11 +3,22 @@ import * as React from "react";
3
3
  import * as ReactDOM from "react-dom/client";
4
4
 
5
5
  import {Bootstrap} from "./bootstrap";
6
+ import {getProcessArgs} from "./common/Helpers";
7
+ import {createLogger} from "./common/Logger";
6
8
  import schema from "./common/Store";
7
9
 
10
+ const args = getProcessArgs();
11
+
12
+ global.logger = createLogger({
13
+ level: args["debug"] ? "debug" : "error",
14
+ logFile: true,
15
+ logFilePath: "application.log",
16
+ });
8
17
  global.store = new Store({ schema, clearInvalidConfig: true });
18
+ logger.debug("Electron store initialized.");
9
19
 
10
20
  const container = document.getElementById("root");
11
21
  const root = ReactDOM.createRoot(container!);
12
22
 
13
23
  root.render(React.createElement(Bootstrap));
24
+ logger.debug("Application rendered.");
Binary file
@@ -1,4 +1,4 @@
1
- import $_ from "lodash";
1
+ import {assign, get} from "lodash-es";
2
2
 
3
3
  import {Theme} from "@mui/material";
4
4
  import {grey} from "@mui/material/colors";
@@ -325,5 +325,5 @@ const themes: Record<string, Partial<Theme | any>> = {
325
325
  export const getThemeDefinition = (name: string, mode: "light" | "dark") => {
326
326
  const theme = themes[name];
327
327
 
328
- return $_.assign({}, theme, { palette: $_.get(theme, `palette.${mode}`) });
328
+ return assign({}, theme, { palette: get(theme, `palette.${mode}`) });
329
329
  };