Skip to content

Commit

Permalink
✨ user defined output bitrates, own context for lame converter
Browse files Browse the repository at this point in the history
  • Loading branch information
richard-unterberg committed Jan 7, 2024
1 parent 396627b commit 4bdc903
Show file tree
Hide file tree
Showing 26 changed files with 323 additions and 164 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# squeez
# squeezer
## browser based wav to mp3 converter

Tired of searching for a reliable WAV to MP3 converter that doesn't come with limitations or require registrations? So was I. That's why I created this hassle-free solution to help you seamlessly convert your audio files in the browser. 🌊

## See it in [action](https://richard-unterberg.github.io/squeez/)

### Features:
- Convert WAV to MP3
- Download converted file
Expand All @@ -11,6 +13,8 @@ Tired of searching for a reliable WAV to MP3 converter that doesn't come with li
- No ads
- No server required (Developers)



### Coming soon:
- Define bitrate of output file
- Show status of conversion
Expand Down
2 changes: 1 addition & 1 deletion components/Popover.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import tw from 'tailwind-styled-components'

export default tw.div`
export default tw.aside`
fixed
md:absolute
top-0
Expand Down
33 changes: 0 additions & 33 deletions lamejs/useLameContext.tsx

This file was deleted.

38 changes: 0 additions & 38 deletions lamejs/useUploadContext.ts

This file was deleted.

8 changes: 4 additions & 4 deletions layouts/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import LinkElement from '#components/LinkElement'

const Footer = () => (
<div className="mb-20 mt-20 text-small text-center">
<footer className="pb-20 mt-20 text-small text-center">
<div className="w-1/2 mb-10 border-grayDark h-1 border-dotted border-b-2 mx-auto" />
<LinkElement href="https://github.com/richard-unterberg/squeez">
⭐️ https://github.com/richard-unterberg/squeez
<LinkElement href="https://github.com/richard-unterberg/squeezer">
⭐️ richard-unterberg/squeezer
</LinkElement>
</div>
</footer>
)

export default Footer
19 changes: 9 additions & 10 deletions layouts/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,32 @@ const Header = () => {
const { spacing } = useAppTheme()

return (
<div>
<header>
<Popover>
<Link aria-label="to-github" href="https://github.com/richard-unterberg/squeez" external>
<Link aria-label="to-github" href="https://github.com/richard-unterberg/squeezer" external>
<Icon icon={ICON_ID.Github} size={spacing(8)} className="text-dark" />
</Link>
</Popover>

<div className="xs:block md:flex gap-6 mt-16 my-10 items-center">
<Icon
icon={ICON_ID.AudioLines}
className="mx-auto mb-4 md:mx-0 text-warning"
size={spacing(24)}
/>
<div className="text-center md:text-left">
<h1 className="text-2xl md:text-3xl mb-2 font-bold">squeez</h1>
<p className="font-mono">wav-to-mp3</p>
<h1 className="text-2xl md:text-3xl mb-2 font-bold">squeezer</h1>
<h2 className="font-normal mb-6">
browser based wav to mp3 converter
<br /> no registration, no ads, no tracking
free wav to mp3 converter - no registration, no ads, no tracking 🤯
</h2>
</div>
</div>
<p className="mb-10">
{`Tired of searching for a reliable WAV to MP3 converter that doesn't come with limitations or
require registrations? So was I. That's why I created this hassle-free solution to help you
seamlessly convert your audio files in the browser. 🌊`}
{`Tired of searching the web for a reliable WAV-to-MP3 converter that doesn't come with limitations or
require registrations? So was I. That's why I created this hassle-free solution to help you and me to
seamlessly convert audio files directly in the browser. 💫`}
</p>
</div>
</header>
)
}

Expand Down
50 changes: 41 additions & 9 deletions layouts/LayoutDefault.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,47 @@ import '@fontsource/inter/latin-700.css'
import '#layouts/styles.css'

import { ReactNode, StrictMode } from 'react'
import Dropzone from 'react-dropzone'
import { PageContextClient, PageContextServer } from 'vike/types'

import useUploadContext from '#hooks/useUploadContext'
import LameContextProvider from '#lamejs/LameContextProvider'
import UploadContextProvider from '#lamejs/UploadContextProvider'
import Footer from '#layouts/Footer'
import Header from '#layouts/Header'
import { PageContextProvider } from '#renderer/usePageContext'

