Skip to content

Commit

Permalink
Adding mp3 upload option
Browse files Browse the repository at this point in the history
  • Loading branch information
thepirat000 committed Nov 20, 2019
1 parent 2b3ed6c commit 639f7da
Show file tree
Hide file tree
Showing 12 changed files with 467 additions and 86 deletions.
202 changes: 202 additions & 0 deletions Controllers/Mp3Controller.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using SpleeterAPI.Youtube;
using SpleeterAPI.Split;
using System.Collections.Concurrent;

namespace SpleeterAPI.Controllers
{

[Route("mp3")]
[ApiController]
public class Mp3Controller : ControllerBase
{
private static long Max_Upload_Size = long.Parse(Startup.Configuration["Mp3:MaxUploadSize"]);
private readonly static ConcurrentDictionary<string, DateTime> _processing = new ConcurrentDictionary<string, DateTime>();

private readonly ILogger<Mp3Controller> _logger;
public Mp3Controller(ILogger<Mp3Controller> logger)
{
_logger = logger;
}

[HttpPost("p")]
[Produces("application/json")]
public async Task<ActionResult<ProcessResponse>> Process([FromForm] string format, [FromForm] bool includeHf)
{
if (format != "2stems" && format != "4stems" && format != "5stems" && format != "karaoke")
{
return BadRequest("Format must be '2stems', '4stems' or '5stems'");
}
if (Request.Form.Files?.Count == 0)
{
return BadRequest("No files to process");
}
var totalBytes = Request.Form.Files.Sum(f => f.Length);
if (totalBytes > Max_Upload_Size)
{
return BadRequest($"Can't process more than {Max_Upload_Size / 1024:N0} Mb of data");
}

var archiveName = GetFileId(Request.Form.Files, format, includeHf, false);

var now = DateTime.UtcNow;
if (_processing.TryGetValue(archiveName, out DateTime startDate))
{
var startedSecondsAgo = (now - startDate).TotalSeconds;
if (startedSecondsAgo < 1800)
{
return new ProcessResponse() { Error = $"File {archiveName} is being processed, started {startedSecondsAgo:N0} seconds ago. Try again later in few more minutes..." };
}
}

if (format == "karaoke")
{
if (Request.Form.Files.Count == 1)
{
var mp3File = $"/output/{archiveName}.mp3";
if (System.IO.File.Exists(mp3File))
{
return new ProcessResponse() { FileId = $"{archiveName}.mp3" };
}
}
}

var zipFile = $"/output/{archiveName}.zip";
if (System.IO.File.Exists(zipFile))
{
return new ProcessResponse() { FileId = $"{archiveName}.zip" };
}

_processing[archiveName] = now;

var inputFolder = $"/input/{archiveName}";
if (!Directory.Exists(inputFolder))
{
Directory.CreateDirectory(inputFolder);
}
var inputFilenames = new List<string>();

// 1. copy input files to a temp folder
foreach (var file in Request.Form.Files)
{
var fileName = SanitizeFilename(file.FileName);
inputFilenames.Add($"{fileName}");
var filePath = $"{inputFolder}/{fileName}";
if (!System.IO.File.Exists(filePath))
{
using (var output = System.IO.File.Create(filePath))
{
await file.CopyToAsync(output);
}
}
}
if (inputFilenames.Count == 0)
{
_processing.TryRemove(archiveName, out _);
return Problem("Unknown problem when creating files");
}

// 2. call spleeter with those multiple files
var sw = Stopwatch.StartNew();
var inputFileParam = string.Join(' ', inputFilenames.Select(fn => $"\"{inputFolder}/{fn}\""));
var separateResult = SpliterHelper.Split(inputFileParam, archiveName, format, includeHf, _logger, isBatch: true);
_logger.LogInformation($"Separation for {inputFilenames.Count} files:\n\tProcessing time: {sw.Elapsed:hh\\:mm\\:ss}");

if (separateResult.ExitCode != 0)
{
_processing.TryRemove(archiveName, out _);
return Problem($"spleeter separate command exited with code {separateResult.ExitCode}\nException: {separateResult.Exception}\nMessages: {separateResult.Output}.");
}

// 2.1 If karaoke
if (format == "karaoke")
{
// 2.1.1 If just 1 file -> copy to output renaming as karaoke and return mp3 file name
if (inputFilenames.Count == 1)
{
System.IO.File.Copy($"/output/{archiveName}/{Path.GetFileNameWithoutExtension(inputFilenames[0])}/accompaniment.mp3", $"/output/{archiveName}.mp3");
Directory.Delete($"/output/{archiveName}", true);
Directory.Delete(inputFolder, true);
_processing.TryRemove(archiveName, out _);
return new ProcessResponse() { FileId = $"{archiveName}.mp3" };
}
else
{
// More than 1 karaoke -> remove all the vocals.mp3
foreach (var file in Directory.EnumerateFiles($"/output/{archiveName}", "vocals.mp3", SearchOption.AllDirectories))
{
System.IO.File.Delete(file);
}
}
}

// 3. Zip the output folder
ZipFile.CreateFromDirectory($"/output/{archiveName}", zipFile, CompressionLevel.Fastest, false);

// 4. Delete temp files
Directory.Delete($"/output/{archiveName}", true);
Directory.Delete(inputFolder, true);

_processing.TryRemove(archiveName, out _);

return new ProcessResponse() { FileId = $"{archiveName}.zip" };
}


[HttpGet("d")]
[Produces("application/json")]
public ActionResult Download([FromQuery] string fn)
{
if (string.IsNullOrWhiteSpace(fn))
{
return BadRequest();
}
var file = $"/output/{fn}";
var cType = fn.ToLower().EndsWith("zip") ? "application/zip" : "application/mp3";
if (System.IO.File.Exists(file))
{
return PhysicalFile(file, cType, fn);
}
return Problem($"File {fn} not found");
}

private string GetFileId(IFormFileCollection files, string format, bool includeHf, bool includeOri)
{
int hash1 = string.Join('|', files.OrderBy(f => f.FileName).Select(f => f.FileName).ToArray()).GetStableHashCode();
int hash2 = (int)(files.Sum(f => f.Length) % int.MaxValue);
var fileId = StringHelper.SeededString(hash1, 8) + StringHelper.SeededString(hash2, 8);
fileId += $".{format}";
if (includeOri || includeHf)
{
fileId += ".";
if (includeHf)
{
fileId += "hf";
}
if (includeOri)
{
fileId += "o";
}
}
return fileId;
}

private string SanitizeFilename(string filename)
{
filename = filename.Replace("/", "").Replace("\\", "").Replace("\"", "");
return string.Concat(filename.Split(Path.GetInvalidFileNameChars()));
}

}
}
17 changes: 10 additions & 7 deletions Controllers/YoutubeController.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Compression;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using SpleeterAPI.Youtube;

