Added ffmpeg command generator
This commit is contained in:
parent
40cc7358e4
commit
6e83e9a025
7 changed files with 8877 additions and 4315 deletions
12860
package-lock.json
generated
12860
package-lock.json
generated
File diff suppressed because it is too large
Load diff
16
package.json
16
package.json
|
@ -4,17 +4,17 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@material-ui/core": "^4.11.0",
|
"@material-ui/core": "^4.11.3",
|
||||||
"@material-ui/icons": "^4.9.1",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.5.0",
|
"@testing-library/react": "^9.5.0",
|
||||||
"@testing-library/user-event": "^7.2.1",
|
"@testing-library/user-event": "^7.2.1",
|
||||||
"react": "^16.13.1",
|
"react": "^16.14.0",
|
||||||
"react-copy-to-clipboard": "^5.0.2",
|
"react-copy-to-clipboard": "^5.0.3",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.14.0",
|
||||||
"react-dropzone": "^11.0.2",
|
"react-dropzone": "^11.3.2",
|
||||||
"react-ga": "^3.1.2",
|
"react-ga": "^3.3.0",
|
||||||
"react-scripts": "3.4.1"
|
"react-scripts": "^4.0.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"predeploy": "npm run build",
|
"predeploy": "npm run build",
|
||||||
|
|
|
@ -17,7 +17,8 @@ import FileCopyOutlined from '@material-ui/icons/FileCopyOutlined';
|
||||||
|
|
||||||
|
|
||||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||||
import { Accordion, AccordionItem } from 'react-sanfona';
|
|
||||||
|
import ControlledAccordions from './ControlledAccordions'
|
||||||
|
|
||||||
|
|
||||||
const useStyles = theme => ({
|
const useStyles = theme => ({
|
||||||
|
@ -34,32 +35,29 @@ const useStyles = theme => ({
|
||||||
form: {
|
form: {
|
||||||
width: '100%', // Fix IE 11 issue.
|
width: '100%', // Fix IE 11 issue.
|
||||||
marginTop: theme.spacing(1),
|
marginTop: theme.spacing(1),
|
||||||
}
|
},
|
||||||
|
|
||||||
|
//Accordeon
|
||||||
|
heading: {
|
||||||
|
fontSize: theme.typography.pxToRem(15),
|
||||||
|
flexBasis: '33.33%',
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
secondaryHeading: {
|
||||||
|
fontSize: theme.typography.pxToRem(15),
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function Copyright() {
|
|
||||||
return (
|
|
||||||
<Typography variant="body2" color="textSecondary" align="center">
|
|
||||||
{'Copyright © '}
|
|
||||||
<Link color="inherit" href="https://audible-tools.github.io/">
|
|
||||||
audible-tools
|
|
||||||
</Link>{' '}
|
|
||||||
{new Date().getFullYear()}
|
|
||||||
{'.'}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ChecksumResolver extends React.Component {
|
class ChecksumResolver extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
checksum: ""
|
checksum: "",
|
||||||
|
fileName: "input.aax"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DarkerDisabledTextField = withStyles({
|
DarkerDisabledTextField = withStyles({
|
||||||
root: {
|
root: {
|
||||||
marginRight: 8,
|
marginRight: 8,
|
||||||
|
@ -69,6 +67,19 @@ class ChecksumResolver extends React.Component {
|
||||||
}
|
}
|
||||||
})(TextField);
|
})(TextField);
|
||||||
|
|
||||||
|
Copyright = (function () {
|
||||||
|
return (
|
||||||
|
<Typography variant="body2" color="textSecondary" align="center">
|
||||||
|
{'Copyright © '}
|
||||||
|
<Link color="inherit" href="https://audible-tools.github.io/">
|
||||||
|
audible-tools
|
||||||
|
</Link>{' '}
|
||||||
|
{new Date().getFullYear()}
|
||||||
|
{'.'}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
setChecksum = (value) => {
|
setChecksum = (value) => {
|
||||||
if (value.length > 40) {
|
if (value.length > 40) {
|
||||||
return;
|
return;
|
||||||
|
@ -125,6 +136,7 @@ class ChecksumResolver extends React.Component {
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
this.setState({ fileName: file.name });
|
||||||
const slic = file.slice(653, 653 + 20);
|
const slic = file.slice(653, 653 + 20);
|
||||||
const results = this.buf2hex(await slic.arrayBuffer());
|
const results = this.buf2hex(await slic.arrayBuffer());
|
||||||
this.setChecksum(results)
|
this.setChecksum(results)
|
||||||
|
@ -134,11 +146,11 @@ class ChecksumResolver extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { classes } = this.props;
|
const { classes } = this.props;
|
||||||
const { checksum, activationBytes } = this.state;
|
const { checksum, activationBytes, fileName } = this.state;
|
||||||
//const acc = accAX();
|
//const acc = accAX();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container component="main" maxWidth="xs">
|
<Container component="main" maxWidth="md">
|
||||||
|
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<div className={classes.paper}>
|
<div className={classes.paper}>
|
||||||
|
@ -221,21 +233,15 @@ class ChecksumResolver extends React.Component {
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<ControlledAccordions
|
||||||
|
fileName={fileName}
|
||||||
|
activationBytes={activationBytes}
|
||||||
|
/>
|
||||||
<Box mt={1}>
|
<Box mt={1}>
|
||||||
<Copyright />
|
<this.Copyright />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Accordion backgroundColor="Black">
|
|
||||||
{[1, 2, 3, 4, 5].map(item => {
|
|
||||||
return (
|
|
||||||
<AccordionItem title={`Item ${item}`} expanded={item === 1}>
|
|
||||||
<div>
|
|
||||||
{`Item ${item} content`}
|
|
||||||
</div>
|
|
||||||
</AccordionItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Accordion>
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
171
src/ControlledAccordions.js
Normal file
171
src/ControlledAccordions.js
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { makeStyles, withStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
import { Accordion, AccordionDetails, AccordionSummary } from '@material-ui/core';
|
||||||
|
|
||||||
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import TextField from '@material-ui/core/TextField';
|
||||||
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||||
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
|
|
||||||
|
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||||
|
import FileCopyOutlined from '@material-ui/icons/FileCopyOutlined';
|
||||||
|
|
||||||
|
// import {ExpandMoreIcon, FileCopyOutlined} from '@material-ui/icons';
|
||||||
|
|
||||||
|
import OutputFormatSelection from './OutputFormatSelection'
|
||||||
|
import OSSelector from './OSSelector'
|
||||||
|
|
||||||
|
|
||||||
|
import { Radio, RadioGroup } from '@material-ui/core';
|
||||||
|
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||||
|
import FormControl from '@material-ui/core/FormControl';
|
||||||
|
import FormLabel from '@material-ui/core/FormLabel';
|
||||||
|
|
||||||
|
class ControlledAccordions extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
expanded: "",
|
||||||
|
outputFormat: "m4b",
|
||||||
|
operatingSystem: "win"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
DarkerDisabledTextField = withStyles({
|
||||||
|
root: {
|
||||||
|
marginRight: 8,
|
||||||
|
"& .MuiInputBase-root.Mui-disabled": {
|
||||||
|
color: "rgba(0, 0, 0, 0.6)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(TextField);
|
||||||
|
|
||||||
|
setExpanded = x => this.setState({ expanded: x })
|
||||||
|
handleChange = (panel) => (event, isExpanded) => {
|
||||||
|
this.setExpanded(isExpanded ? panel : false);
|
||||||
|
};
|
||||||
|
|
||||||
|
getCommand = () => {
|
||||||
|
const { outputFormat, operatingSystem } = this.state;
|
||||||
|
let { fileName, activationBytes } = this.props;
|
||||||
|
|
||||||
|
activationBytes = activationBytes ?? "00000000";
|
||||||
|
|
||||||
|
// ffmpeg.exe -y -activation_bytes 9f786605 -i '.\INFINITUM - Die Ewigkeit der Sterne.AAX' -ss 5 -to 20 -c copy out-t01.m4a
|
||||||
|
// faster:
|
||||||
|
// ffmpeg.exe -y -activation_bytes 9f786605 -i '.\INFINITUM - Die Ewigkeit der Sterne.AAX' -map_metadata 0 -id3v2_version 3 -ss 5 -to 20 -vn out-t02.m4a
|
||||||
|
// ffmpeg.exe -y -activation_bytes 9f786605 -i '.\INFINITUM - Die Ewigkeit der Sterne.AAX' -map_metadata 0 -ss 5 -to 20 -vn out-t02.m4a
|
||||||
|
|
||||||
|
// -vn: As an output option, disables video recording i.e. automatic selection or mapping of any video stream. For full manual control see the -map option.
|
||||||
|
// " works on ps and cmd as discriminator
|
||||||
|
|
||||||
|
const outputFormatCodecMaps = [
|
||||||
|
{ format: "m4b", codec: "copy" },
|
||||||
|
{ format: "flac", codec: "flac" },
|
||||||
|
{ format: "mp3", codec: "libmp3lame" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const osToBinMaps = [
|
||||||
|
{ os: "win", cmd: "ffmpeg.exe", discriminator: '"' },
|
||||||
|
{ os: "linux", cmd: "./ffmpeg", discriminator: '\'' },
|
||||||
|
{ os: "osx", cmd: "./ffmpeg", discriminator: '\'' },
|
||||||
|
];
|
||||||
|
|
||||||
|
let fileNameWithoutExtension = fileName.split('.')[0];
|
||||||
|
fileNameWithoutExtension = fileNameWithoutExtension == 'input' ? 'output' : fileNameWithoutExtension;
|
||||||
|
|
||||||
|
const osMap = osToBinMaps.filter(x => x.os == operatingSystem)[0];
|
||||||
|
|
||||||
|
const codec = outputFormatCodecMaps.filter(x => x.format == outputFormat)[0].codec;
|
||||||
|
const bin = osMap.cmd;
|
||||||
|
const di = osMap.discriminator;
|
||||||
|
|
||||||
|
return `${bin} -y`
|
||||||
|
+ ` -activation_bytes ${activationBytes} -i ${di}.\\${fileName}${di}`
|
||||||
|
+ ` -map_metadata 0`
|
||||||
|
+ ` -id3v2_version 3`
|
||||||
|
+ ` -codec:a ${codec}`
|
||||||
|
+ ` -vn ${di}${fileNameWithoutExtension}.${outputFormat}${di}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { classes } = this.props;
|
||||||
|
const { expanded, outputFormat, operatingSystem } = this.state;
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<Accordion expanded={expanded === 'panel2'} onChange={this.handleChange('panel2')}>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ExpandMoreIcon />}
|
||||||
|
aria-controls="panel2bh-content"
|
||||||
|
id="panel2bh-header"
|
||||||
|
>
|
||||||
|
<Typography className={classes.heading}>Command</Typography>
|
||||||
|
<Typography className={classes.secondaryHeading}>
|
||||||
|
Generate ffmpeg command
|
||||||
|
</Typography>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails style={{ display: 'block' }}>
|
||||||
|
<OutputFormatSelection
|
||||||
|
outputFormat={outputFormat}
|
||||||
|
setOutputFormat={x => this.setState({ outputFormat: x })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<OSSelector
|
||||||
|
operatingSystem={operatingSystem}
|
||||||
|
setOperatingSystem={x => this.setState({ operatingSystem: x })}
|
||||||
|
style={{ paddingLeft: '20px' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<this.DarkerDisabledTextField
|
||||||
|
value={this.getCommand()}
|
||||||
|
disabled
|
||||||
|
multiline
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
id="activationBytes"
|
||||||
|
label="cmd"
|
||||||
|
name="activationBytes"
|
||||||
|
autoComplete="activationBytes"
|
||||||
|
aria-readonly
|
||||||
|
fontSize={5}
|
||||||
|
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
endAdornment: (
|
||||||
|
<CopyToClipboard text={this.getCommand()}>
|
||||||
|
<IconButton >
|
||||||
|
<FileCopyOutlined />
|
||||||
|
</IconButton>
|
||||||
|
</CopyToClipboard>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
|
||||||
|
/>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = theme => ({
|
||||||
|
root: {
|
||||||
|
width: '100%',
|
||||||
|
|
||||||
|
},
|
||||||
|
heading: {
|
||||||
|
fontSize: theme.typography.pxToRem(15),
|
||||||
|
flexBasis: '33.33%',
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
secondaryHeading: {
|
||||||
|
fontSize: theme.typography.pxToRem(15),
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export default withStyles(useStyles)(ControlledAccordions);
|
26
src/OSSelector.js
Normal file
26
src/OSSelector.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Radio from '@material-ui/core/Radio';
|
||||||
|
import RadioGroup from '@material-ui/core/RadioGroup';
|
||||||
|
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||||
|
import FormControl from '@material-ui/core/FormControl';
|
||||||
|
import FormLabel from '@material-ui/core/FormLabel';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function OSSelector(props) {
|
||||||
|
|
||||||
|
const { operatingSystem, setOperatingSystem, style } = props;
|
||||||
|
|
||||||
|
const handleChange = (event) => setOperatingSystem(event.target.value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl component="fieldset" style={style}>
|
||||||
|
<FormLabel component="legend">Operating System</FormLabel>
|
||||||
|
<RadioGroup aria-label="format" name="format" value={operatingSystem} onChange={handleChange}>
|
||||||
|
<FormControlLabel value="win" control={<Radio />} label="Windows" />
|
||||||
|
<FormControlLabel value="linux" control={<Radio />} label="Linux" />
|
||||||
|
<FormControlLabel value="osx" control={<Radio />} label="Mac" />
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
29
src/OutputFormatSelection.js
Normal file
29
src/OutputFormatSelection.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Radio from '@material-ui/core/Radio';
|
||||||
|
import RadioGroup from '@material-ui/core/RadioGroup';
|
||||||
|
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||||
|
import FormControl from '@material-ui/core/FormControl';
|
||||||
|
import FormLabel from '@material-ui/core/FormLabel';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function OutputFormatSelection(props) {
|
||||||
|
|
||||||
|
const { outputFormat, setOutputFormat, style } = props;
|
||||||
|
|
||||||
|
const handleChange = (event) => {
|
||||||
|
|
||||||
|
setOutputFormat(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl component="fieldset" style={style}>
|
||||||
|
<FormLabel component="legend">Output Format</FormLabel>
|
||||||
|
<RadioGroup aria-label="format" name="format" value={outputFormat} onChange={handleChange}>
|
||||||
|
<FormControlLabel value="m4b" control={<Radio />} label="AAC m4b" />
|
||||||
|
<FormControlLabel value="flac" control={<Radio />} label="FLAC hq" />
|
||||||
|
<FormControlLabel value="mp3" control={<Radio />} label="Lame mp3" />
|
||||||
|
</RadioGroup>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,13 +1,27 @@
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
font-family:
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
'Segoe UI',
|
||||||
|
'Roboto',
|
||||||
|
'Oxygen',
|
||||||
|
'Ubuntu',
|
||||||
|
'Cantarell',
|
||||||
|
'Fira Sans',
|
||||||
|
'Droid Sans',
|
||||||
|
'Helvetica Neue',
|
||||||
sans-serif;
|
sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
font-family:
|
||||||
|
source-code-pro,
|
||||||
|
Menlo,
|
||||||
|
Monaco,
|
||||||
|
Consolas,
|
||||||
|
'Courier New',
|
||||||
monospace;
|
monospace;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue