yt-grabber 1.8.4 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yt-grabber",
3
- "version": "1.8.4",
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",
@@ -24,6 +24,8 @@ export type Format = {
24
24
  extension?: AudioType | VideoType;
25
25
  videoQuality?: string;
26
26
  audioQuality?: number;
27
+ gifTopText?: string;
28
+ gifBottomText?: string;
27
29
  }
28
30
 
29
31
  export enum FormatScope {
@@ -334,15 +334,24 @@ export const generateColorPalette = (directory: string, filename: string, format
334
334
  });
335
335
  };
336
336
 
337
- export const createGifUsingPalette = (directory: string, filename: string, format: Format, extension: string, callback: (error?: Error) => void) => {
337
+ export const createGifUsingPalette = (directory: string, filename: string, format: Format, callback: (error?: Error) => void) => {
338
338
  const errors: string[] = [];
339
339
  const selected = format.videoQuality;
340
- const [width] = _map(selected.match(/\d+/g), Number);
340
+ const [width, height] = _map(selected.match(/\d+/g), Number);
341
+ const gifTopTextLength = format.gifTopText ? format.gifTopText.length : 0;
342
+ const gifBottomTextLength = format.gifBottomText ? format.gifBottomText.length : 0;
343
+ const scalingFactor = 1.7;
344
+ const paddingX = width * 0.02;
345
+ const maxFontSize = height * 0.07;
346
+ const fontSize = Math.min(Math.round(((width - paddingX * 2) / Math.max(gifTopTextLength, gifBottomTextLength, 1)) * scalingFactor), maxFontSize);
347
+ const borderSize = Math.max(Math.round(fontSize * 0.04));
348
+ const gifTopText = format.gifTopText ? `,drawtext=fontfile=/path/to/Arial.ttf:text='${format.gifTopText}':x=(w-text_w)/2:y=h*0.05:fontcolor=white:fontsize=${fontSize}:bordercolor=black:borderw=${borderSize}` : "";
349
+ const gifBottomText = format.gifBottomText ? `,drawtext=fontfile=/path/to/Arial.ttf:text='${format.gifBottomText}':x=(w-text_w)/2:y=h*0.95-th:fontcolor=white:fontsize=${fontSize}:bordercolor=black:borderw=${borderSize}` : "";
341
350
  const cmdArgs = [
342
351
  "-y",
343
352
  "-i", `${directory}/${filename}.mkv`,
344
353
  "-i", `${directory}/${filename}-palette.png`,
345
- "-filter_complex", `fps=15,scale=${width}:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=floyd_steinberg`,
354
+ "-filter_complex", `fps=15,scale=${width}:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=floyd_steinberg${gifTopText}${gifBottomText}`,
346
355
  `${directory}/${filename}.gif`,
347
356
  ];
348
357
 
@@ -1,3 +1,38 @@
1
1
  .format-selector {
2
+
3
+ .accordion {
4
+ background: transparent;
5
+ background-image: none;
2
6
 
7
+ .accordion-summary {
8
+ border-radius: 8px;
9
+ background-color: var(--theme-palette-background-paper);
10
+ background-color: var(--theme-palette-primary-dark);
11
+ background-image: var(--Paper-overlay);
12
+ border: 1px solid var(--theme-palette-divider);
13
+ }
14
+
15
+ &:global(.Mui-expanded) {
16
+ .accordion-summary {
17
+ border-bottom-left-radius: 0;
18
+ border-bottom-right-radius: 0;
19
+ }
20
+ }
21
+
22
+ .accordion-details {
23
+ border-radius: 8px;
24
+ border-top-left-radius: 0;
25
+ border-top-right-radius: 0;
26
+ border: 1px solid var(--theme-palette-divider);
27
+ border-top: none;
28
+ }
29
+ }
30
+
31
+ .text-input-group {
32
+ gap: var(--Grid-rowSpacing) var(--Grid-columnSpacing);
33
+
34
+ .control-group {
35
+ gap: var(--Grid-rowSpacing) var(--Grid-columnSpacing);
36
+ }
37
+ }
3
38
  }
@@ -10,10 +10,13 @@ import _last from "lodash/last";
10
10
  import _map from "lodash/map";
11
11
  import _uniq from "lodash/uniq";
12
12
  import _values from "lodash/values";
13
- import React, {useEffect, useState} from "react";
13
+ import React, {ChangeEvent, useEffect, useState} from "react";
14
14
  import {useTranslation} from "react-i18next";
15
15
 
16
- import {FormControl, Grid, InputLabel, MenuItem, Select, SelectChangeEvent} from "@mui/material";
16
+ import {
17
+ Accordion, AccordionDetails, AccordionSummary, FormControl, Grid, InputLabel, MenuItem, Select,
18
+ SelectChangeEvent, Stack, TextField, Typography
19
+ } from "@mui/material";
17
20
 
18
21
  import {AudioType, Format, FormatScope, MediaFormat, VideoType} from "../../../common/Media";
19
22
  import {ApplicationOptions} from "../../../common/Store";
@@ -44,7 +47,9 @@ export const FormatSelector: React.FC<FormatSelectorProps> = (props) => {
44
47
  const [format, setFormat] = useState(resolveFormat(options.formatScope, formats, activeTab));
45
48
  const [extensions, setExtensions] = useState<Array<AudioType | VideoType>>(format.type === MediaFormat.Audio ? audioExtensions : videoExtensions);
46
49
  const [resolutions, setResolutions] = useState<string[]>();
47
-
50
+ const [gifTopText, setGifTopText] = useState("");
51
+ const [gifBottomText, setGifBottomText] = useState("");
52
+
48
53
  const [selectedMediaType, setSelectedMediaType] = useState<MediaFormat>(format.type ?? MediaFormat.Audio);
49
54
  const [selectedAudioExtension, setSelectedAudioExtension] = useState<AudioType>(format.type === MediaFormat.Audio ? format.extension as AudioType :_first(audioExtensions));
50
55
  const [selectedVideoExtension, setSelectedVideoExtension] = useState<VideoType>(format.type === MediaFormat.Video ? format.extension as VideoType :_first(videoExtensions));
@@ -83,8 +88,10 @@ export const FormatSelector: React.FC<FormatSelectorProps> = (props) => {
83
88
  extension: selectedFormat,
84
89
  videoQuality: selectedResolution,
85
90
  audioQuality: selectedQuality,
91
+ gifTopText,
92
+ gifBottomText
86
93
  });
87
- }, [selectedMediaType, selectedFormat, selectedVideoExtension, selectedResolution, selectedQuality]);
94
+ }, [selectedMediaType, selectedFormat, selectedVideoExtension, selectedResolution, selectedQuality, gifTopText, gifBottomText]);
88
95
 
