Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- backend/__pycache__/main.cpython-313.pyc +0 -0
- backend/examples/default/README.md +1 -0
- backend/main.py +18 -0
- frontend/src/App.tsx +3 -0
- frontend/src/api.ts +1 -0
- frontend/src/components/OriginalTrack.tsx +27 -18
- frontend/src/hooks/useSeparation.ts +14 -1
- frontend/src/types.ts +5 -0
backend/__pycache__/main.cpython-313.pyc
CHANGED
|
Binary files a/backend/__pycache__/main.cpython-313.pyc and b/backend/__pycache__/main.cpython-313.pyc differ
|
|
|
backend/examples/default/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
Place the bundled example audio files for the demo flow in this directory.
|
|
|
|
| 2 |
|
| 3 |
Expected filenames:
|
| 4 |
|
|
|
|
| 1 |
Place the bundled example audio files for the demo flow in this directory.
|
| 2 |
+
Song: Satelites - Periphery
|
| 3 |
|
| 4 |
Expected filenames:
|
| 5 |
|
backend/main.py
CHANGED
|
@@ -22,6 +22,7 @@ app = FastAPI(title="Stem Separator")
|
|
| 22 |
MAX_UPLOAD_SIZE = 100 * 1024 * 1024 # 100MB
|
| 23 |
EXAMPLE_NAME = "default"
|
| 24 |
EXAMPLE_DIR = Path(__file__).parent / "examples" / EXAMPLE_NAME
|
|
|
|
| 25 |
EXAMPLE_ORIGINAL_FILE = "original.mp3"
|
| 26 |
EXAMPLE_STEM_FILES = {
|
| 27 |
"Vocals": "Vocals.mp3",
|
|
@@ -188,6 +189,7 @@ async def delete_job(job_id: str):
|
|
| 188 |
@app.get("/api/examples/default")
|
| 189 |
async def get_example_output():
|
| 190 |
original_path = get_example_file(EXAMPLE_ORIGINAL_FILE, allow_original=True)
|
|
|
|
| 191 |
stems = []
|
| 192 |
for stem_name, filename in EXAMPLE_STEM_FILES.items():
|
| 193 |
stem_path = get_example_file(filename)
|
|
@@ -201,9 +203,11 @@ async def get_example_output():
|
|
| 201 |
)
|
| 202 |
|
| 203 |
return {
|
|
|
|
| 204 |
"original": {
|
| 205 |
"filename": original_path.name,
|
| 206 |
"audioUrl": f"/api/examples/{EXAMPLE_NAME}/audio/{original_path.name}",
|
|
|
|
| 207 |
},
|
| 208 |
"stems": stems,
|
| 209 |
"downloadAllUrl": f"/api/examples/{EXAMPLE_NAME}/download/all",
|
|
@@ -269,3 +273,17 @@ def get_example_file(filename: str, allow_original: bool = False) -> Path:
|
|
| 269 |
raise HTTPException(404, f"Missing example asset: {filename}")
|
| 270 |
|
| 271 |
return path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
MAX_UPLOAD_SIZE = 100 * 1024 * 1024 # 100MB
|
| 23 |
EXAMPLE_NAME = "default"
|
| 24 |
EXAMPLE_DIR = Path(__file__).parent / "examples" / EXAMPLE_NAME
|
| 25 |
+
EXAMPLE_README = EXAMPLE_DIR / "README.md"
|
| 26 |
EXAMPLE_ORIGINAL_FILE = "original.mp3"
|
| 27 |
EXAMPLE_STEM_FILES = {
|
| 28 |
"Vocals": "Vocals.mp3",
|
|
|
|
| 189 |
@app.get("/api/examples/default")
|
| 190 |
async def get_example_output():
|
| 191 |
original_path = get_example_file(EXAMPLE_ORIGINAL_FILE, allow_original=True)
|
| 192 |
+
song_name = get_example_song_name()
|
| 193 |
stems = []
|
| 194 |
for stem_name, filename in EXAMPLE_STEM_FILES.items():
|
| 195 |
stem_path = get_example_file(filename)
|
|
|
|
| 203 |
)
|
| 204 |
|
| 205 |
return {
|
| 206 |
+
"song": song_name,
|
| 207 |
"original": {
|
| 208 |
"filename": original_path.name,
|
| 209 |
"audioUrl": f"/api/examples/{EXAMPLE_NAME}/audio/{original_path.name}",
|
| 210 |
+
"songName": song_name,
|
| 211 |
},
|
| 212 |
"stems": stems,
|
| 213 |
"downloadAllUrl": f"/api/examples/{EXAMPLE_NAME}/download/all",
|
|
|
|
| 273 |
raise HTTPException(404, f"Missing example asset: {filename}")
|
| 274 |
|
| 275 |
return path
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
def get_example_song_name() -> str:
|
| 279 |
+
default_name = "Example Song"
|
| 280 |
+
if not EXAMPLE_README.exists():
|
| 281 |
+
return default_name
|
| 282 |
+
|
| 283 |
+
for line in EXAMPLE_README.read_text(encoding="utf-8").splitlines():
|
| 284 |
+
if line.lower().startswith("song:"):
|
| 285 |
+
value = line.split(":", 1)[1].strip()
|
| 286 |
+
if value:
|
| 287 |
+
return value
|
| 288 |
+
|
| 289 |
+
return default_name
|
frontend/src/App.tsx
CHANGED
|
@@ -88,6 +88,7 @@ export default function App() {
|
|
| 88 |
<OriginalTrack
|
| 89 |
url={state.originalUrl}
|
| 90 |
filename={state.filename}
|
|
|
|
| 91 |
/>
|
| 92 |
<OutputFormatSelector
|
| 93 |
value={state.outputFormat}
|
|
@@ -119,6 +120,7 @@ export default function App() {
|
|
| 119 |
<OriginalTrack
|
| 120 |
url={state.originalUrl}
|
| 121 |
filename={state.filename}
|
|
|
|
| 122 |
/>
|
| 123 |
<StemResults
|
| 124 |
stems={state.stems}
|
|
@@ -133,6 +135,7 @@ export default function App() {
|
|
| 133 |
<OriginalTrack
|
| 134 |
url={state.original.audioUrl}
|
| 135 |
filename={state.original.filename}
|
|
|
|
| 136 |
/>
|
| 137 |
<StemResults
|
| 138 |
stems={state.stems}
|
|
|
|
| 88 |
<OriginalTrack
|
| 89 |
url={state.originalUrl}
|
| 90 |
filename={state.filename}
|
| 91 |
+
songName={state.songName}
|
| 92 |
/>
|
| 93 |
<OutputFormatSelector
|
| 94 |
value={state.outputFormat}
|
|
|
|
| 120 |
<OriginalTrack
|
| 121 |
url={state.originalUrl}
|
| 122 |
filename={state.filename}
|
| 123 |
+
songName={state.songName}
|
| 124 |
/>
|
| 125 |
<StemResults
|
| 126 |
stems={state.stems}
|
|
|
|
| 135 |
<OriginalTrack
|
| 136 |
url={state.original.audioUrl}
|
| 137 |
filename={state.original.filename}
|
| 138 |
+
songName={state.original.songName}
|
| 139 |
/>
|
| 140 |
<StemResults
|
| 141 |
stems={state.stems}
|
frontend/src/api.ts
CHANGED
|
@@ -168,6 +168,7 @@ export function subscribeProgress(
|
|
| 168 |
}
|
| 169 |
|
| 170 |
export interface ExampleOutputResponse {
|
|
|
|
| 171 |
original: OriginalTrackAsset;
|
| 172 |
stems: StemAsset[];
|
| 173 |
downloadAllUrl: string;
|
|
|
|
| 168 |
}
|
| 169 |
|
| 170 |
export interface ExampleOutputResponse {
|
| 171 |
+
song?: string;
|
| 172 |
original: OriginalTrackAsset;
|
| 173 |
stems: StemAsset[];
|
| 174 |
downloadAllUrl: string;
|
frontend/src/components/OriginalTrack.tsx
CHANGED
|
@@ -3,28 +3,37 @@ import { WaveformPlayer } from "./WaveformPlayer";
|
|
| 3 |
interface OriginalTrackProps {
|
| 4 |
url: string;
|
| 5 |
filename: string;
|
|
|
|
| 6 |
}
|
| 7 |
|
| 8 |
-
export function OriginalTrack({ url, filename }: OriginalTrackProps) {
|
| 9 |
return (
|
| 10 |
<div className="bg-bg-card rounded-xl p-4 md:p-5">
|
| 11 |
-
<div className="
|
| 12 |
-
<
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
</div>
|
| 29 |
<WaveformPlayer
|
| 30 |
url={url}
|
|
|
|
| 3 |
interface OriginalTrackProps {
|
| 4 |
url: string;
|
| 5 |
filename: string;
|
| 6 |
+
songName?: string;
|
| 7 |
}
|
| 8 |
|
| 9 |
+
export function OriginalTrack({ url, filename, songName }: OriginalTrackProps) {
|
| 10 |
return (
|
| 11 |
<div className="bg-bg-card rounded-xl p-4 md:p-5">
|
| 12 |
+
<div className="mb-3 space-y-1.5">
|
| 13 |
+
<div className="flex items-center gap-2">
|
| 14 |
+
<svg
|
| 15 |
+
className="w-5 h-5 text-accent"
|
| 16 |
+
fill="none"
|
| 17 |
+
viewBox="0 0 24 24"
|
| 18 |
+
stroke="currentColor"
|
| 19 |
+
strokeWidth={2}
|
| 20 |
+
>
|
| 21 |
+
<path
|
| 22 |
+
strokeLinecap="round"
|
| 23 |
+
strokeLinejoin="round"
|
| 24 |
+
d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"
|
| 25 |
+
/>
|
| 26 |
+
</svg>
|
| 27 |
+
<span className="text-sm font-medium text-text-primary truncate">
|
| 28 |
+
{filename}
|
| 29 |
+
</span>
|
| 30 |
+
</div>
|
| 31 |
+
{songName && (
|
| 32 |
+
<p className="text-sm text-text-secondary">
|
| 33 |
+
<span className="font-medium text-text-primary">Song:</span>{" "}
|
| 34 |
+
{songName}
|
| 35 |
+
</p>
|
| 36 |
+
)}
|
| 37 |
</div>
|
| 38 |
<WaveformPlayer
|
| 39 |
url={url}
|
frontend/src/hooks/useSeparation.ts
CHANGED
|
@@ -33,6 +33,7 @@ function reducer(state: AppState, action: AppAction): AppState {
|
|
| 33 |
filename: action.filename,
|
| 34 |
originalUrl: action.originalUrl,
|
| 35 |
outputFormat: action.outputFormat,
|
|
|
|
| 36 |
sourceKind: action.sourceKind,
|
| 37 |
sourceUrl: action.sourceUrl,
|
| 38 |
resolvedUrl: action.resolvedUrl,
|
|
@@ -49,6 +50,7 @@ function reducer(state: AppState, action: AppAction): AppState {
|
|
| 49 |
filename: state.filename,
|
| 50 |
originalUrl: state.originalUrl,
|
| 51 |
outputFormat: state.outputFormat,
|
|
|
|
| 52 |
sourceKind: state.sourceKind,
|
| 53 |
sourceUrl: state.sourceUrl,
|
| 54 |
resolvedUrl: state.resolvedUrl,
|
|
@@ -72,6 +74,7 @@ function reducer(state: AppState, action: AppAction): AppState {
|
|
| 72 |
filename: state.filename,
|
| 73 |
originalUrl: state.originalUrl,
|
| 74 |
outputFormat: state.outputFormat,
|
|
|
|
| 75 |
sourceKind: state.sourceKind,
|
| 76 |
sourceUrl: state.sourceUrl,
|
| 77 |
resolvedUrl: state.resolvedUrl,
|
|
@@ -118,6 +121,7 @@ export function useSeparation() {
|
|
| 118 |
`input${getExtFromFilename(result.filename)}`
|
| 119 |
),
|
| 120 |
outputFormat: getDefaultOutputFormat(result.filename),
|
|
|
|
| 121 |
sourceKind: "file",
|
| 122 |
});
|
| 123 |
} catch (err) {
|
|
@@ -189,6 +193,7 @@ export function useSeparation() {
|
|
| 189 |
`input${getExtFromFilename(result.filename)}`
|
| 190 |
),
|
| 191 |
outputFormat: getDefaultOutputFormat(result.filename),
|
|
|
|
| 192 |
sourceKind: result.platform,
|
| 193 |
sourceUrl: result.source_url,
|
| 194 |
resolvedUrl: result.resolved_url,
|
|
@@ -211,7 +216,10 @@ export function useSeparation() {
|
|
| 211 |
const example = await fetchExampleOutput();
|
| 212 |
dispatch({
|
| 213 |
type: "LOAD_EXAMPLE_DONE",
|
| 214 |
-
original:
|
|
|
|
|
|
|
|
|
|
| 215 |
stems: example.stems,
|
| 216 |
downloadAllUrl: example.downloadAllUrl,
|
| 217 |
});
|
|
@@ -252,6 +260,11 @@ function getExtFromFilename(filename: string): string {
|
|
| 252 |
return dot >= 0 ? filename.slice(dot) : ".wav";
|
| 253 |
}
|
| 254 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
function getDefaultOutputFormat(filename: string): OutputFormat {
|
| 256 |
const ext = getExtFromFilename(filename).toLowerCase();
|
| 257 |
if (ext === ".wav") return "wav";
|
|
|
|
| 33 |
filename: action.filename,
|
| 34 |
originalUrl: action.originalUrl,
|
| 35 |
outputFormat: action.outputFormat,
|
| 36 |
+
songName: action.songName,
|
| 37 |
sourceKind: action.sourceKind,
|
| 38 |
sourceUrl: action.sourceUrl,
|
| 39 |
resolvedUrl: action.resolvedUrl,
|
|
|
|
| 50 |
filename: state.filename,
|
| 51 |
originalUrl: state.originalUrl,
|
| 52 |
outputFormat: state.outputFormat,
|
| 53 |
+
songName: state.songName,
|
| 54 |
sourceKind: state.sourceKind,
|
| 55 |
sourceUrl: state.sourceUrl,
|
| 56 |
resolvedUrl: state.resolvedUrl,
|
|
|
|
| 74 |
filename: state.filename,
|
| 75 |
originalUrl: state.originalUrl,
|
| 76 |
outputFormat: state.outputFormat,
|
| 77 |
+
songName: state.songName,
|
| 78 |
sourceKind: state.sourceKind,
|
| 79 |
sourceUrl: state.sourceUrl,
|
| 80 |
resolvedUrl: state.resolvedUrl,
|
|
|
|
| 121 |
`input${getExtFromFilename(result.filename)}`
|
| 122 |
),
|
| 123 |
outputFormat: getDefaultOutputFormat(result.filename),
|
| 124 |
+
songName: stripExtension(result.filename),
|
| 125 |
sourceKind: "file",
|
| 126 |
});
|
| 127 |
} catch (err) {
|
|
|
|
| 193 |
`input${getExtFromFilename(result.filename)}`
|
| 194 |
),
|
| 195 |
outputFormat: getDefaultOutputFormat(result.filename),
|
| 196 |
+
songName: result.title || stripExtension(result.filename),
|
| 197 |
sourceKind: result.platform,
|
| 198 |
sourceUrl: result.source_url,
|
| 199 |
resolvedUrl: result.resolved_url,
|
|
|
|
| 216 |
const example = await fetchExampleOutput();
|
| 217 |
dispatch({
|
| 218 |
type: "LOAD_EXAMPLE_DONE",
|
| 219 |
+
original: {
|
| 220 |
+
...example.original,
|
| 221 |
+
songName: example.song || example.original.songName,
|
| 222 |
+
},
|
| 223 |
stems: example.stems,
|
| 224 |
downloadAllUrl: example.downloadAllUrl,
|
| 225 |
});
|
|
|
|
| 260 |
return dot >= 0 ? filename.slice(dot) : ".wav";
|
| 261 |
}
|
| 262 |
|
| 263 |
+
function stripExtension(filename: string): string {
|
| 264 |
+
const dot = filename.lastIndexOf(".");
|
| 265 |
+
return dot >= 0 ? filename.slice(0, dot) : filename;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
function getDefaultOutputFormat(filename: string): OutputFormat {
|
| 269 |
const ext = getExtFromFilename(filename).toLowerCase();
|
| 270 |
if (ext === ".wav") return "wav";
|
frontend/src/types.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type InputMode = "upload" | "link";
|
|
| 10 |
export interface OriginalTrackAsset {
|
| 11 |
filename: string;
|
| 12 |
audioUrl: string;
|
|
|
|
| 13 |
}
|
| 14 |
|
| 15 |
export interface StemAsset extends StemResult {
|
|
@@ -26,6 +27,7 @@ export type AppState =
|
|
| 26 |
filename: string;
|
| 27 |
originalUrl: string;
|
| 28 |
outputFormat: OutputFormat;
|
|
|
|
| 29 |
sourceKind: SourceKind;
|
| 30 |
sourceUrl?: string;
|
| 31 |
resolvedUrl?: string;
|
|
@@ -36,6 +38,7 @@ export type AppState =
|
|
| 36 |
filename: string;
|
| 37 |
originalUrl: string;
|
| 38 |
outputFormat: OutputFormat;
|
|
|
|
| 39 |
sourceKind: SourceKind;
|
| 40 |
sourceUrl?: string;
|
| 41 |
resolvedUrl?: string;
|
|
@@ -49,6 +52,7 @@ export type AppState =
|
|
| 49 |
filename: string;
|
| 50 |
originalUrl: string;
|
| 51 |
outputFormat: OutputFormat;
|
|
|
|
| 52 |
sourceKind: SourceKind;
|
| 53 |
sourceUrl?: string;
|
| 54 |
resolvedUrl?: string;
|
|
@@ -72,6 +76,7 @@ export type AppAction =
|
|
| 72 |
filename: string;
|
| 73 |
originalUrl: string;
|
| 74 |
outputFormat: OutputFormat;
|
|
|
|
| 75 |
sourceKind: SourceKind;
|
| 76 |
sourceUrl?: string;
|
| 77 |
resolvedUrl?: string;
|
|
|
|
| 10 |
export interface OriginalTrackAsset {
|
| 11 |
filename: string;
|
| 12 |
audioUrl: string;
|
| 13 |
+
songName?: string;
|
| 14 |
}
|
| 15 |
|
| 16 |
export interface StemAsset extends StemResult {
|
|
|
|
| 27 |
filename: string;
|
| 28 |
originalUrl: string;
|
| 29 |
outputFormat: OutputFormat;
|
| 30 |
+
songName?: string;
|
| 31 |
sourceKind: SourceKind;
|
| 32 |
sourceUrl?: string;
|
| 33 |
resolvedUrl?: string;
|
|
|
|
| 38 |
filename: string;
|
| 39 |
originalUrl: string;
|
| 40 |
outputFormat: OutputFormat;
|
| 41 |
+
songName?: string;
|
| 42 |
sourceKind: SourceKind;
|
| 43 |
sourceUrl?: string;
|
| 44 |
resolvedUrl?: string;
|
|
|
|
| 52 |
filename: string;
|
| 53 |
originalUrl: string;
|
| 54 |
outputFormat: OutputFormat;
|
| 55 |
+
songName?: string;
|
| 56 |
sourceKind: SourceKind;
|
| 57 |
sourceUrl?: string;
|
| 58 |
resolvedUrl?: string;
|
|
|
|
| 76 |
filename: string;
|
| 77 |
originalUrl: string;
|
| 78 |
outputFormat: OutputFormat;
|
| 79 |
+
songName?: string;
|
| 80 |
sourceKind: SourceKind;
|
| 81 |
sourceUrl?: string;
|
| 82 |
resolvedUrl?: string;
|