yt-grabber 1.8.3 → 1.8.5

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.
Files changed (39) hide show
  1. package/.swcrc +20 -0
  2. package/eslint.config.ts +61 -60
  3. package/jest.config.ts +21 -0
  4. package/jest.setup.ts +33 -0
  5. package/package.json +14 -4
  6. package/playwright.config.ts +79 -79
  7. package/src/@types/global.d.ts +4 -0
  8. package/src/@types/i18next-scanner-webpack.d.ts +1 -1
  9. package/src/@types/jest.d.ts +2 -0
  10. package/src/@types/webpack-node-externals.d.ts +1 -0
  11. package/src/App.test.tsx +15 -0
  12. package/src/App.tsx +2 -2
  13. package/src/common/Helpers.ts +36 -5
  14. package/src/common/Media.ts +3 -0
  15. package/src/common/Store.ts +5 -0
  16. package/src/common/YtdplUtils.ts +93 -0
  17. package/src/components/appBar/AppBar.tsx +3 -4
  18. package/src/components/logo/Logo.test.tsx +12 -0
  19. package/src/components/progress/Progress.test.tsx +31 -0
  20. package/src/components/progress/Progress.tsx +4 -3
  21. package/src/components/youtube/formatSelector/FormatSelector.styl +35 -0
  22. package/src/components/youtube/formatSelector/FormatSelector.tsx +44 -4
  23. package/src/components/youtube/inputModePicker/InputModePicker.tsx +2 -2
  24. package/src/components/youtube/inputPanel/InputPanel.tsx +11 -11
  25. package/src/i18next.test.ts +42 -0
  26. package/src/i18next.ts +0 -21
  27. package/src/index.ts +11 -7
  28. package/src/resources/bin/gifsicle.exe +0 -0
  29. package/src/resources/locales/de-DE/help.json +7 -1
  30. package/src/resources/locales/de-DE/translation.json +3 -0
  31. package/src/resources/locales/en-GB/help.json +7 -1
  32. package/src/resources/locales/en-GB/translation.json +3 -0
  33. package/src/resources/locales/pl-PL/help.json +7 -1
  34. package/src/resources/locales/pl-PL/translation.json +3 -0
  35. package/src/views/home/HomeView.tsx +54 -6
  36. package/tests/FileMock.ts +1 -0
  37. package/tests/TestRenderer.tsx +40 -0
  38. package/tsconfig.json +6 -3
  39. package/webpack.config.ts +6 -7