89
96
  useEffect(() => {
90
97
  setFormat(resolveFormat(options.formatScope, formats, activeTab));
@@ -149,6 +156,14 @@ export const FormatSelector: React.FC<FormatSelectorProps> = (props) => {
149
156
  setSelectedQuality(value);
150
157
  };
151
158
 
159
+ const onGifTopTextChanged = (event: ChangeEvent<HTMLInputElement>) => {
160
+ setGifTopText(event.target.value);
161
+ };
162
+
163
+ const onGifBottomTextChanged = (event: ChangeEvent<HTMLInputElement>) => {
164
+ setGifBottomText(event.target.value);
165
+ };
166
+
152
167
  return (
153
168
  <Grid className={Styles.formatSelector} container spacing={2} padding={2}>
154
169
  <Grid size="grow">
@@ -214,6 +229,31 @@ export const FormatSelector: React.FC<FormatSelectorProps> = (props) => {
214
229
  />
215
230
  </Grid>
216
231
  }
232
+ {selectedMediaType === MediaFormat.Video && selectedFormat === VideoType.Gif &&
233
+ <Grid size={12}>
234
+ <Accordion
235
+ elevation={0}
236
+ className={Styles.accordion}
237
+ data-help="gifTextOptions"
238
+ disableGutters
239
+ expanded
240
+ >
241
+ <AccordionSummary className={Styles.accordionSummary}>
242
+ <Typography variant="body1">{t("gifTextOptions")}</Typography>
243
+ </AccordionSummary>
244
+ <AccordionDetails className={Styles.accordionDetails}>
245
+ <Stack direction="column" spacing={1} paddingX={0} paddingY={2} paddingBottom={0}>
246
+ <FormControl className={Styles.textInputGroup} data-help="gifTopText">
247
+ <TextField label={t("gifTopText")} variant="outlined" value={gifTopText} onChange={onGifTopTextChanged} />
248
+ </FormControl>
249
+ <FormControl className={Styles.textInputGroup} data-help="gifBottomText">
250
+ <TextField label={t("gifBottomText")} variant="outlined" value={gifBottomText} onChange={onGifBottomTextChanged} />
251
+ </FormControl>
252
+ </Stack>
253
+ </AccordionDetails>
254
+ </Accordion>
255
+ </Grid>
256
+ }
217
257
  </Grid>
218
258
  );
219
259
  };
@@ -175,5 +175,11 @@
175
175
  "downloadReleaseDateUntilHeader": "Bis Jahr",
176
176
  "downloadReleaseDateUntilContent": "Elemente mit einem Veröffentlichungsdatum nach diesem Jahr werden ausgelassen.",
177
177
  "downloadAlbumsHeader": "Alben",
178
- "downloadAlbumsContent": "Schließen Sie beim Herunterladen der Alben des Künstlers vollständige Albumveröffentlichungen ein."
178
+ "downloadAlbumsContent": "Schließen Sie beim Herunterladen der Alben des Künstlers vollständige Albumveröffentlichungen ein.",
179
+ "gifTextOptionsHeader": "GIF Text Optionen",
180
+ "gifTextOptionsContent": "Optionen zum Hinzufügen von Text oben und/oder unten zu generierten GIF-Dateien.",
181
+ "gifTopTextHeader": "Text oben",
182
+ "gifTopTextContent": "Text, der oben in der generierten GIF-Datei hinzugefügt werden soll",
183
+ "gifBottomTextHeader": "Text unten",
184
+ "gifBottomTextContent": "Text, der unten in der generierten GIF-Datei hinzugefügt werden soll"
179
185
  }
