Skip to content

Commit

Permalink
Add filename parameter for overall video length (#1302)
Browse files Browse the repository at this point in the history
* Rename {length} to {trim_length}, add {length} parameter in FilenameService

* Update tests

* Update translations

* Add parameters to settings window

* Update FilenameService.GetFilename usages
  • Loading branch information
ScrubN authored Feb 11, 2025
1 parent b04ccfa commit 2a55439
Show file tree
Hide file tree
Showing 21 changed files with 170 additions and 69 deletions.
54 changes: 28 additions & 26 deletions TwitchDownloaderCore.Tests/ServiceTests/FilenameServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ namespace TwitchDownloaderCore.Tests.ServiceTests
{
public class FilenameServiceTests
{
private static (string title, string id, DateTime date, string channel, string channelId, TimeSpan trimStart, TimeSpan trimEnd, int viewCount, string game, string clipper, string clipperId) GetExampleInfo() =>
("A Title", "abc123", new DateTime(1984, 11, 1, 9, 43, 21), "streamer8", "123456789", new TimeSpan(0, 1, 2, 3, 4), new TimeSpan(0, 5, 6, 7, 8), 123456789, "A Game", "viewer8", "987654321");
private static (string title, string id, DateTime date, string channel, string channelId, TimeSpan trimStart, TimeSpan trimEnd, TimeSpan videoLength, int viewCount, string game, string clipper, string clipperId) GetExampleInfo() =>
("A Title", "abc123", new DateTime(1984, 11, 1, 9, 43, 21), "streamer8", "123456789", new TimeSpan(0, 1, 2, 3, 4), new TimeSpan(0, 5, 6, 7, 8), new TimeSpan(0, 9, 10, 11, 12), 123456789, "A Game", "viewer8", "987654321");

[Theory]
[InlineData("{title}", "A Title")]
Expand All @@ -15,45 +15,47 @@ private static (string title, string id, DateTime date, string channel, string c
[InlineData("{date}", "11-1-84")]
[InlineData("{trim_start}", "01-02-03")]
[InlineData("{trim_end}", "05-06-07")]
[InlineData("{length}", "04-04-04")]
[InlineData("{trim_length}", "04-04-04")]
[InlineData("{length}", "09-10-11")]
[InlineData("{views}", "123456789")]
[InlineData("{game}", "A Game")]
[InlineData("{clipper}", "viewer8")]
[InlineData("{clipper_id}", "987654321")]
[InlineData("{date_custom=\"s\"}", "1984-11-01T09_43_21")]
[InlineData("{trim_start_custom=\"hh\\-mm\\-ss\"}", "01-02-03")]
[InlineData("{trim_end_custom=\"hh\\-mm\\-ss\"}", "05-06-07")]
[InlineData("{length_custom=\"hh\\-mm\\-ss\"}", "04-04-04")]
[InlineData("{trim_length_custom=\"hh\\-mm\\-ss\\-fff\"}", "04-04-04-004")]
[InlineData("{length_custom=\"hh\\-mm\\-ss\\-fff\"}", "09-10-11-012")]
public void CorrectlyGeneratesIndividualTemplates(string template, string expected)
{
var (title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId) = GetExampleInfo();
var (title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId) = GetExampleInfo();

var result = FilenameService.GetFilename(template, title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId);
var result = FilenameService.GetFilename(template, title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId);

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")]
[InlineData("{title} by {channel} playing {game} on {date_custom=\"M dd, yyyy\"} for {length_custom=\"h'h 'm'm 's's'\"} trimmed to {trim_length_custom=\"h'h 'm'm 's's'\"} with {views} views", "A Title by streamer8 playing A Game on 11 01, 1984 for 9h 10m 11s trimmed to 4h 4m 4s with 123456789 views")]
public void CorrectlyGeneratesLargeTemplates(string template, string expected)
{
var (title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId) = GetExampleInfo();
var (title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId) = GetExampleInfo();

var result = FilenameService.GetFilename(template, title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId);
var result = FilenameService.GetFilename(template, title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId);

Assert.Equal(expected, result);
}

[Fact]
public void CorrectlyInterpretsMultipleCustomParameters()
{
const string TEMPLATE = "{date_custom=\"yyyy\"} {date_custom=\"MM\"} {date_custom=\"dd\"} {trim_start_custom=\"hh\\-mm\\-ss\"} {trim_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, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId) = GetExampleInfo();
const string TEMPLATE = "{date_custom=\"yyyy\"} {date_custom=\"MM\"} {date_custom=\"dd\"} {trim_start_custom=\"hh\\-mm\\-ss\"} {trim_end_custom=\"hh\\-mm\\-ss\"} {trim_length_custom=\"hh\\-mm\\-ss\"} {length_custom=\"hh\\-mm\\-ss\"}";
const string EXPECTED = "1984 11 01 01-02-03 05-06-07 04-04-04 09-10-11";
var (title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId) = GetExampleInfo();

var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId);
var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId);

Assert.Equal(EXPECTED, result);
}
Expand All @@ -63,9 +65,9 @@ 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, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId) = GetExampleInfo();
var (title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId) = GetExampleInfo();

var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId);
var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId);

