Skip to content

Commit 5b4eaf2

Browse files
Make release metadata defaults production-safe with local saved defaults
1 parent df4ed42 commit 5b4eaf2

1 file changed

Lines changed: 88 additions & 18 deletions

File tree

app.tsx

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ type Platform = typeof PLATFORMS[number];
1919
type ItemStatus = 'pending' | 'analyzing' | 'processing' | 'done' | 'error';
2020
type RiskLevel = 'High' | 'Low';
2121

22-
const DEFAULT_RELEASE_ARTIST = 'Sobelo';
23-
const DEFAULT_RELEASE_PRODUCER = 'Triple7';
24-
const DEFAULT_RELEASE_COPYRIGHT = ${new Date().getUTCFullYear()} Sobelo / Triple7 Music`;
22+
const DEFAULT_RELEASE_ARTIST = '';
23+
const RELEASE_DEFAULTS_STORAGE_KEY = 'spectracleanse_release_defaults';
24+
const DEFAULT_RELEASE_PRODUCER = '';
25+
const DEFAULT_RELEASE_COPYRIGHT = '';
2526

2627
// ─────────────────────────────────────────────────────────────────────────────
2728
// Types
@@ -63,6 +64,7 @@ interface ReleaseMetadata {
6364
tags: string;
6465
lyrics: string;
6566
}
67+
type SavedReleaseDefaults = Pick<ReleaseMetadata, 'artist' | 'albumArtist' | 'producer' | 'copyright' | 'genre' | 'description' | 'comment' | 'tags'>;
6668

6769
interface QueueItem {
6870
id: string;
@@ -98,16 +100,36 @@ function PlanBadge({ plan }: { plan: string }) {
98100

99101
const cleanMetadataField = (value: string | undefined | null) => String(value || '').trim();
100102

101-
const getInitialReleaseMetadata = (file: File): ReleaseMetadata => ({
103+
const getSavedReleaseDefaults = (): SavedReleaseDefaults | null => {
104+
try {
105+
const raw = localStorage.getItem(RELEASE_DEFAULTS_STORAGE_KEY);
106+
if (!raw) return null;
107+
const parsed = JSON.parse(raw) as Partial<SavedReleaseDefaults>;
108+
return {
109+
artist: cleanMetadataField(parsed.artist),
110+
albumArtist: cleanMetadataField(parsed.albumArtist),
111+
producer: cleanMetadataField(parsed.producer),
112+
copyright: cleanMetadataField(parsed.copyright),
113+
genre: cleanMetadataField(parsed.genre),
114+
description: cleanMetadataField(parsed.description),
115+
comment: cleanMetadataField(parsed.comment),
116+
tags: cleanMetadataField(parsed.tags),
117+
};
118+
} catch {
119+
return null;
120+
}
121+
};
122+
123+
const getInitialReleaseMetadata = (file: File, savedDefaults: SavedReleaseDefaults | null): ReleaseMetadata => ({
102124
title: file.name.replace(/\.[^.]+$/, ''),
103-
artist: DEFAULT_RELEASE_ARTIST,
104-
albumArtist: DEFAULT_RELEASE_ARTIST,
105-
producer: DEFAULT_RELEASE_PRODUCER,
106-
copyright: DEFAULT_RELEASE_COPYRIGHT,
107-
genre: '',
108-
description: '',
109-
comment: '',
110-
tags: '',
125+
artist: savedDefaults?.artist || DEFAULT_RELEASE_ARTIST,
126+
albumArtist: savedDefaults?.albumArtist || savedDefaults?.artist || DEFAULT_RELEASE_ARTIST,
127+
producer: savedDefaults?.producer || DEFAULT_RELEASE_PRODUCER,
128+
copyright: savedDefaults?.copyright || DEFAULT_RELEASE_COPYRIGHT,
129+
genre: savedDefaults?.genre || '',
130+
description: savedDefaults?.description || '',
131+
comment: savedDefaults?.comment || '',
132+
tags: savedDefaults?.tags || '',
111133
lyrics: '',
112134
});
113135

@@ -727,14 +749,15 @@ export default function App() {
727749

728750
const addFiles = (files: FileList | File[]) => {
729751
const validExt = /\.(mp3|wav|flac|m4a|mp4)$/i;
752+
const savedDefaults = getSavedReleaseDefaults();
730753
const newItems: QueueItem[] = Array.from(files)
731754
.filter(f => validExt.test(f.name))
732755
.slice(0, 20 - queue.length)
733756
.map(file => ({
734757
id: crypto.randomUUID(),
735758
file,
736759
status: 'pending' as ItemStatus,
737-
seo: getInitialReleaseMetadata(file),
760+
seo: getInitialReleaseMetadata(file, savedDefaults),
738761
downloadUrl: null, downloadName: null, report: null, error: null, analysis: null, logs: [],
739762
}));
740763
if (newItems.length === 0) return;
@@ -756,6 +779,20 @@ export default function App() {
756779
updateItem(id, { logs: [...(queue.find(i => i.id === id)?.logs || []), `[${stamp}] ${message}`] });
757780
};
758781

782+
const saveReleaseDefaults = (metadata: ReleaseMetadata) => {
783+
const defaultsToSave: SavedReleaseDefaults = {
784+
artist: cleanMetadataField(metadata.artist),
785+
albumArtist: cleanMetadataField(metadata.albumArtist),
786+
producer: cleanMetadataField(metadata.producer),
787+
copyright: cleanMetadataField(metadata.copyright),
788+
genre: cleanMetadataField(metadata.genre),
789+
description: cleanMetadataField(metadata.description),
790+
comment: cleanMetadataField(metadata.comment),
791+
tags: cleanMetadataField(metadata.tags),
792+
};
793+
localStorage.setItem(RELEASE_DEFAULTS_STORAGE_KEY, JSON.stringify(defaultsToSave));
794+
};
795+
759796
const withOperationTimeout = async <T,>(promise: Promise<T>, timeoutMs: number, timeoutMessage: string): Promise<T> => {
760797
return new Promise<T>((resolve, reject) => {
761798
const timer = window.setTimeout(() => {
@@ -1238,33 +1275,66 @@ export default function App() {
12381275
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Artist</label>
12391276
<input type="text" value={activeItem.seo.artist}
12401277
onChange={e => updateItem(activeItem.id, { seo: { ...activeItem.seo, artist: e.target.value } })}
1278+
placeholder="Your artist name"
1279+
className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:border-cyan-500 outline-none transition-colors" />
1280+
</div>
1281+
<div>
1282+
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Album Artist</label>
1283+
<input type="text" value={activeItem.seo.albumArtist}
1284+
onChange={e => updateItem(activeItem.id, { seo: { ...activeItem.seo, albumArtist: e.target.value } })}
1285+
placeholder="Your artist name or group"
12411286
className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:border-cyan-500 outline-none transition-colors" />
12421287
</div>
12431288
<div>
12441289
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Producer</label>
12451290
<input type="text" value={activeItem.seo.producer}
12461291
onChange={e => updateItem(activeItem.id, { seo: { ...activeItem.seo, producer: e.target.value } })}
1292+
placeholder="Producer / label name"
12471293
className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:border-cyan-500 outline-none transition-colors" />
12481294
</div>
12491295
<div>
12501296
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Copyright</label>
12511297
<input type="text" value={activeItem.seo.copyright}
12521298
onChange={e => updateItem(activeItem.id, { seo: { ...activeItem.seo, copyright: e.target.value } })}
1299+
placeholder="© 2026 Your Name or Label"
12531300
className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:border-cyan-500 outline-none transition-colors" />
12541301
</div>
12551302
<div>
12561303
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Genre</label>
12571304
<input type="text" value={activeItem.seo.genre}
12581305
onChange={e => updateItem(activeItem.id, { seo: { ...activeItem.seo, genre: e.target.value } })}
1259-
placeholder="trap, metal, cinematic…"
1306+
placeholder="trap, hip-hop, latin urban"
12601307
className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:border-cyan-500 outline-none transition-colors" />
12611308
</div>
12621309
</div>
1263-
<p className="text-[11px] text-slate-500">Sobelo / Triple7 values are editable defaults used only when release fields are left blank.</p>
1310+
<div className="flex flex-wrap gap-2">
1311+
<button
1312+
onClick={() => {
1313+
saveReleaseDefaults(resolveReleaseMetadata(activeItem.seo));
1314+
addLog(activeItem.id, 'Release defaults saved.');
1315+
}}
1316+
className="px-3 py-1.5 text-xs bg-cyan-700 hover:bg-cyan-600 rounded-lg"
1317+
>Save as My Defaults</button>
1318+
<button
1319+
onClick={() => {
1320+
localStorage.removeItem(RELEASE_DEFAULTS_STORAGE_KEY);
1321+
addLog(activeItem.id, 'Release defaults cleared.');
1322+
}}
1323+
className="px-3 py-1.5 text-xs bg-slate-700 hover:bg-slate-600 rounded-lg"
1324+
>Clear Saved Defaults</button>
1325+
</div>
12641326
<div>
12651327
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Description</label>
12661328
<textarea rows={3} value={activeItem.seo.description}
12671329
onChange={e => updateItem(activeItem.id, { seo: { ...activeItem.seo, description: e.target.value } })}
1330+
placeholder="Short release description"
1331+
className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:border-cyan-500 outline-none resize-none" />
1332+
</div>
1333+
<div>
1334+
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Comment</label>
1335+
<textarea rows={2} value={activeItem.seo.comment}
1336+
onChange={e => updateItem(activeItem.id, { seo: { ...activeItem.seo, comment: e.target.value } })}
1337+
placeholder="Optional comment / credits"
12681338
className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:border-cyan-500 outline-none resize-none" />
12691339
</div>
12701340
<button onClick={async ()=>{
@@ -1311,14 +1381,14 @@ export default function App() {
13111381
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Tags (comma-separated)</label>
13121382
<input type="text" value={activeItem.seo.tags}
13131383
onChange={e => updateItem(activeItem.id, { seo: { ...activeItem.seo, tags: e.target.value } })}
1314-
placeholder="trap, heavy metal, original…"
1384+
placeholder="comma-separated tags"
13151385
className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:border-cyan-500 outline-none transition-colors" />
13161386
</div>
13171387
<div>
13181388
<label className="block text-[10px] font-bold text-slate-500 uppercase mb-1">Lyrics</label>
13191389
<textarea rows={3} value={activeItem.seo.lyrics}
13201390
onChange={e => updateItem(activeItem.id, { seo: { ...activeItem.seo, lyrics: e.target.value } })}
1321-
placeholder="Optional lyrics to write when available…"
1391+
placeholder="Optional lyrics"
13221392
className="w-full bg-slate-950 border border-slate-700 rounded-lg px-3 py-2 text-sm focus:border-cyan-500 outline-none resize-none" />
13231393
</div>
13241394
</div>
@@ -1371,7 +1441,7 @@ export default function App() {
13711441
comment: activeItem.seo.comment,
13721442
lyrics: activeItem.seo.lyrics || activeItem.analysis?.lyrics || '',
13731443
tags: activeItem.seo.tags,
1374-
publisher: 'Triple7 Music',
1444+
publisher: activeItem.seo.producer || '',
13751445
});
13761446
rewrittenBlob = blob;
13771447
addLog(itemId, `ID3 frames written: ${frameReport.writtenFrames.join(', ') || 'none'}${frameReport.skippedFrames.length ? ` | skipped: ${frameReport.skippedFrames.join(', ')}` : ''}`);

0 commit comments

Comments
 (0)