Merge pull request #493 from anidl/4.1.0

4.1.0 -- Numerous fixes and improvements
This commit is contained in:
AnimeDL 2023-07-09 14:23:17 -07:00 committed by GitHub
commit bfe4ae6164
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1769 additions and 1654 deletions

View file

@ -20,7 +20,7 @@ jobs:
- name: Use Node.js 16
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
- run: pnpm i
- run: pnpm run docs
- uses: stefanzweifel/git-auto-commit-action@v4

View file

@ -20,7 +20,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
check-latest: true
- name: Install Node modules
run: |

View file

@ -1644,6 +1644,9 @@ export default class Crunchy implements ServiceClass {
ret[season_number][lang.code] = item;
} else if (item.is_dubbed && lang.code === 'eng' && !langsData.languages.some(a => item.title.includes(`(${a.name})`) || item.title.includes(`(${a.name} Dub)`))) { // Dubbed with no more infos will be treated as eng dubs
ret[season_number][lang.code] = item;
//TODO: look into if below is stable
} else if (item.audio_locale == lang.cr_locale) {
ret[season_number][lang.code] = item;
}
}
}

View file

@ -1,4 +1,4 @@
# multi-downloader-nx (4.0.3v)
# multi-downloader-nx (4.1.0v)
If you find any bugs in this documentation or in the programm itself please report it [over on GitHub](https://github.com/anidl/multi-downloader-nx/issues).
@ -136,6 +136,12 @@ This will speed up the download speed, if multiple languages are selected.
If selected, it will remove the bumpers such as the hidive intro from the final file.
Currently disabling this sometimes results in bugs such as video/audio desync
#### `--originalFontSize`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **Default** |**cli-default Entry**
| --- | --- | --- | --- | --- | --- | ---|
| Hidive | `--originalFontSize ` | `boolean` | `No`| `NaN` | `true`| `originalFontSize: ` |
If selected, it will prefer to keep the original Font Size defined by the service.
#### `-x`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **Choices** | **Default** |**cli-default Entry**
| --- | --- | --- | --- | --- | --- | --- | ---|

File diff suppressed because it is too large Load diff

View file

@ -2,12 +2,15 @@ import { Box, List, ListItem, Typography, Divider, Dialog, Select, MenuItem, For
import { CheckBox, CheckBoxOutlineBlank } from '@mui/icons-material';
import React from 'react';
import useStore from '../../../../hooks/useStore';
import ContextMenu from '../../../reusable/ContextMenu';
import { useSnackbar } from 'notistack';
const EpisodeListing: React.FC = () => {
const [store, dispatch] = useStore();
const [season, setSeason] = React.useState<'all'|string>('all');
const { enqueueSnackbar } = useSnackbar();
const seasons = React.useMemo(() => {
const s: string[] = [];
@ -72,28 +75,26 @@ const EpisodeListing: React.FC = () => {
</ListItem>
{store.episodeListing.filter((a) => season === 'all' ? true : a.season === season).map((item, index, { length }) => {
const e = isNaN(parseInt(item.e)) ? item.e : parseInt(item.e);
const idStr = `S${item.season}E${e}`
const isSelected = selected.includes(e.toString());
return <Box {...{ mouseData: isSelected }} key={`Episode_List_Item_${index}`} sx={{
backdropFilter: isSelected ? 'brightness(1.5)' : '',
'&:hover': {
backdropFilter: 'brightness(1.5)'
}
}}
onClick={() => {
let arr: string[] = [];
if (isSelected) {
arr = [...selected.filter(a => a !== e.toString())];
} else {
arr = [...selected, e.toString()];
}
setSelected(arr.filter(a => a.length > 0));
}}>
<ListItem sx={{ display: 'grid', gridTemplateColumns: '25px 50px 1fr 5fr' }}>
const imageRef = React.createRef<HTMLImageElement>();
const summaryRef = React.createRef<HTMLParagraphElement>();
return <Box {...{ mouseData: isSelected }} key={`Episode_List_Item_${index}`}>
<ListItem sx={{backdropFilter: isSelected ? 'brightness(1.5)' : '', '&:hover': {backdropFilter: 'brightness(1.5)'}, display: 'grid', gridTemplateColumns: '25px 50px 1fr 5fr' }}
onClick={() => {
let arr: string[] = [];
if (isSelected) {
arr = [...selected.filter(a => a !== e.toString())];
} else {
arr = [...selected, e.toString()];
}
setSelected(arr.filter(a => a.length > 0));
}}>
{ isSelected ? <CheckBox /> : <CheckBoxOutlineBlank /> }
<Typography color='text.primary' sx={{ textAlign: 'center' }}>
{e}
{idStr}
</Typography>
<img style={{ width: 'inherit', maxHeight: '200px', minWidth: '150px' }} src={item.img} alt="thumbnail" />
<img ref={imageRef} style={{ width: 'inherit', maxHeight: '200px', minWidth: '150px' }} src={item.img} alt="thumbnail" />
<Box sx={{ display: 'flex', flexDirection: 'column', pl: 1 }}>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr min-content' }}>
<Typography color='text.primary' variant="h5">
@ -103,7 +104,7 @@ const EpisodeListing: React.FC = () => {
{item.time.startsWith('00:') ? item.time.slice(3) : item.time}
</Typography>
</Box>
<Typography color='text.primary'>
<Typography color='text.primary' ref={summaryRef}>
{item.description}
</Typography>
<Box sx={{ display: 'grid', gridTemplateColumns: 'fit-content 1fr' }}>
@ -114,6 +115,29 @@ const EpisodeListing: React.FC = () => {
</Box>
</Box>
</ListItem>
<ContextMenu options={[ { text: 'Copy image URL', onClick: async () => {
await navigator.clipboard.writeText(item.img);
enqueueSnackbar('Copied URL to clipboard', {
variant: 'info'
});
}},
{
text: 'Open image in new tab',
onClick: () => {
window.open(item.img);
}
} ]} popupItem={imageRef} />
<ContextMenu options={[
{
onClick: async () => {
await navigator.clipboard.writeText(item.description!);
enqueueSnackbar('Copied summary to clipboard', {
variant: 'info'
})
},
text: "Copy summary to clipboard"
}
]} popupItem={summaryRef} />
{index < length - 1 && <Divider />}
</Box>;
})}
@ -160,4 +184,4 @@ const parseSelect = (s: string): string[] => {
return [...new Set(ret)];
};
export default EpisodeListing;
export default EpisodeListing;

View file

@ -32,7 +32,7 @@ const SearchBox: React.FC = () => {
React.useEffect(() => {
if (search.trim().length === 0)
return setSearchResult({ isOk: true, value: [] });
const timeOutId = setTimeout(async () => {
if (search.trim().length > 3) {
const s = await messageHandler?.search({search});
@ -49,12 +49,13 @@ const SearchBox: React.FC = () => {
<Box sx={{ m: 2 }}>
<TextField ref={anchor} value={search} onClick={() => setFocus(true)} onChange={e => setSearch(e.target.value)} variant='outlined' label='Search' fullWidth />
{searchResult !== undefined && searchResult.isOk && searchResult.value.length > 0 && focus &&
<Paper sx={{ position: 'fixed', maxHeight: '50%', width: `${anchorBounding?.width}px`,
<Paper sx={{ position: 'fixed', maxHeight: '50%', width: `${anchorBounding?.width}px`,
left: anchorBounding?.x, top: (anchorBounding?.y ?? 0) + (anchorBounding?.height ?? 0), zIndex: 99, overflowY: 'scroll'}}>
<List>
{searchResult && searchResult.isOk ?
searchResult.value.map((a, ind, arr) => {
const imageRef = React.createRef<HTMLImageElement>();
const summaryRef = React.createRef<HTMLParagraphElement>();
return <Box key={a.id}>
<ListItem className='listitem-hover' onClick={() => {
selectItem(a.id);
@ -68,7 +69,7 @@ const SearchBox: React.FC = () => {
<Typography variant='h6' component='h6' color='text.primary' sx={{ }}>
{a.name}
</Typography>
{a.desc && <Typography variant='caption' component='p' color='text.primary' sx={{ pt: 1, pb: 1 }}>
{a.desc && <Typography variant='caption' component='p' color='text.primary' sx={{ pt: 1, pb: 1 }} ref={summaryRef}>
{a.desc}
</Typography>}
{a.lang && <Typography variant='caption' component='p' color='text.primary' sx={{ }}>
@ -90,8 +91,21 @@ const SearchBox: React.FC = () => {
text: 'Open image in new tab',
onClick: () => {
window.open(a.image);
}
}
} ]} popupItem={imageRef} />
{a.desc &&
<ContextMenu options={[
{
onClick: async () => {
await navigator.clipboard.writeText(a.desc!);
enqueueSnackbar('Copied summary to clipboard', {
variant: 'info'
})
},
text: "Copy summary to clipboard"
}
]} popupItem={summaryRef} />
}
{(ind < arr.length - 1) && <Divider />}
</Box>;
})
@ -102,4 +116,4 @@ const SearchBox: React.FC = () => {
</ClickAwayListener>;
};
export default SearchBox;
export default SearchBox;

View file

@ -1,10 +1,24 @@
import { Box, Button, Menu, MenuItem } from '@mui/material';
import { Box, Button, Menu, MenuItem, Typography } from '@mui/material';
import React from 'react';
import { messageChannelContext } from '../../provider/MessageChannel';
import useStore from '../../hooks/useStore';
import { StoreState } from '../../provider/Store'
const MenuBar: React.FC = () => {
const [ openMenu, setMenuOpen ] = React.useState<'settings'|'help'|undefined>();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [store] = useStore();
const transformService = (service: StoreState['service']) => {
switch(service) {
case 'crunchy':
return "Crunchyroll"
case 'funi':
return "Funimation"
case "hidive":
return "Hidive"
}
}
const msg = React.useContext(messageChannelContext);
@ -79,6 +93,9 @@ const MenuBar: React.FC = () => {
Discord
</MenuItem>
</Menu>
<Typography variant="h5" color="text.primary" component="div" align="center" sx={{flexGrow: 1}}>
{transformService(store.service)}
</Typography>
</Box>;
};

View file

@ -23,7 +23,7 @@ const buttonSx: SxProps = {
function ContextMenu<T extends HTMLElement, >(props: ContextMenuProps<T>) {
const [anchor, setAnchor] = React.useState( { x: 0, y: 0 } );
const [show, setShow] = React.useState(false);
React.useEffect(() => {
@ -37,16 +37,16 @@ function ContextMenu<T extends HTMLElement, >(props: ContextMenuProps<T>) {
};
ref.current.addEventListener('contextmenu', listener);
return () => {
return () => {
if (ref.current)
ref.current.removeEventListener('contextmenu', listener);
};
}, [ props.popupItem ]);
return show ? <Box sx={{ zIndex: 9999, p: 1, background: 'rgba(0, 0, 0, 0.75)', backdropFilter: 'blur(5px)', position: 'fixed', left: anchor.x, top: anchor.y }}>
return show ? <Box sx={{ zIndex: 1400, p: 1, background: 'rgba(0, 0, 0, 0.75)', backdropFilter: 'blur(5px)', position: 'fixed', left: anchor.x, top: anchor.y }}>
<List sx={{ p: 0, m: 0, display: 'flex', flexDirection: 'column' }}>
{props.options.map((item, i) => {
return item === 'divider' ? <Divider key={`ContextMenu_Divider_${i}_${item}`}/> :
return item === 'divider' ? <Divider key={`ContextMenu_Divider_${i}_${item}`}/> :
<Button color='inherit' key={`ContextMenu_Value_${i}_${item}`} onClick={() => {
item.onClick();
setShow(false);
@ -62,4 +62,4 @@ function ContextMenu<T extends HTMLElement, >(props: ContextMenuProps<T>) {
</Box> : <></>;
}
export default ContextMenu;
export default ContextMenu;

View file

@ -416,7 +416,7 @@ export default class Hidive implements ServiceClass {
console.info(`[INFO] Selected dub(s): ${options.dubLang.join(', ')}`);
const videoUrls = videoData.Data.VideoUrls;
const subsUrls = videoData.Data.CaptionVttUrls;
const fontSize = videoData.Data.FontSize ? videoData.Data.FontSize : 34;
const fontSize = videoData.Data.FontSize ? videoData.Data.FontSize : options.fontSize;
const subsSel = subsList;
//Get Selected Video URLs
const videoSel = videoList.sort().filter(videoLanguage =>
@ -490,10 +490,11 @@ export default class Hidive implements ServiceClass {
let mediaName = '...';
let fileName;
const files: DownloadedMedia[] = [];
const variables: Variable[] = [];
let dlFailed = false;
//let dlVideoOnce = false; // Variable to save if best selected video quality was downloaded
let subsMargin = 0;
const variables: Variable[] = [];
const chosenFontSize = options.originalFontSize ? fontSize : options.fontSize;
for (const videoData of videoUrls) {
if(videoData.seriesTitle && videoData.episodeNumber && videoData.episodeTitle){
mediaName = `${videoData.seriesTitle} - ${videoData.episodeNumber} - ${videoData.episodeTitle}`;
@ -700,8 +701,7 @@ export default class Hidive implements ServiceClass {
const getVttContent = await this.req.getData(await this.genSubsUrl('vtt', subsXUrl));
if (getCssContent.ok && getVttContent.ok && getCssContent.res && getVttContent.res) {
//vttConvert(getVttContent.res.body, false, subLang.name, fontSize);
//TODO: look into potentially having an option for native fontSize
const sBody = vtt(undefined, options.fontSize, getVttContent.res.body, getCssContent.res.body, subsMargin, options.fontName);
const sBody = vtt(undefined, chosenFontSize, getVttContent.res.body, getCssContent.res.body, subsMargin, options.fontName);
sxData.title = `${subLang.language} / ${sxData.title}`;
sxData.fonts = fontsData.assFonts(sBody) as Font[];
fs.writeFileSync(sxData.path, sBody);

View file

@ -7,7 +7,7 @@ import { execSync } from 'child_process';
import { console } from './log';
const buildsDir = './_builds';
const nodeVer = 'node16-';
const nodeVer = 'node18-';
type BuildTypes = `${'ubuntu'|'windows'|'macos'|'arm'}64`

View file

@ -63,6 +63,7 @@ let argvC: {
$0: string;
dlVideoOnce: boolean;
removeBumpers: boolean;
originalFontSize: boolean;
};
export type ArgvType = typeof argvC;

View file

@ -205,6 +205,18 @@ const args: TAppArg<boolean|number|string|unknown[]>[] = [
default: true
}
},
{
name: 'originalFontSize',
describe: 'Keep original font size',
type: 'boolean',
group: 'dl',
service: ['hidive'],
docDescribe: 'If selected, it will prefer to keep the original Font Size defined by the service.',
usage: '',
default: {
default: true
}
},
{
name: 'x',
group: 'dl',

View file

@ -1,7 +1,7 @@
{
"name": "multi-downloader-nx",
"short_name": "aniDL",
"version": "4.0.3",
"version": "4.1.0",
"description": "Download videos from Funimation, Crunchyroll, or Hidive via cli",
"keywords": [
"download",

12
src/hooks/useStore.tsx Normal file
View file

@ -0,0 +1,12 @@
import React from 'react';
import { StoreAction, StoreContext, StoreState } from '../provider/Store';
const useStore = () => {
const context = React.useContext(StoreContext as unknown as React.Context<[StoreState, React.Dispatch<StoreAction<keyof StoreState>>]>);
if (!context) {
throw new Error('useStore must be used under Store');
}
return context;
};
export default useStore;