Assert.Equal(expected, result);
}
Expand All @@ -75,9 +77,9 @@ 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, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId) = GetExampleInfo();
var (title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId) = GetExampleInfo();

var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId);
var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId);

Assert.Equal(expected, result);
}
Expand All @@ -95,7 +97,7 @@ public void CorrectlyReplacesInvalidCharactersForNonCustomTemplates(string templ
const string INVALID_CHARS = "\"*:<>?|/\\";
const string EXPECTED = ""*:<>?|/\";

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

Assert.Equal(EXPECTED, result);
}
Expand All @@ -111,7 +113,7 @@ public void CorrectlyReplacesInvalidCharactersForCustomTemplates(string template
const string INVALID_CHARS = "\"*:<>?|/\\\\";
var template = templateStart + INVALID_CHARS + "'\"}";

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

Assert.Equal(EXPECTED, result);
}
Expand All @@ -123,9 +125,9 @@ public void CorrectlyReplacesInvalidCharactersForSubFolders()
const string FULL_WIDTH_CHARS = ""*:<>?|";
const string TEMPLATE = INVALID_CHARS + "\\{title}";
var expected = Path.Combine(FULL_WIDTH_CHARS, "A Title");
var (title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId) = GetExampleInfo();
var (title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId) = GetExampleInfo();

var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId);
var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId);

Assert.Equal(expected, result);
}
Expand All @@ -134,10 +136,10 @@ public void CorrectlyReplacesInvalidCharactersForSubFolders()
public void RandomStringIsRandom()
{
const string TEMPLATE = "{random_string}";
var (title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId) = GetExampleInfo();
var (title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId) = GetExampleInfo();

var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId);
var result2 = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId);
var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId);
var result2 = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId);

Assert.NotEqual(result, result2);
}
Expand All @@ -147,9 +149,9 @@ public void DoesNotInterpretBogusTemplateParameter()
{
const string TEMPLATE = "{foobar}";
const string EXPECTED = "{foobar}";
var (title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId) = GetExampleInfo();
var (title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId) = GetExampleInfo();

var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, viewCount, game, clipper, clipperId);
var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, channelId, trimStart, trimEnd, videoLength, viewCount, game, clipper, clipperId);