package/.swcrc ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "jsc": {
3
+ "target": "es2020",
4
+ "parser": {
5
+ "syntax": "typescript",
6
+ "tsx": true,
7
+ "decorators": false
8
+ },
9
+ "transform": {
10
+ "react": {
11
+ "runtime": "automatic",
12
+ "development": true
13
+ }
14
+ }
15
+ },
16
+ "module": {
17
+ "type": "commonjs"
18
+ },
19
+ "sourceMaps": true
20
+ }
package/eslint.config.ts CHANGED
@@ -1,61 +1,62 @@
1
- import react from "eslint-plugin-react";
2
- import {defineConfig} from "eslint/config";
3
- import globals from "globals";
4
- import path from "node:path";
5
- import {fileURLToPath} from "node:url";
6
-
7
- import {FlatCompat} from "@eslint/eslintrc";
8
- import js from "@eslint/js";
9
- import typescriptEslint from "@typescript-eslint/eslint-plugin";
10
- import tsParser from "@typescript-eslint/parser";
11
-
12
- const __filename = fileURLToPath(import.meta.url);
13
- const __dirname = path.dirname(__filename);
14
- const compat = new FlatCompat({
15
- baseDirectory: __dirname,
16
- recommendedConfig: js.configs.recommended,
17
- allConfig: js.configs.all
18
- });
19
-
20
- export default defineConfig([{
21
- extends: compat.extends(
22
- "eslint:recommended",
23
- "plugin:react/recommended",
24
- "plugin:@typescript-eslint/recommended",
25
- "prettier",
26
- ),
27
-
28
- plugins: {
29
- react,
30
- "@typescript-eslint": typescriptEslint as any,
31
- },
32
-
33
- languageOptions: {
34
- globals: {
35
- ...globals.browser,
36
- },
37
-
38
- parser: tsParser,
39
- ecmaVersion: "latest",
40
- sourceType: "module",
41
- },
42
-
43
- settings: {
44
- react: {
45
- version: "detect",
46
- },
47
- },
48
-
49
- rules: {
50
- indent: ["warn", 4],
51
- "linebreak-style": ["warn", "windows"],
52
- quotes: ["warn", "double"],
53
- semi: ["warn", "always"],
54
- "@typescript-eslint/no-unused-vars": ["warn"],
55
- "@typescript-eslint/no-var-requires": 0,
56
- "@typescript-eslint/no-explicit-any": 0,
57
- "@typescript-eslint/no-empty-interface": 0,
58
- "react/prop-types": 0,
59
- "react/display-name": 0,
60
- },
1
+ import react from "eslint-plugin-react";
2
+ import {defineConfig} from "eslint/config";
3
+ import globals from "globals";
4
+ import path from "node:path";
5
+ import {fileURLToPath} from "node:url";
6
+
7
+ import {FlatCompat} from "@eslint/eslintrc";
8
+ import js from "@eslint/js";
9
+ import typescriptEslint from "@typescript-eslint/eslint-plugin";
10
+ import tsParser from "@typescript-eslint/parser";
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ const compat = new FlatCompat({
15
+ baseDirectory: __dirname,
16
+ recommendedConfig: js.configs.recommended,
17
+ allConfig: js.configs.all
18
+ });
19
+
20
+ export default defineConfig([{
21
+ extends: compat.extends(
22
+ "eslint:recommended",
23
+ "plugin:react/recommended",
24
+ "plugin:@typescript-eslint/recommended",
25
+ "prettier",
26
+ ),
27
+
28
+ plugins: {
29
+ react,
30
+ "@typescript-eslint": typescriptEslint as any,
31
+ },
32
+
33
+ languageOptions: {
34
+ globals: {
35
+ ...globals.browser,
36
+ },
37
+
38
+ parser: tsParser,
39
+ ecmaVersion: "latest",
40
+ sourceType: "module",
41
+ },
42
+
43
+ settings: {
44
+ react: {
45
+ version: "detect",
46
+ },
47
+ },
48
+
49
+ rules: {
50
+ indent: ["warn", 4],
51
+ "linebreak-style": ["warn", "windows"],
52
+ quotes: ["warn", "double"],
53
+ semi: ["warn", "always"],
54
+ "@typescript-eslint/no-unused-vars": ["warn"],
55
+ "@typescript-eslint/no-var-requires": 0,
56
+ "@typescript-eslint/no-explicit-any": 0,
57
+ "@typescript-eslint/no-empty-interface": 0,
58
+ "react/prop-types": 0,
59
+ "react/display-name": 0,
60
+ "react/react-in-jsx-scope": 0,
61
+ },
61
62
  }]);
package/jest.config.ts ADDED
@@ -0,0 +1,21 @@
1
+ import type {Config} from "jest";
2
+
3
+ const config: Config = {
4
+ rootDir: ".",
5
+ testEnvironment: "jsdom",
6
+ transform: {
7
+ "^.+\\.(t|j)sx?$": ["@swc/jest", {}],
8
+ },
9
+ moduleNameMapper: {
10
+ "\\.(css|less|scss|sass|styl)$": "identity-obj-proxy",
11
+ "\\.(jpg|jpeg|png|gif|svg)$": "<rootDir>/tests/FileMock.ts",
12
+ "^@tests/(.*)$": "<rootDir>/tests/$1"
13
+ },
14
+ setupFilesAfterEnv: [
15
+ "@testing-library/jest-dom",
16
+ "<rootDir>/jest.setup.ts",
17
+ ],
18
+ testPathIgnorePatterns: ["/node_modules/", "/dist/", "/out/", "/e2e/"],
19
+ };
20
+
21
+ export default config;
package/jest.setup.ts ADDED
@@ -0,0 +1,33 @@
1
+ import {TextDecoder, TextEncoder} from "util";
2
+
3
+ global.TextEncoder = TextEncoder;
4
+ global.TextDecoder = TextDecoder as typeof global.TextDecoder;
5
+
6
+ const mockStore = {
7
+ get: jest.fn().mockReturnValue({language: "en"}),
8
+ set: jest.fn(),
9
+ delete: jest.fn(),
10
+ clear: jest.fn(),
11
+ has: jest.fn().mockReturnValue(true),
12
+ onDidAnyChange: jest.fn().mockReturnValue(jest.fn()),
13
+ onDidChange: jest.fn().mockReturnValue(jest.fn())
14
+ };
15
+
16
+ Object.defineProperty(process, "resourcesPath", {
17
+ value: "/mocked/resources/path",
18
+ writable: false,
19
+ });
20
+
21
+ jest.mock("electron", () => ({
22
+ ipcRenderer: {
23
+ send: jest.fn(),
24
+ on: jest.fn(),
25
+ off: jest.fn(),
26
+ },
27
+ }));
28
+
29
+ jest.mock("electron-store", () => {
30
+ return jest.fn(() => mockStore);
31
+ });
32
+
33
+ (global as any).store = mockStore;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yt-grabber",
3
- "version": "1.8.3",
3
+ "version": "1.8.5",
4
4
  "description": "Youtube Grabber - robust desktop application designed to retrieve multimedia from YouTube and YouTube Music services",
5
5
  "keywords": [
6
6
  "youtube",
@@ -36,9 +36,10 @@
36
36
  "build:prod": "yarn clean && \"cross-env NODE_ENV=production webpack\" --config webpack.config.ts",
37
37
  "electron": "wait-on dist/index.js && ELECTRON_DISABLE_SECURITY_WARNINGS=true electron .",
38
38
  "electron-debug": "wait-on dist/index.js && ELECTRON_DISABLE_SECURITY_WARNINGS=true electron --inspect-brk=9229 .",
39
+ "test": "jest",
39
40
  "watch": "webpack --watch --config webpack.config.ts",
40
41
  "pack": "yarn build:prod && electron-builder --dir",
41
- "dist": "yarn build:prod && electron-builder"
42
+ "dist": "yarn build:prod && yarn test && electron-builder"
42
43
  },
43
44
  "build": {
44
45
  "appId": "yt.grabber",
@@ -108,7 +109,6 @@
108
109
  "moment": "^2.30.1",
109
110
  "moment-duration-format": "^2.3.2",
110
111
  "puppeteer": "^24.4.0",
111
- "puppeteer-core": "^24.4.0",
112
112
  "puppeteer-extra": "^3.3.6",
113
113
  "puppeteer-extra-plugin-devtools": "^2.4.6",
114
114
  "puppeteer-extra-plugin-stealth": "^2.11.2",
@@ -123,17 +123,23 @@
123
123
  },
124
124
  "devDependencies": {
125
125
  "@playwright/test": "^1.53.1",
126
+ "@swc/core": "^1.13.5",
127
+ "@swc/jest": "^0.2.39",
128
+ "@testing-library/dom": "^10.4.1",
129
+ "@testing-library/jest-dom": "^6.9.1",
130
+ "@testing-library/react": "^16.3.0",
131
+ "@testing-library/user-event": "^14.6.1",
126
132
  "@types/eslint": "^9.6.1",
127
133
  "@types/fs-extra": "^11.0.4",
128
134
  "@types/i18next": "^13.0.0",
129
135
  "@types/i18next-node-fs-backend": "^2.1.5",
136
+ "@types/jest": "^30.0.0",
130
137
  "@types/jsonschema": "^1.1.1",
131
138
  "@types/lodash": "^4.17.16",
132
139
  "@types/moment-duration-format": "^2.2.6",
133
140
  "@types/mui-image": "^1.0.5",
134
141
  "@types/node": "^22.13.14",
135
142
  "@types/prettier": "^3.0.0",
136
- "@types/puppeteer-core": "^7.0.4",
137
143
  "@types/react": "^19.0.12",
138
144
  "@types/react-dom": "^19.0.4",
139
145
  "@types/webpack-dev-server": "^4.7.2",
@@ -156,6 +162,9 @@
156
162
  "globals": "^16.0.0",
157
163
  "html-webpack-plugin": "^5.6.3",
158
164
  "i18next-scanner-webpack": "^0.9.1",
165
+ "identity-obj-proxy": "^3.0.0",
166
+ "jest": "^30.2.0",
167
+ "jest-environment-jsdom": "^30.2.0",
159
168
  "jiti": "^2.4.2",
160
169
  "mini-css-extract-plugin": "^2.9.2",
161
170
  "prettier": "^3.5.3",
@@ -168,6 +177,7 @@
168
177
  "ts-node": "^10.9.2",
169
178
  "ts-node-dev": "^2.0.0",
170
179
  "typescript": "^5.8.2",
180
+ "util": "^0.12.5",
171
181
  "wait-on": "^8.0.2",
172
182
  "webpack": "^5.98.0",
173
183
  "webpack-cli": "^6.0.1",
@@ -1,79 +1,79 @@
1
- import {defineConfig, devices} from "@playwright/test";
2
-
3
- /**
4
- * Read environment variables from file.
5
- * https://github.com/motdotla/dotenv
6
- */
7
- // import dotenv from "dotenv";
8
- // import path from "path";
9
- // dotenv.config({ path: path.resolve(__dirname, ".env") });
10
-
11
- /**
12
- * See https://playwright.dev/docs/test-configuration.
13
- */
14
- export default defineConfig({
15
- testDir: "./e2e",
16
- /* Run tests in files in parallel */
17
- fullyParallel: true,
18
- /* Fail the build on CI if you accidentally left test.only in the source code. */
19
- forbidOnly: !!process.env.CI,
20
- /* Retry on CI only */
21
- retries: process.env.CI ? 2 : 0,
22
- /* Opt out of parallel tests on CI. */
23
- workers: process.env.CI ? 1 : undefined,
24
- /* Reporter to use. See https://playwright.dev/docs/test-reporters */
25
- reporter: "html",
26
- /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
27
- use: {
28
- /* Base URL to use in actions like `await page.goto("/")`. */
29
- // baseURL: "http://localhost:3000",
30
-
31
- /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
32
- trace: "on-first-retry",
33
- },
34
-
35
- /* Configure projects for major browsers */
36
- projects: [
37
- {
38
- name: "chromium",
39
- use: {...devices["Desktop Chrome"]},
40
- },
41
-
42
- // {
43
- // name: "firefox",
44
- // use: { ...devices["Desktop Firefox"] },
45
- // },
46
-
47
- // {
48
- // name: "webkit",
49
- // use: { ...devices["Desktop Safari"] },
50
- // },
51
-
52
- /* Test against mobile viewports. */
53
- // {
54
- // name: "Mobile Chrome",
55
- // use: { ...devices["Pixel 5"] },
56
- // },
57
- // {
58
- // name: "Mobile Safari",
59
- // use: { ...devices["iPhone 12"] },
60
- // },
61
-
62
- /* Test against branded browsers. */
63
- // {
64
- // name: "Microsoft Edge",
65
- // use: { ...devices["Desktop Edge"], channel: "msedge" },
66
- // },
67
- // {
68
- // name: "Google Chrome",
69
- // use: { ...devices["Desktop Chrome"], channel: "chrome" },
70
- // },
71
- ],
72
-
73
- /* Run your local dev server before starting the tests */
74
- // webServer: {
75
- // command: "npm run start",
76
- // url: "http://localhost:3000",
77
- // reuseExistingServer: !process.env.CI,
78
- // },
79
- });
1
+ import {defineConfig, devices} from "@playwright/test";
2
+
3
+ /**
4
+ * Read environment variables from file.
5
+ * https://github.com/motdotla/dotenv
6
+ */
7
+ // import dotenv from "dotenv";
8
+ // import path from "path";
9
+ // dotenv.config({ path: path.resolve(__dirname, ".env") });
10
+
11
+ /**
12
+ * See https://playwright.dev/docs/test-configuration.
13
+ */
14
+ export default defineConfig({
15
+ testDir: "./e2e",
16
+ /* Run tests in files in parallel */
17
+ fullyParallel: true,
18
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
19
+ forbidOnly: !!process.env.CI,
20
+ /* Retry on CI only */
21
+ retries: process.env.CI ? 2 : 0,
22
+ /* Opt out of parallel tests on CI. */
23
+ workers: process.env.CI ? 1 : undefined,
24
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
25
+ reporter: "html",
26
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
27
+ use: {
28
+ /* Base URL to use in actions like `await page.goto("/")`. */
29
+ // baseURL: "http://localhost:3000",
30
+
31
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
32
+ trace: "on-first-retry",
33
+ },
34
+
35
+ /* Configure projects for major browsers */
36
+ projects: [
37
+ {
38
+ name: "chromium",
39
+ use: {...devices["Desktop Chrome"]},
40
+ },
41
+
42
+ // {
43
+ // name: "firefox",
44
+ // use: { ...devices["Desktop Firefox"] },
45
+ // },
46
+
47
+ // {
48
+ // name: "webkit",
49
+ // use: { ...devices["Desktop Safari"] },
50
+ // },
51
+
52
+ /* Test against mobile viewports. */
53
+ // {
54
+ // name: "Mobile Chrome",
55
+ // use: { ...devices["Pixel 5"] },
56
+ // },
57
+ // {
58
+ // name: "Mobile Safari",
59
+ // use: { ...devices["iPhone 12"] },
60
+ // },
61
+
62
+ /* Test against branded browsers. */
63
+ // {
64
+ // name: "Microsoft Edge",
65
+ // use: { ...devices["Desktop Edge"], channel: "msedge" },
66
+ // },
67
+ // {
68
+ // name: "Google Chrome",
69
+ // use: { ...devices["Desktop Chrome"], channel: "chrome" },
70
+ // },
71
+ ],
72
+
73
+ /* Run your local dev server before starting the tests */
74
+ // webServer: {
75
+ // command: "npm run start",
76
+ // url: "http://localhost:3000",
77
+ // reuseExistingServer: !process.env.CI,
78
+ // },
79
+ });
@@ -4,4 +4,8 @@ import {IStore} from "../common/Store";
4
4
 
5
5
  declare global {
6
6
  var store: ElectronStore<IStore>;
7
+
8
+ interface Global {
9
+ [key: string]: any;
10
+ }
7
11
  }
@@ -1 +1 @@
1
- declare module "i18next-scanner-webpack";
1
+ declare module "i18next-scanner-webpack";
@@ -0,0 +1,2 @@
1
+ /// <reference types="@types/jest" />
2
+ /// <reference types="@testing-library/jest-dom" />
@@ -0,0 +1 @@
1
+ declare module "webpack-node-externals";
@@ -0,0 +1,15 @@
1
+
2
+ import {screen} from "@testing-library/react";
3
+ import {render} from "@tests/TestRenderer";
4
+
5
+ import App from "./App";
6
+
7
+ describe("App component", () => {
8
+
9
+ test("renders properly", async () => {
10
+ await render(<App data-testid="app" />);
11
+
12
+ expect(screen.getByTestId("app")).toBeInTheDocument();
13
+ });
14
+
15
+ });
package/src/App.tsx CHANGED
@@ -15,7 +15,7 @@ import DevelopmentView from "./views/development/DevelopmentView";
15
15
  import {HomeView} from "./views/home/HomeView";
16
16
  import SettingsView from "./views/settings/SettingsView";
17
17
 
18
- export const App: React.FC = () => {
18
+ export const App: React.FC = (props: Record<string, any>) => {
19
19
  const {state} = useAppContext();
20
20
  const {anchorEl, help} = useHelp();
21
21
 
@@ -31,7 +31,7 @@ export const App: React.FC = () => {
31
31
 
32
32
  return (
33
33
  <HashRouter>
34
- <Box className={classnames(Styles.app, {[Styles.help]: state.help})}>
34
+ <Box {...props} className={classnames(Styles.app, {[Styles.help]: state.help})}>
35
35
  <CssBaseline enableColorScheme />
36
36
  <AppBar disableNavigation={state.loading} />
37
37
  <Routes location={state.location}>
@@ -9,16 +9,22 @@ import _replace from "lodash/replace";
9
9
  import {VideoType} from "./Media";
10
10
  import {TrackInfo, UrlType, YoutubeInfoResult} from "./Youtube";
11
11
 
12
+ type DataAttributes<T> = {
13
+ [K in keyof T as K extends `data-${string}` ? K : never]: T[K];
14
+ };
15
+
16
+ type NonDataAttributes<T> = Omit<T, keyof DataAttributes<T>>;
17
+
12
18
  export const isDev = () => process.env.NODE_ENV === "development";
13
19
 
14
20
  export const formatFileSize = (sizeInBytes: number, decimals = 2) => {
15
21
  if (!_isNumber(sizeInBytes)) return "";
16
22
  if (sizeInBytes === 0) return "0 Bytes";
17
-
23
+
18
24
  const k = 1024;
19
25
  const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB"];
20
26
  const i = Math.floor(Math.log(sizeInBytes) / Math.log(k));
21
-
27
+
22
28
  return parseFloat((sizeInBytes / Math.pow(k, i)).toFixed(decimals)) + " " + sizes[i];
23
29
  };
24
30
 
@@ -33,7 +39,7 @@ export const escapePathString = (value: string) => {
33
39
  export const resolveMockData = (delay = 1000) => {
34
40
  const TracksMock: any[] = [];
35
41
  const groupped = _groupBy(TracksMock as unknown as TrackInfo[], (item) => item.playlist_id ?? item.id);
36
-
42
+
37
43
  return _map(groupped, (v, k, c) => new Promise<YoutubeInfoResult>((resolve) => {
38
44
  setTimeout(() => {
39
45
  resolve({url: k, value: v});
@@ -61,7 +67,7 @@ export const isTrack = (url: string) => {
61
67
  return trackRegex.test(url);
62
68
  };
63
69
 
64
- export const getUrlType = (url: string) => {
70
+ export const getUrlType = (url: string) => {
65
71
  if (isArtist(url)) {
66
72
  return UrlType.Artist;
67
73
  } else if (isPlaylist(url)) {
@@ -74,5 +80,30 @@ export const getUrlType = (url: string) => {
74
80
  };
75
81
 
76
82
  export const getRealFileExtension = (ext: string) => {
77
- return _includes([VideoType.Avi, VideoType.Mov, VideoType.Mpeg], ext) ? VideoType.Mkv : ext;
83
+ return _includes([VideoType.Avi, VideoType.Mov, VideoType.Mpeg, VideoType.Gif], ext) ? VideoType.Mkv : ext;
84
+ };
85
+
86
+ export const getDataAttributes = (props: Record<string, any>) => {
87
+ const dataAttrs: Record<string, any> = {};
88
+ for (const key in props) {
89
+ if (key.startsWith("data-")) {
90
+ dataAttrs[key] = props[key];
91
+ }
92
+ }
93
+ return dataAttrs;
94
+ };
95
+
96
+ export const splitDataAttributes = <T extends Record<string, any>>(props: T) => {
97
+ const dataProps: Partial<DataAttributes<T>> = {};
98
+ const restProps: Partial<NonDataAttributes<T>> = {};
99
+
100
+ for (const key in props) {
101
+ if (key.startsWith("data-")) {
102
+ (dataProps as any)[key] = props[key];
103
+ } else {
104
+ (restProps as any)[key] = props[key];
105
+ }
106
+ }
107
+
108
+ return [dataProps as DataAttributes<T>, restProps as NonDataAttributes<T>] as const;
78
109
  };
@@ -16,6 +16,7 @@ export enum VideoType {
16
16
  Mov = "mov",
17
17
  Avi = "avi",
18
18
  Mpeg = "mpeg",
19
+ Gif = "gif",
19
20
  };
20
21
 
21
22
  export type Format = {
@@ -23,6 +24,8 @@ export type Format = {
23
24
  extension?: AudioType | VideoType;
24
25
  videoQuality?: string;
25
26
  audioQuality?: number;
27
+ gifTopText?: string;
28
+ gifBottomText?: string;
26
29
  }
27
30
 
28
31
  export enum FormatScope {
@@ -8,6 +8,7 @@ export type ApplicationOptions = {
8
8
  outputDirectory?: string;
9
9
  ytdlpExecutablePath?: string;
10
10
  ffmpegExecutablePath?: string;
11
+ gifsicleExecutablePath?: string;
11
12
  chromeExecutablePath?: string;
12
13
  albumOutputTemplate?: string;
13
14
  playlistOutputTemplate?: string;
@@ -75,6 +76,10 @@ export const StoreSchema: Schema<IStore> = {
75
76
  type: "string",
76
77
  default: ""
77
78
  },
79
+ gifsicleExecutablePath: {
80
+ type: "string",
81
+ default: ""
82
+ },
78
83
  chromeExecutablePath: {
79
84
  type: "string",
80
85
  default: ""