const LayoutDefault = ({
const LayoutDefault = ({ children }: { children: ReactNode }) => {
const { isDropping, setIsDropping, setAttachments } = useUploadContext()

const handleDrop = (acceptedFiles: File[]) => {
setAttachments(acceptedFiles)
setIsDropping(false)
}

return (
<Dropzone
noClick
onDragOver={() => setIsDropping(true)}
onDragLeave={() => setIsDropping(false)}
onDrop={handleDrop}
>
{({ getRootProps }) => (
<div {...getRootProps()} className="absolute inset-0">
<div className={`max-w-4xl m-auto text-light ${isDropping ? 'opacity-10' : ''}`}>
<div className="relative container px-5 mx-auto text-white text-base">
<Header />
<div>{children}</div>
<Footer />
</div>
</div>
</div>
)}
</Dropzone>
)
}

const LayoutProviderWrapper = ({
pageContext,
children,
}: {
Expand All @@ -18,15 +52,13 @@ const LayoutDefault = ({
}) => (
<StrictMode>
<PageContextProvider pageContext={pageContext}>
<div className="max-w-4xl m-auto text-light">
<div className="relative container px-5 mx-auto text-white text-base">
<Header />
<div className="page-portal">{children}</div>
<Footer />
</div>
</div>
<UploadContextProvider>
<LameContextProvider>
<LayoutDefault>{children}</LayoutDefault>
</LameContextProvider>
</UploadContextProvider>
</PageContextProvider>
</StrictMode>
)

export default LayoutDefault
export default LayoutProviderWrapper
5 changes: 3 additions & 2 deletions lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const AppConfig = {
viteBaseUrl: '/squeez',
viteBaseUrl: '/squeezer',
defaultOutputBitrate: 320,
}

export const SomeIteration = [2, 4, 8, 16]
export const OutputBitrates = [32, 40, 48, 56, 64, 80, 96, 112, 128, 192, 224, 256, 320]
39 changes: 39 additions & 0 deletions lib/hooks/useLameContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useCallback, useContext, useMemo } from 'react'

import useUploadContext from '#hooks/useUploadContext'
import convertTo from '#lamejs/convert'
import { LameContext } from '#lamejs/LameContextProvider'

const useLameContext = () => {
const context = useContext(LameContext)
const lamejs = useMemo(() => context?.lamejs, [context?.lamejs])
const setLamejs = useMemo(() => context?.setLamejs, [context?.setLamejs])
const outputBits = useMemo(() => context?.outputBits, [context?.outputBits])
const setOutputBits = useMemo(() => context?.setOutputBits, [context?.setOutputBits])
const { attachments } = useUploadContext()

const handleConvert = useCallback(() => {
const conversionSequence = attachments.reduce(
(chain, file) =>
chain.then(() =>
convertTo({ file, lameLib: lamejs, outputBits })
.then(mp3File => {
const downloadLink = document.createElement('a')
downloadLink.href = URL.createObjectURL(mp3File)
downloadLink.download = mp3File.name
downloadLink.click()
})
.catch(error => {
throw new Error(error)
}),
),
Promise.resolve(),
)

return conversionSequence
}, [attachments, lamejs, outputBits])

return { lamejs, outputBits, setLamejs, handleConvert, setOutputBits }
}

export default useLameContext
76 changes: 76 additions & 0 deletions lib/hooks/useUploadContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useCallback, useContext, useMemo } from 'react'

import { UploadContext } from '#lamejs/UploadContextProvider'

const isValidWav = (file: File) => file.type === 'audio/wav'

const useUploadContext = () => {
const context = useContext(UploadContext)

const attachments = useMemo(() => context?.attachments || [], [context?.attachments])
const handleSetAttachments = useMemo(
() => context?.setAttachments || (() => []),
[context?.setAttachments],
)

const formatError = useMemo(() => context?.formatError || false, [context?.formatError])
const setFormatError = useMemo(
() => context?.setFormatError || (() => false),
[context?.setFormatError],
)

const isDropping = useMemo(() => context?.isDropping || false, [context?.isDropping])
const setIsDropping = useMemo(
() => context?.setIsDropping || (() => false),
[context?.setIsDropping],
)

const setAttachments = useCallback(
(files: File[]) => {
files.forEach(file => {
if (!isValidWav(file)) {
setFormatError('File must be a .wav file - Files ignored')
return
}
const isDuplicate = attachments.some(prevFile => prevFile.name === file.name)

if (isDuplicate) {
setFormatError('File with the same name already exists - Files ignored')
} else {
// Add the file to the array if it's not a duplicate
handleSetAttachments(previousFiles => [...previousFiles, file])
setFormatError(false)
}
})
},
[attachments, handleSetAttachments, setFormatError],
)

const removeAttachment = useCallback(
(fileName: string) => {
handleSetAttachments(previousFiles => previousFiles.filter(file => file.name !== fileName))
},
[handleSetAttachments],
)

const clearAttachments = useCallback(() => {
handleSetAttachments([])
}, [handleSetAttachments])

if (!context) {
throw new Error('useUploadContext must be used within a Dialog')
}

return {
attachments,
formatError,
isDropping,
setAttachments,
setIsDropping,
setFormatError,
removeAttachment,
clearAttachments,
} as const
}

export default useUploadContext
1 change: 1 addition & 0 deletions lib/icons/iconID.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export enum ICON_ID {
AudioLines,
Drum,
FileVolume,
Trash,
}
2 changes: 2 additions & 0 deletions lib/icons/iconMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
PackageCheck,
PencilRuler,
Sailboat,
Trash2,
} from 'lucide-react'

import { ICON_ID } from './iconID'
Expand All @@ -40,6 +41,7 @@ const APP_ICON: ICON_TYPE = {
[ICON_ID.AudioLines]: { component: AudioLines },
[ICON_ID.Drum]: { component: Drum },
[ICON_ID.FileVolume]: { component: FileVolume2 },
[ICON_ID.Trash]: { component: Trash2 },
}

export default APP_ICON
Loading

0 comments on commit 4bdc903

Please sign in to comment.