Skip to content

Commit

Permalink
fix filenames using single quotes (#3)
Browse files Browse the repository at this point in the history
* refactor

* support filenames that use single quotes
  • Loading branch information
jasongdove authored Jan 24, 2024
1 parent 72cad3f commit 330e9bc
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 226 deletions.
42 changes: 42 additions & 0 deletions BlackDetect.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace MkChap;

public class BlackDetect : FFprobeBase
{
public static async Task<string> Detect(string? inputFile, double minBlackSeconds, double ratioBlackPixels,
double blackPixelThreshold)
{
if (string.IsNullOrWhiteSpace(inputFile))
{
return string.Empty;
}

inputFile = FixFileName(inputFile);
return await GetFFprobeOutput(new List<string>
{
"-f", "lavfi",
"-i",
$"movie={inputFile},blackdetect=d={minBlackSeconds}:pic_th={ratioBlackPixels}:pix_th={blackPixelThreshold}[out0]",
"-show_entries", "frame_tags=lavfi.black_start,lavfi.black_end",
"-of", "default=nw=1",
"-v", "panic"
});
}

private static string FixFileName(string inputFile)
{
// rework filename in a format that works on windows
if (OperatingSystem.IsWindows())
{
// \ is escape, so use / for directory separators
inputFile = inputFile.Replace(@"\", "/");

// colon after drive letter needs to be escaped
inputFile = inputFile.Replace(":/", @"\:/");
}

// escape apostrophes
inputFile = inputFile.Replace("'", @"\\\'");

return inputFile;
}
}
114 changes: 114 additions & 0 deletions ChapterWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using System.Diagnostics;
using System.Text;
using MkChap.Models;

namespace MkChap;

public class ChapterWriter
{
public static async Task WriteToFile(string? inputFile, string outputFile, List<Chapter> chapters)
{
if (string.IsNullOrWhiteSpace(inputFile))
{
return;
}

var ffMetadata = GetFFMetadata(chapters);
var metadataFile = string.Empty;

try
{
metadataFile = await WriteFFMetadata(ffMetadata);
await WriteMetadataToFile(inputFile, outputFile, metadataFile);
}
finally
{
try
{
if (File.Exists(metadataFile))
{
File.Delete(metadataFile);
}
}
catch
{
// do nothing
}
}
}

private static string GetFFMetadata(List<Chapter> chapters)
{
var sb = new StringBuilder();

sb.Append(";FFMETADATA1\n");
sb.Append('\n');

for (var i = 0; i < chapters.Count; i++)
{
sb.Append(chapters[i].GetMetadata(i + 1));
}

return sb.ToString();
}

private static async Task<string> WriteFFMetadata(string ffMetadata)
{
var file = Path.GetTempFileName();
await File.WriteAllTextAsync(file, ffMetadata);
return file;
}

private static async Task WriteMetadataToFile(string inputFile, string outputFile, string metadataFile)
{
if (inputFile == outputFile)
{
var extension = Path.GetExtension(inputFile);
var tempFile = Path.ChangeExtension(Path.GetTempFileName(), extension);
await PerformWrite(inputFile, tempFile, metadataFile);
File.Move(tempFile, outputFile, true);
}
else
{
await PerformWrite(inputFile, outputFile, metadataFile);
}
}

private static async Task PerformWrite(string inputFile, string outputFile, string metadataFile)
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "ffmpeg",
UseShellExecute = false,
RedirectStandardError = false,
RedirectStandardOutput = false
}
};

var arguments = new List<string>
{
"-hide_banner",
"-v", "error",
"-i", inputFile,
"-i", metadataFile,
"-map_metadata", "1",
"-map_chapters", "1",
"-codec", "copy",
"-y", outputFile
};

foreach (var arg in arguments)
{
process.StartInfo.ArgumentList.Add(arg);
}

process.Start();

await process.WaitForExitAsync();

// ReSharper disable once MethodHasAsyncOverload
process.WaitForExit();
}
}
30 changes: 30 additions & 0 deletions Duration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Globalization;
using LanguageExt;

namespace MkChap;

public class Duration : FFprobeBase
{
public static async Task<Option<TimeSpan>> GetDuration(string? inputFile)
{
if (string.IsNullOrWhiteSpace(inputFile))
{
return Option<TimeSpan>.None;
}

var output = await GetFFprobeOutput(new List<string>
{
"-v", "panic",
"-show_entries", "format=duration",
"-of", "default=nw=1:nokey=1",
inputFile
});

if (double.TryParse(output, NumberStyles.Number, NumberFormatInfo.InvariantInfo, out var value))
{
return TimeSpan.FromSeconds(value);
}

return Option<TimeSpan>.None;
}
}
18 changes: 18 additions & 0 deletions FFprobeBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using CliWrap;
using CliWrap.Buffered;

namespace MkChap;

public abstract class FFprobeBase
{
protected static async Task<string> GetFFprobeOutput(IEnumerable<string> arguments)
{
var result = await Cli.Wrap("ffprobe")
.WithArguments(arguments)
.WithValidation(CommandResultValidation.None)
.WithStandardErrorPipe(PipeTarget.ToStream(Stream.Null))
.ExecuteBufferedAsync();

return result.StandardOutput;
}
}
1 change: 1 addition & 0 deletions MkChap.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CliWrap" Version="3.6.6" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="LanguageExt.Core" Version="4.4.7" />
</ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions Models/AnalysisResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace MkChap.Models;

public record AnalysisResult(List<BlackSection> BlackSections, List<Chapter> Chapters);
7 changes: 7 additions & 0 deletions Models/BlackSection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MkChap.Models;

public record BlackSection(TimeSpan Start, TimeSpan Finish, State State)
{
public TimeSpan Duration => Finish - Start;
public TimeSpan Midpoint() => Start + (Finish - Start) / 2.0;
};
21 changes: 21 additions & 0 deletions Models/Chapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Globalization;
using System.Text;

namespace MkChap.Models;

public record Chapter(TimeSpan Start, TimeSpan Finish)
{
public string GetMetadata(int num)
{
var sb = new StringBuilder();

sb.Append("[CHAPTER]\n");
sb.Append("TIMEBASE=1/1000\n");
sb.Append($"START={Start.TotalMilliseconds.ToString(NumberFormatInfo.InvariantInfo)}\n");
sb.Append($"END={Finish.TotalMilliseconds.ToString(NumberFormatInfo.InvariantInfo)}\n");
sb.Append($"title=Chapter {num}\n");
sb.Append('\n');

return sb.ToString();
}
}
8 changes: 8 additions & 0 deletions Models/State.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace MkChap.Models;

public enum State
{
TooShort,
OutsideOfWindows,
Ok
}
6 changes: 6 additions & 0 deletions Models/Window.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace MkChap.Models;

public record Window(TimeSpan Start, TimeSpan Finish)
{
public bool Contains(TimeSpan time) => time >= Start && time <= Finish;
}
Loading

0 comments on commit 330e9bc

Please sign in to comment.