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 +1 -1
- package/src/common/Media.ts +2 -0
- package/src/common/YtdplUtils.ts +12 -3
- package/src/components/youtube/formatSelector/FormatSelector.styl +35 -0
- package/src/components/youtube/formatSelector/FormatSelector.tsx +44 -4
- package/src/resources/locales/de-DE/help.json +7 -1
- package/src/resources/locales/de-DE/translation.json +3 -0
- package/src/resources/locales/en-GB/help.json +7 -1
- package/src/resources/locales/en-GB/translation.json +3 -0
- package/src/resources/locales/pl-PL/help.json +7 -1
- package/src/resources/locales/pl-PL/translation.json +3 -0
- package/src/views/home/HomeView.tsx +2 -2
package/package.json
CHANGED
package/src/common/Media.ts
CHANGED
package/src/common/YtdplUtils.ts
CHANGED
|
@@ -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,
|
|
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 {
|
|
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,
|
|
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,
|
|
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
|
}
|