@@ -49,6 +49,9 @@
49
49
  "foundDeletedOrPrivateMedia": "Einige gelöschte oder private Medien wurden gefunden. Sie wurden übersprungen und werden in den Ergebnissen nicht angezeigt.",
50
50
  "from": "von",
51
51
  "fromYear": "Von (Jahr)",
52
+ "gifBottomText": "Text unten",
53
+ "gifTextOptions": "Textoptionen für GIFs",
54
+ "gifTopText": "Text oben",
52
55
  "invalidTemplateKeys": "Ungültige Vorlagenschlüssel: {{invalidKeys}}",
53
56
  "langName": "Deutsch",
54
57
  "loadFromFile": "Aus Datei laden",
@@ -175,5 +175,11 @@
175
175
  "downloadReleaseDateUntilHeader": "Until year",
176
176
  "downloadReleaseDateUntilContent": "Items with release date after this year will be omitted.",
177
177
  "downloadAlbumsHeader": "Albums",
178
- "downloadAlbumsContent": "Include full album releases when downloading artist's albums."
178
+ "downloadAlbumsContent": "Include full album releases when downloading artist's albums.",
179
+ "gifTextOptionsHeader": "GIF Text Options",
180
+ "gifTextOptionsContent": "Options to add text to top and/or bottom of generated GIF files.",
181
+ "gifTopTextHeader": "Top Text",
182
+ "gifTopTextContent": "Text to be added at the top of generated GIF file",
183
+ "gifBottomTextHeader": "Bottom Text",
184
+ "gifBottomTextContent": "Text to be added at the bottom of generated GIF file"
179
185
  }