Assert.Equal(EXPECTED, result);
}
Expand Down
11 changes: 9 additions & 2 deletions TwitchDownloaderCore/Services/FilenameService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ namespace TwitchDownloaderCore.Services
{
public static class FilenameService
{
public static string GetFilename(string template, [AllowNull] string title, [AllowNull] string id, DateTime date, [AllowNull] string channel, [AllowNull] string channelId, TimeSpan trimStart, TimeSpan trimEnd, long viewCount,
public static string GetFilename(string template, [AllowNull] string title, [AllowNull] string id, DateTime date, [AllowNull] string channel, [AllowNull] string channelId, TimeSpan trimStart, TimeSpan trimEnd, TimeSpan videoLength, long viewCount,
[AllowNull] string game, [AllowNull] string clipper = null, [AllowNull] string clipperId = null)
{
var videoLength = trimEnd - trimStart;
var trimLength = trimEnd - trimStart;

var stringBuilder = new StringBuilder(template)
.Replace("{title}", ReplaceInvalidFilenameChars(title))
Expand All @@ -27,6 +27,7 @@ public static string GetFilename(string template, [AllowNull] string title, [All
.Replace("{random_string}", Path.GetRandomFileName().Remove(8)) // Remove the period
.Replace("{trim_start}", TimeSpanHFormat.ReusableInstance.Format(@"HH\-mm\-ss", trimStart))
.Replace("{trim_end}", TimeSpanHFormat.ReusableInstance.Format(@"HH\-mm\-ss", trimEnd))
.Replace("{trim_length}", TimeSpanHFormat.ReusableInstance.Format(@"HH\-mm\-ss", trimLength))
.Replace("{length}", TimeSpanHFormat.ReusableInstance.Format(@"HH\-mm\-ss", videoLength))
.Replace("{views}", viewCount.ToString(CultureInfo.CurrentCulture))
.Replace("{game}", ReplaceInvalidFilenameChars(game));
Expand All @@ -49,6 +50,12 @@ public static string GetFilename(string template, [AllowNull] string title, [All
ReplaceCustomWithFormattable(stringBuilder, trimEndRegex, trimEnd, TimeSpanHFormat.ReusableInstance);
}

if (template.Contains("{trim_length_custom="))
{
var lengthRegex = new Regex("{trim_length_custom=\"(.*?)\"}");
ReplaceCustomWithFormattable(stringBuilder, lengthRegex, trimLength, TimeSpanHFormat.ReusableInstance);
}

if (template.Contains("{length_custom="))
{
var lengthRegex = new Regex("{length_custom=\"(.*?)\"}");
Expand Down
2 changes: 1 addition & 1 deletion TwitchDownloaderWPF/PageChatDownload.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ private async void SplitBtnDownload_Click(object sender, RoutedEventArgs e)
FileName = FilenameService.GetFilename(Settings.Default.TemplateChat, textTitle.Text, downloadId, currentVideoTime, textStreamer.Text, streamerId,
CheckTrimStart.IsChecked == true ? new TimeSpan((int)numStartHour.Value, (int)numStartMinute.Value, (int)numStartSecond.Value) : TimeSpan.Zero,
CheckTrimEnd.IsChecked == true ? new TimeSpan((int)numEndHour.Value, (int)numEndMinute.Value, (int)numEndSecond.Value) : vodLength,
viewCount, game, clipper, clipperId)
vodLength, viewCount, game, clipper, clipperId)
};

if (radioJson.IsChecked == true)
Expand Down
2 changes: 1 addition & 1 deletion TwitchDownloaderWPF/PageChatUpdate.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ private async void SplitBtnUpdate_Click(object sender, RoutedEventArgs e)
ChatJsonInfo.video.id ?? ChatJsonInfo.comments.FirstOrDefault()?.content_id ?? "-1", VideoCreatedAt, textStreamer.Text, StreamerId,
checkStart.IsChecked == true ? new TimeSpan((int)numStartHour.Value, (int)numStartMinute.Value, (int)numStartSecond.Value) : TimeSpan.FromSeconds(double.IsNegative(ChatJsonInfo.video.start) ? 0.0 : ChatJsonInfo.video.start),
checkEnd.IsChecked == true ? new TimeSpan((int)numEndHour.Value, (int)numEndMinute.Value, (int)numEndSecond.Value) : VideoLength,
ViewCount, Game, ClipperName, ClipperId)
VideoLength, ViewCount, Game, ClipperName, ClipperId)
};

if (radioJson.IsChecked == true)
Expand Down
4 changes: 2 additions & 2 deletions TwitchDownloaderWPF/PageClipDownload.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,8 @@ private async void SplitBtnDownload_Click(object sender, RoutedEventArgs e)
SaveFileDialog saveFileDialog = new SaveFileDialog
{
Filter = "MP4 Files | *.mp4",
FileName = FilenameService.GetFilename(Settings.Default.TemplateClip, textTitle.Text, clipId, currentVideoTime, textStreamer.Text, streamerId, TimeSpan.Zero, clipLength, viewCount, game, clipperName,
clipperId) + ".mp4"
FileName = FilenameService.GetFilename(Settings.Default.TemplateClip, textTitle.Text, clipId, currentVideoTime, textStreamer.Text, streamerId, TimeSpan.Zero, clipLength, clipLength, viewCount, game,
clipperName, clipperId) + ".mp4"
};
if (saveFileDialog.ShowDialog() != true)
{
Expand Down
4 changes: 2 additions & 2 deletions TwitchDownloaderWPF/PageVodDownload.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ public VideoDownloadOptions GetOptions(string filename, string folder)
Filename = filename ?? Path.Combine(folder, FilenameService.GetFilename(Settings.Default.TemplateVod, textTitle.Text, currentVideoId.ToString(), currentVideoTime, textStreamer.Text, streamerId,
checkStart.IsChecked == true ? new TimeSpan((int)numStartHour.Value, (int)numStartMinute.Value, (int)numStartSecond.Value) : TimeSpan.Zero,
checkEnd.IsChecked == true ? new TimeSpan((int)numEndHour.Value, (int)numEndMinute.Value, (int)numEndSecond.Value) : vodLength,
viewCount, game) + FilenameService.GuessVodFileExtension(comboQuality.Text)),
vodLength, viewCount, game) + FilenameService.GuessVodFileExtension(comboQuality.Text)),
Oauth = TextOauth.Text,
Quality = GetQualityWithoutSize(comboQuality.Text),
Id = currentVideoId,
Expand Down Expand Up @@ -428,7 +428,7 @@ private async void SplitBtnDownloader_Click(object sender, RoutedEventArgs e)
FileName = FilenameService.GetFilename(Settings.Default.TemplateVod, textTitle.Text, currentVideoId.ToString(), currentVideoTime, textStreamer.Text, streamerId,
checkStart.IsChecked == true ? new TimeSpan((int)numStartHour.Value, (int)numStartMinute.Value, (int)numStartSecond.Value) : TimeSpan.Zero,
checkEnd.IsChecked == true ? new TimeSpan((int)numEndHour.Value, (int)numEndMinute.Value, (int)numEndSecond.Value) : vodLength,
viewCount, game) + FilenameService.GuessVodFileExtension(comboQuality.Text)
vodLength, viewCount, game) + FilenameService.GuessVodFileExtension(comboQuality.Text)
};
if (saveFileDialog.ShowDialog() == false)
{
Expand Down
Loading

0 comments on commit 2a55439

Please sign in to comment.