Skip to content

Commit

Permalink
Fix issues with filename templates (#898)
Browse files Browse the repository at this point in the history
* Fix Fix custom parameters not working when more than one custom parameter is used

* Change date parameter because it sucks

* Minor refactor

* Add basic unit tests
  • Loading branch information
ScrubN authored Nov 28, 2023
1 parent b91f48f commit 0e89b0b
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 20 deletions.
40 changes: 20 additions & 20 deletions TwitchDownloaderCore/Tools/FilenameService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,6 @@ namespace TwitchDownloaderCore.Tools
{
public static class FilenameService
{
private static string[] GetTemplateSubfolders(ref string fullPath)
{
var returnString = fullPath.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
fullPath = returnString[^1];
Array.Resize(ref returnString, returnString.Length - 1);

for (var i = 0; i < returnString.Length; i++)
{
returnString[i] = RemoveInvalidFilenameChars(returnString[i]);
}

return returnString;
}

public static string GetFilename(string template, string title, string id, DateTime date, string channel, TimeSpan cropStart, TimeSpan cropEnd, string viewCount, string game)
{
var videoLength = cropEnd - cropStart;
Expand All @@ -30,8 +16,8 @@ public static string GetFilename(string template, string title, string id, DateT
.Replace("{title}", RemoveInvalidFilenameChars(title))
.Replace("{id}", id)
.Replace("{channel}", RemoveInvalidFilenameChars(channel))
.Replace("{date}", date.ToString("Mdyy"))
.Replace("{random_string}", Path.GetRandomFileName().Replace(".", ""))
.Replace("{date}", date.ToString("M-d-yy"))
.Replace("{random_string}", Path.GetRandomFileName().Remove(8)) // Remove the period
.Replace("{crop_start}", TimeSpanHFormat.ReusableInstance.Format(@"HH\-mm\-ss", cropStart))
.Replace("{crop_end}", TimeSpanHFormat.ReusableInstance.Format(@"HH\-mm\-ss", cropEnd))
.Replace("{length}", TimeSpanHFormat.ReusableInstance.Format(@"HH\-mm\-ss", videoLength))
Expand All @@ -40,25 +26,25 @@ public static string GetFilename(string template, string title, string id, DateT

if (template.Contains("{date_custom="))
{
var dateRegex = new Regex("{date_custom=\"(.*)\"}");
var dateRegex = new Regex("{date_custom=\"(.*?)\"}");
ReplaceCustomWithFormattable(stringBuilder, dateRegex, date);
}

if (template.Contains("{crop_start_custom="))
{
var cropStartRegex = new Regex("{crop_start_custom=\"(.*)\"}");
var cropStartRegex = new Regex("{crop_start_custom=\"(.*?)\"}");
ReplaceCustomWithFormattable(stringBuilder, cropStartRegex, cropStart);
}

if (template.Contains("{crop_end_custom="))
{
var cropEndRegex = new Regex("{crop_end_custom=\"(.*)\"}");
var cropEndRegex = new Regex("{crop_end_custom=\"(.*?)\"}");
ReplaceCustomWithFormattable(stringBuilder, cropEndRegex, cropEnd);
}

if (template.Contains("{length_custom="))
{
var lengthRegex = new Regex("{length_custom=\"(.*)\"}");
var lengthRegex = new Regex("{length_custom=\"(.*?)\"}");
ReplaceCustomWithFormattable(stringBuilder, lengthRegex, videoLength);
}

Expand All @@ -83,6 +69,20 @@ private static void ReplaceCustomWithFormattable(StringBuilder sb, Regex regex,
} while (true);
}

private static string[] GetTemplateSubfolders(ref string fullPath)
{
var returnString = fullPath.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries);
fullPath = returnString[^1];
Array.Resize(ref returnString, returnString.Length - 1);

for (var i = 0; i < returnString.Length; i++)
{
returnString[i] = RemoveInvalidFilenameChars(returnString[i]);
}

return returnString;
}

private static readonly char[] FilenameInvalidChars = Path.GetInvalidFileNameChars();

private static string RemoveInvalidFilenameChars(string filename) => filename.ReplaceAny(FilenameInvalidChars, '_');
Expand Down
155 changes: 155 additions & 0 deletions TwitchDownloaderTests/FilenameServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using TwitchDownloaderCore.Extensions;
using TwitchDownloaderCore.Tools;

namespace TwitchDownloaderTests
{
public class FilenameServiceTests
{
private static (string title, string id, DateTime date, string channel, TimeSpan cropStart, TimeSpan cropEnd, string viewCount, string game) GetExampleInfo() =>
("A Title", "abc123", new DateTime(1984, 11, 1, 9, 43, 21), "streamer8", new TimeSpan(0, 1, 2, 3, 4), new TimeSpan(0, 5, 6, 7, 8), "123456789", "A Game");

[Theory]
[InlineData("{title}", "A Title")]
[InlineData("{id}", "abc123")]
[InlineData("{channel}", "streamer8")]
[InlineData("{date}", "11-1-84")]
[InlineData("{crop_start}", "01-02-03")]
[InlineData("{crop_end}", "05-06-07")]
[InlineData("{length}", "04-04-04")]
[InlineData("{views}", "123456789")]
[InlineData("{game}", "A Game")]
[InlineData("{date_custom=\"s\"}", "1984-11-01T09_43_21")]
[InlineData("{crop_start_custom=\"hh\\-mm\\-ss\"}", "01-02-03")]
[InlineData("{crop_end_custom=\"hh\\-mm\\-ss\"}", "05-06-07")]
[InlineData("{length_custom=\"hh\\-mm\\-ss\"}", "04-04-04")]
public void CorrectlyGeneratesIndividualTemplates(string template, string expected)
{
var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo();

var result = FilenameService.GetFilename(template, title, id, date, channel, cropStart, cropEnd, viewCount, game);

Assert.Equal(expected, result);
}

[Theory]
[InlineData("[{date_custom=\"M-dd-yy\"}] {channel} - {title}", "[11-01-84] streamer8 - A Title")]
[InlineData("[{channel}] [{date_custom=\"M-dd-yy\"}] [{game}] {title} ({id}) - {views} views", "[streamer8] [11-01-84] [A Game] A Title (abc123) - 123456789 views")]
[InlineData("{title} by {channel} playing {game} on {date_custom=\"M dd, yyyy\"} for {length_custom=\"h'h 'm'm 's's'\"} with {views} views", "A Title by streamer8 playing A Game on 11 01, 1984 for 4h 4m 4s with 123456789 views")]
public void CorrectlyGeneratesLargeTemplates(string template, string expected)
{
var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo();

var result = FilenameService.GetFilename(template, title, id, date, channel, cropStart, cropEnd, viewCount, game);

Assert.Equal(expected, result);
}

[Fact]
public void CorrectlyInterpretsMultipleCustomParameters()
{
const string TEMPLATE = "{date_custom=\"yyyy\"} {date_custom=\"MM\"} {date_custom=\"dd\"} {crop_start_custom=\"hh\\-mm\\-ss\"} {crop_end_custom=\"hh\\-mm\\-ss\"} {length_custom=\"hh\\-mm\\-ss\"}";
const string EXPECTED = "1984 11 01 01-02-03 05-06-07 04-04-04";
var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo();

var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, cropStart, cropEnd, viewCount, game);

Assert.Equal(EXPECTED, result);
}

[Fact]
public void CorrectlyGeneratesSubFolders_WithForwardSlash()
{
const string TEMPLATE = "{channel}/{date_custom=\"yyyy\"}/{date_custom=\"MM\"}/{date_custom=\"dd\"}/{title}.mp4";
var expected = Path.Combine("streamer8", "1984", "11", "01", "A Title.mp4");
var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo();

var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, cropStart, cropEnd, viewCount, game);

Assert.Equal(expected, result);
}

[Fact]
public void CorrectlyGeneratesSubFolders_WithBackSlash()
{
const string TEMPLATE = "{channel}\\{date_custom=\"yyyy\"}\\{date_custom=\"MM\"}\\{date_custom=\"dd\"}\\{title}";
var expected = Path.Combine("streamer8", "1984", "11", "01", "A Title");
var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo();

var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, cropStart, cropEnd, viewCount, game);

Assert.Equal(expected, result);
}

[Theory]
[InlineData("{title}")]
[InlineData("{id}")]
[InlineData("{channel}")]
[InlineData("{views}")]
[InlineData("{game}")]
public void CorrectlyReplacesInvalidCharactersForNonCustomTemplates(string template)
{
const char EXPECTED = '_';
var invalidChars = new string(Path.GetInvalidFileNameChars());

var result = FilenameService.GetFilename(template, invalidChars, invalidChars, default, invalidChars, default, default, invalidChars, invalidChars);

Assert.All(result, c => Assert.Equal(EXPECTED, c));
}

[Theory]
[InlineData("{date_custom=\"'")]
[InlineData("{crop_start_custom=\"'")]
[InlineData("{crop_end_custom=\"'")]
[InlineData("{length_custom=\"'")]
public void CorrectlyReplacesInvalidCharactersForCustomTemplates(string templateStart)
{
const char EXPECTED = '_';
var invalidChars = new string(Path.GetInvalidFileNameChars());
var template = string.Concat(
templateStart,
invalidChars.ReplaceAny("\r\n", EXPECTED), // newline chars are not supported by the custom parameters. This will not change.
"'\"}");

var result = FilenameService.GetFilename(template, invalidChars, invalidChars, default, invalidChars, default, default, invalidChars, invalidChars);

Assert.All(result, c => Assert.Equal(EXPECTED, c));
}

[Fact]
public void CorrectlyReplacesInvalidCharactersForSubFolders()
{
var invalidChars = new string(Path.GetInvalidPathChars());
var template = invalidChars + "\\{title}";
var expected = Path.Combine(new string('_', invalidChars.Length), "A Title");
var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo();

var result = FilenameService.GetFilename(template, title, id, date, channel, cropStart, cropEnd, viewCount, game);

Assert.Equal(expected, result);
}

[Fact]
public void RandomStringIsRandom()
{
const string TEMPLATE = "{random_string}";
var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo();

var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, cropStart, cropEnd, viewCount, game);
var result2 = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, cropStart, cropEnd, viewCount, game);

Assert.NotEqual(result, result2);
}

[Fact]
public void DoesNotInterpretBogusTemplateParameter()
{
const string TEMPLATE = "{foobar}";
const string EXPECTED = "{foobar}";
var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo();

var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, cropStart, cropEnd, viewCount, game);

Assert.Equal(EXPECTED, result);
}
}
}

0 comments on commit 0e89b0b

Please sign in to comment.