Skip to content

Commit

Permalink
Audio cover generator (#445)
Browse files Browse the repository at this point in the history
* waveform image generator
---------

Co-authored-by: Yannick Goossens <yannick@ziroh.be>
  • Loading branch information
ryangtanaka-org and Zir0h authored Jan 12, 2025
1 parent 8e04cb5 commit 6d11464
Show file tree
Hide file tree
Showing 15 changed files with 380 additions and 37 deletions.
10 changes: 10 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 @@ -24,6 +24,7 @@
"markdown-to-jsx": "^7.1.9",
"mime-types": "^2.1.35",
"react": "^18.2.0",
"react-audio-visualize": "^1.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.43.1",
"react-infinite-scroller": "^1.2.6",
Expand Down
1 change: 1 addition & 0 deletions src/atoms/modal/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
}

.modalContent {
position: absolute;
top: 0;
background-color: var(--background-color);
padding: 20px;
Expand Down
99 changes: 99 additions & 0 deletions src/components/form/AudioCoverMetadataOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { HEN_CONTRACT_FA2 } from '@constants';
import React, { useEffect, useRef } from 'react';

export const combineVisualizerWithMetadata = async (
visualizerRef: HTMLDivElement,
fileValue: {
artifact?: {
name?: string;
size?: number;
mimeType?: string;
};
}
): Promise<Blob> => {
return new Promise((resolve, reject) => {
try {
// Create a new canvas for the final image
const finalCanvas = document.createElement('canvas');
finalCanvas.width = 618; // Match the AudioVisualizer dimensions
finalCanvas.height = 382;

const ctx = finalCanvas.getContext('2d');
if (!ctx) throw new Error('Could not get canvas context');

// Fill with black background
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, finalCanvas.width, finalCanvas.height);

// Get both canvas elements
const visualizerCanvas = visualizerRef.querySelector('canvas') as HTMLCanvasElement | null;
const metadataCanvas = visualizerRef.querySelector('canvas:last-child') as HTMLCanvasElement | null;

if (!visualizerCanvas || !metadataCanvas) {
throw new Error('Could not find canvas elements');
}

// First draw the visualizer
ctx.drawImage(visualizerCanvas, 0, 0);

// Then draw the metadata canvas on top
ctx.drawImage(metadataCanvas, 0, 0);

finalCanvas.toBlob((blob) => {
if (blob) resolve(blob);
else reject(new Error('Failed to create blob'));
}, 'image/png');
} catch (error) {
reject(error);
}
});
};

interface MetadataOverlayProps {
title: string;
artist: string;
mimeType: string;
style?: React.CSSProperties;
}

const MetadataOverlay: React.FC<MetadataOverlayProps> = ({
title,
artist,
mimeType,
style
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null);

useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;

const ctx = canvas.getContext('2d');
if (!ctx) return;

ctx.clearRect(0, 0, canvas.width, canvas.height);

ctx.fillStyle = 'white';
ctx.font = '16px monospace';
ctx.globalAlpha = 0.8;

// Draw metadata
ctx.fillText(`Title: ${title}`, 15, 25);
ctx.fillText(`Wallet: ${artist}`, 15, 45);
ctx.fillText(`${new Date().toISOString()}`, 15, 65);
ctx.fillText(`${mimeType}`, 15, 85);
ctx.fillText(`Mint Contract: ${HEN_CONTRACT_FA2} (HEN/TEIA)`, 15, 370);

}, [title, artist, mimeType]);

return (
<canvas
ref={canvasRef}
width={618}
height={382}
style={{ ...style, pointerEvents: 'none' }}
/>
);
};

export default MetadataOverlay;
15 changes: 10 additions & 5 deletions src/components/form/CustomCopyrightForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const initialClauses = {
exclusiveRights: 'none', // Options are 'none', 'majority', 'superMajority'
retainCreatorRights: true, // When exclusive rights conditions are met, does the Creator retain their rights to their own work?
releasePublicDomain: false,
requireAttribution: false,
requireAttribution: true,
rightsAreTransferable: true,
expirationDate: '',
expirationDateExists: false,
Expand Down Expand Up @@ -165,10 +165,12 @@ export const ClausesDescriptions = ({ clauses }) => {
)
}

function CustomCopyrightForm({ onChange, value }) {
function CustomCopyrightForm({ onChange, value, defaultValue }) {
const { watch } = useFormContext()
const { license, minterName, address } = useOutletContext()
const [clauses, setClauses] = useState(initialClauses)
const [clauses, setClauses] = useState(
defaultValue?.clauses || initialClauses
)
const [generatedDocument, setGeneratedDocument] = useState(
'No Permissions Chosen'
)
Expand Down Expand Up @@ -598,7 +600,8 @@ Unless stated otherwise (in this Agreement itself), this Agreement remains effec
<Checkbox
name="expirationDateExists"
label="Add an Expiration Date to Clauses"
checked={clauses.expirationDateExists}
checked={clauses?.expirationDateExists}
defaultValue={defaultValue?.clauses?.expirationDateExists}
onCheck={handleDateChange}
className={styles.field}
/>
Expand All @@ -619,8 +622,9 @@ Unless stated otherwise (in this Agreement itself), this Agreement remains effec
<h4>{clauseLabels.expirationDate}</h4>
<Input
type="date"
value={clauses.expirationDate}
value={clauses?.expirationDate}
onChange={(e) => handleChange(e, 'expirationDate')}
defaultValue={defaultValue?.clauses?.expirationDate}
className={styles.field}
/>
</div>
Expand All @@ -634,6 +638,7 @@ Unless stated otherwise (in this Agreement itself), this Agreement remains effec
value={clauses?.addendum || ''}
onChange={handleInputChange}
placeholder="Add additional notes, clauses, restrictions, scopes, etc."
defaultValue={defaultValue?.clauses?.addendum}
className={styles.field}
/>
</div>
Expand Down
16 changes: 14 additions & 2 deletions src/components/form/Form.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import Button from '@atoms/button/Button'
import { get } from 'lodash'
import { memo } from 'react'
import { memo, useEffect } from 'react'
import { useFormContext } from 'react-hook-form'
import { FormFields } from './FormFields'
import { useMintStore } from '@context/mintStore'

function Form({ fields, defaultValues, children, onSubmit, onReset }) {
const {
Expand All @@ -14,6 +15,14 @@ function Form({ fields, defaultValues, children, onSubmit, onReset }) {
formState: { errors },
} = useFormContext()

// Watch all form values and update mintStore
const formValues = watch()
useEffect(() => {
if (Object.keys(formValues).length > 0) {
useMintStore.setState(formValues)
}
}, [formValues])

return (
<form style={{ width: '100%' }} onSubmit={handleSubmit(onSubmit)}>
{fields.map((f) => {
Expand All @@ -24,7 +33,10 @@ function Form({ fields, defaultValues, children, onSubmit, onReset }) {
register={register}
value={value}
control={control}
field={f}
field={{
...f,
defaultValue: defaultValues?.[f.name],
}}
error={get(errors, f.name)}
/>
)
Expand Down
Loading

0 comments on commit 6d11464

Please sign in to comment.