@@ -49,6 +49,9 @@
49
49
  "foundDeletedOrPrivateMedia": "Encountered some deleted or private media. They were skipped and won't be show in results.",
50
50
  "from": "from",
51
51
  "fromYear": "From (year)",
52
+ "gifBottomText": "Bottom text",
53
+ "gifTextOptions": "Text options for GIFs",
54
+ "gifTopText": "Top text",
52
55
  "invalidTemplateKeys": "Invalid template keys: {{invalidKeys}}",
53
56
  "langName": "English",
54
57
  "loadFromFile": "Load from file",
@@ -175,5 +175,11 @@
175
175
  "downloadReleaseDateUntilHeader": "Do",
176
176
  "downloadReleaseDateUntilContent": "Elementy z datą wydania późniejszą niż ten rok zostaną pominięte.",
177
177
  "downloadAlbumsHeader": "Albumy",
178
- "downloadAlbumsContent": "Uwzględnij pełne wydania albumów podczas pobierania dyskografii artysty."
178
+ "downloadAlbumsContent": "Uwzględnij pełne wydania albumów podczas pobierania dyskografii artysty.",
179
+ "gifTextOptionsHeader": "Opcje tekstu GIF",
180
+ "gifTextOptionsContent": "Opcje dodawania tekstu na górze i/lub dole generowanych plików GIF.",
181
+ "gifTopTextHeader": "Tekst na górze",
182
+ "gifTopTextContent": "Tekst do dodania na górze generowanego pliku GIF",
183
+ "gifBottomTextHeader": "Tekst na dole",
184
+ "gifBottomTextContent": "Tekst do dodania na dole generowanego pliku GIF"
179
185
  }
@@ -49,6 +49,9 @@
49
49
  "foundDeletedOrPrivateMedia": "Napotkano usunięte lub prywatne media. Zostały one pominięte i nie będą wyświetlane w wynikach.",
50
50
  "from": "start",
51
51
  "fromYear": "Od (rok)",
52
+ "gifBottomText": "Dolny tekst",
53
+ "gifTextOptions": "Opcje tekstu dla GIF-ów",
54
+ "gifTopText": "Górny tekst",
52
55
  "invalidTemplateKeys": "Nieprawidłowe wartości: {{invalidKeys}}",
53
56
  "langName": "Polski",
54
57
  "loadFromFile": "Wczytaj z pliku",
@@ -692,7 +692,7 @@ export const HomeView: React.FC = () => {
692
692
  if (error) {
693
693
  setErrors((prev) => [...prev, {url: track.original_url, message: error.message}]);
694
694
  }
695
- createGifUsingPalette(fileInfo.dir, fileInfo.name, format, format.extension, (error: Error) => {
695
+ createGifUsingPalette(fileInfo.dir, fileInfo.name, format, (error: Error) => {
696
696
  if (error) {
697
697
  setErrors((prev) => [...prev, {url: track.original_url, message: error.message}]);
698
698
  }
@@ -735,7 +735,7 @@ export const HomeView: React.FC = () => {
735
735
  if (error) {
736
736
  setErrors((prev) => [...prev, {url: track.original_url, message: error.message}]);
737
737
  }
738
- createGifUsingPalette(fileInfo.dir, fileInfo.name, format, format.extension, (error) => {
738
+ createGifUsingPalette(fileInfo.dir, fileInfo.name, format, (error) => {
739
739
  if (error) {
740
740
  setErrors((prev) => [...prev, {url: track.original_url, message: error.message}]);
741
741
  }