Skip to content

Commit

Permalink
feat: add mp3 tagging options
Browse files Browse the repository at this point in the history
  • Loading branch information
eidoriantan committed Feb 17, 2024
1 parent fd17383 commit a7ff2cf
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 5 deletions.
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"ffmpeg-static": "^5.1.0",
"fluent-ffmpeg": "^2.1.2",
"jquery": "^3.6.4",
"mp3tag.js": "^3.9.0",
"styled-components": "^5.3.11",
"ytdl-core": "^4.11.5"
},
Expand Down
8 changes: 8 additions & 0 deletions src/components/FileInput/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

import React from 'react';

const FileInput = (props) => {
return <input type="file" {...props} />
}

export default FileInput;
61 changes: 56 additions & 5 deletions src/components/Format/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import { Box, Text, Link, Spinner, FormControl, TextInput, Select, Checkbox, But
import { AlertIcon } from '@primer/octicons-react';
import axios from 'axios';

import { audioTag } from '../../utils/tag';
import { parseBytes } from '../../utils/bytes';
import { downloadURL } from '../../utils/download';
import { handleChange } from '../../utils/change';
import { handleChange, handleFileChange } from '../../utils/change';
import FileInput from '../FileInput';

const Format = () => {
const [info, setInfo] = useState(null);
Expand All @@ -35,6 +37,12 @@ const Format = () => {
const [audioconvert, setAudioconvert] = useState(false);
const [includesAudio, setIncludesAudio] = useState(false);
const [converting, setConverting] = useState(false);
const [adding, setAdding] = useState(false);
const [addTag, setAddTag] = useState(false);
const [file, setFile] = useState(null);
const [title, setTitle] = useState('');
const [artist, setArtist] = useState('');
const [album, setAlbum] = useState('');

const location = useLocation();
const url = useMemo(() => {
Expand Down Expand Up @@ -67,7 +75,20 @@ const Format = () => {
const data = response.data;
if (data.success) {
const fileExt = data.extension;
downloadURL(`/api/download/${data.id}`, `${filename}.${fileExt}`);
let downloadLink = `/api/download/${data.id}`;

if (addTag) {
setAdding(true);
downloadLink = await audioTag(downloadLink, {
artwork: file,
title,
artist,
album
});
setAdding(false);
}

downloadURL(downloadLink, `${filename}.${fileExt}`);
} else {
setError(data.message);
}
Expand All @@ -76,7 +97,7 @@ const Format = () => {
}

setConverting(false);
}, [url, audioitag, videoitag, audioconvert, filename]);
}, [url, audioitag, videoitag, audioconvert, filename, addTag, file, title, artist, album]);

useEffect(() => {
if (!url) return;
Expand Down Expand Up @@ -160,11 +181,41 @@ const Format = () => {
<FormControl.Caption>Converts the audio format you selected to MP3. The audio filesize indicated may change and conversion will take longer</FormControl.Caption>
</FormControl>

<FormControl id="audiotag" disabled={!includesAudio || !audioconvert} sx={{ mt: 3 }}>
<Checkbox name="audiotag" checked={addTag} onChange={handleChange(setAddTag)} />
<FormControl.Label>Add audio metadata tags</FormControl.Label>
<FormControl.Caption>Adds MP3 metadata tags to audio such as artwork, title, artist, album, etc</FormControl.Caption>
</FormControl>

{ includesAudio && audioconvert && addTag && (
<>
<FormControl id="audiotag-artwork" sx={{ mt: 3 }}>
<FormControl.Label>Artwork Image:</FormControl.Label>
<FileInput name="audiotag-artwork" accept="image/*" onChange={handleFileChange(setFile)} />
</FormControl>

<FormControl id="audiotag-title" sx={{ mt: 3 }}>
<FormControl.Label>Title:</FormControl.Label>
<TextInput name="audiotag-title" value={title} autoComplete="off" block onChange={handleChange(setTitle)} />
</FormControl>

<FormControl id="audiotag-artist" sx={{ mt: 3 }}>
<FormControl.Label>Artist:</FormControl.Label>
<TextInput name="audiotag-artist" value={artist} autoComplete="off" block onChange={handleChange(setArtist)} />
</FormControl>

<FormControl id="audiotag-album" sx={{ mt: 3 }}>
<FormControl.Label>Album:</FormControl.Label>
<TextInput name="audiotag-album" value={album} autoComplete="off" block onChange={handleChange(setAlbum)} />
</FormControl>
</>
)}

{ error && <Flash variant="danger" sx={{ my: 2 }}>{ error }</Flash> }

<Button type="submit" variant="primary" sx={{ display: 'block', width: '100%', mt: 2 }} disabled={converting}>
<Button type="submit" variant="primary" sx={{ display: 'block', width: '100%', mt: 3 }} disabled={converting}>
<Spinner size="small" sx={{ display: converting ? '' : 'none', mr: 2 }} />
<Text>Convert and Download</Text>
<Text>{ adding ? 'Adding tags...' : 'Convert and Download' }</Text>
</Button>

<NavLink to="/" style={{ textDecoration: 'none' }}>
Expand Down
7 changes: 7 additions & 0 deletions src/utils/change.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,10 @@ export function handleChange (setter) {
setter(input.type === 'checkbox' ? (val => !val) : input.value);
}
}

export function handleFileChange (setter) {
return (event) => {
const input = event.target;
setter(input.type === 'file' ? input.files[0] : input.value);
}
}
44 changes: 44 additions & 0 deletions src/utils/tag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

import MP3Tag from 'mp3tag.js';
import axios from 'axios';

export async function audioTag (url, tags) {
const response = await axios({
url,
method: 'GET',
responseType: 'blob'
});

const buffer = await response.data.arrayBuffer();
const mp3tag = new MP3Tag(buffer, true);
mp3tag.read();
if (mp3tag.error) throw new Error(mp3tag.error);

if (tags.artwork) {
const artworkBytes = new Uint8Array(await tags.artwork.arrayBuffer());
mp3tag.tags.v2.APIC = [
{
format: tags.artwork.type,
type: 3,
description: '',
data: artworkBytes
}
];
}

mp3tag.tags.title = tags.title;
mp3tag.tags.artist = tags.artist;
mp3tag.tags.album = tags.album;
mp3tag.save({
id3v1: { include: false },
id3v2: {
include: true,
version: 3,
padding: 1024
}
});
if (mp3tag.error) throw new Error(mp3tag.error);

const blob = new Blob([mp3tag.buffer]);
return URL.createObjectURL(blob);
}

0 comments on commit a7ff2cf

Please sign in to comment.