namespace SpleeterAPI.Controllers
{

[Route("yt")]
[ApiController]
public class YoutubeController : ControllerBase
Expand All @@ -28,7 +31,7 @@ public YoutubeController(ILogger<YoutubeController> logger)
/// <param name="format">2stems, 4stems or 5stems</param>
[HttpGet("p/{format}/{vid}")]
[Produces("application/json")]
public ActionResult<YoutubeProcessResponse> Process([FromRoute] string format, [FromRoute] string vid, [FromQuery] bool includeOriginalAudio = false, [FromQuery] bool hf = false)
public ActionResult<ProcessResponse> Process([FromRoute] string format, [FromRoute] string vid, [FromQuery] bool includeOriginalAudio = false, [FromQuery] bool hf = false)
{
if (format != "2stems" && format != "4stems" && format != "5stems" && format != "karaoke")
{
Expand All @@ -39,11 +42,11 @@ public ActionResult<YoutubeProcessResponse> Process([FromRoute] string format, [
var info = YoutubeHelper.GetVideoInfo(vid);
if (info.DurationSeconds == 0)
{
return new YoutubeProcessResponse() { Error = $"Cannot process live videos" };
return new ProcessResponse() { Error = $"Cannot process live videos" };
}
if (info.DurationSeconds > Max_Duration_Seconds)
{
return new YoutubeProcessResponse() { Error = $"Cannot process videos longer than {Max_Duration_Seconds} seconds" };
return new ProcessResponse() { Error = $"Cannot process videos longer than {Max_Duration_Seconds} seconds" };
}

// Set the file name
Expand All @@ -56,21 +59,21 @@ public ActionResult<YoutubeProcessResponse> Process([FromRoute] string format, [
var startedSecondsAgo = (now - startDate).TotalSeconds;
if (startedSecondsAgo < 1800)
{
return new YoutubeProcessResponse() { Error = $"File {fileId} is being processed, started {startedSecondsAgo:N0} seconds ago. Try again later in few more minutes..." };
return new ProcessResponse() { Error = $"File {fileId} is being processed, started {startedSecondsAgo:N0} seconds ago. Try again later in few more minutes..." };
}
}
if (format == "karaoke")
{
var mp3File = $"/output/{fileId}.mp3";
if (System.IO.File.Exists(mp3File))
{
return new YoutubeProcessResponse() { FileId = fileId };
return new ProcessResponse() { FileId = fileId };
}
}
var zipFile = $"/output/{fileId}.zip";
if (System.IO.File.Exists(zipFile))
{
return new YoutubeProcessResponse() { FileId = fileId };
return new ProcessResponse() { FileId = fileId };
}

_processing[fileId] = now;
Expand Down Expand Up @@ -109,7 +112,7 @@ public ActionResult<YoutubeProcessResponse> Process([FromRoute] string format, [

_processing.TryRemove(fileId, out _);

return new YoutubeProcessResponse() { FileId = fileId };
return new ProcessResponse() { FileId = fileId };
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion SpleeterAPI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<DockerfileContext>.</DockerfileContext>
<DockerfileRunArguments>-v "C:/spleeter/output:/output" -v "C:/Spleeter/model":/model -e MODEL_PATH=/model</DockerfileRunArguments>
<DockerfileRunArguments>-v "C:/spleeter/input:/input" -v "C:/spleeter/output:/output" -v "C:/Spleeter/model":/model -e MODEL_PATH=/model</DockerfileRunArguments>
</PropertyGroup>


Expand Down
5 changes: 3 additions & 2 deletions Split/SpleeterHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public static class SpliterHelper
{
private static string Max_Duration = Startup.Configuration["Spleeter:MaxDuration"];

public static ShellExecutionResult Split(string inputFile, string fileId, string format, bool includeHighFreq, ILogger log)
public static ShellExecutionResult Split(string inputFile, string fileId, string format, bool includeHighFreq, ILogger log, bool isBatch = false)
{
if (format == "karaoke")
{
Expand All @@ -25,7 +25,8 @@ public static ShellExecutionResult Split(string inputFile, string fileId, string
formatParam = $"-p spleeter:{format}";
}
var maxDurationParam = Max_Duration == "" ? "" : $"--max_duration {Max_Duration}";
cmd = $"python -m spleeter separate -i '{inputFile}' -o '/output/{fileId}' {maxDurationParam} {formatParam} -c mp3";
var inputParam = "-i " + (isBatch ? inputFile : $"'{inputFile}'");
cmd = $"python -m spleeter separate {inputParam} -o '/output/{fileId}' {maxDurationParam} {formatParam} -c mp3";
log.LogInformation($"Will execute: {cmd}");
var result = ShellHelper.Bash(cmd);
return result;
Expand Down
44 changes: 44 additions & 0 deletions Split/StringHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace SpleeterAPI.Split
{
public static class StringHelper
{
private static string Valid_Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

public static string SeededString(int seed, int size)
{
var stringChars = new char[size];
var random = new Random(seed);
for (int i = 0; i < stringChars.Length; i++)
{
stringChars[i] = Valid_Chars[random.Next(Valid_Chars.Length)];
}
var finalString = new String(stringChars);
return finalString;
}

public static int GetStableHashCode(this string str)
{
unchecked
{
int hash1 = 5381;
int hash2 = hash1;

for (int i = 0; i < str.Length && str[i] != '\0'; i += 2)
{
hash1 = ((hash1 << 5) + hash1) ^ str[i];
if (i == str.Length - 1 || str[i + 1] == '\0')
break;
hash2 = ((hash2 << 5) + hash2) ^ str[i + 1];
}

return hash1 + (hash2 * 1566083941);
}
}

}
}
12 changes: 2 additions & 10 deletions Startup/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,7 @@ public Startup(IConfiguration configuration)
public void ConfigureServices(IServiceCollection services)
{
// Do not use CORS, should be added by nginx proxy
/*services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins,
builder =>
{
builder.AllowAnyOrigin();
});
});*/

//services.AddCors();

services.AddControllers();
}
Expand All @@ -55,7 +47,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseRouting();

// Do not use CORS, should be added by nginx proxy
//app.UseCors(MyAllowSpecificOrigins);
//app.UseCors(options => options.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());

app.UseAuthorization();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
namespace SpleeterAPI.Youtube
{

public class YoutubeProcessResponse
public class ProcessResponse
{
public string FileId { get; set; }
public string Error { get; set; }
Expand Down
Loading

0 comments on commit 639f7da

Please sign in to comment.