Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add verify check for too high video resolution #27708

Merged
merged 3 commits into from Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
89 changes: 89 additions & 0 deletions osu.Game.Tests/Editing/Checks/CheckVideoResolutionTest.cs
@@ -0,0 +1,89 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.IO;
using System.Linq;
using Moq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Resources;

namespace osu.Game.Tests.Editing.Checks
{
[TestFixture]
public class CheckVideoResolutionTest
{
private CheckVideoResolution check = null!;

private IBeatmap beatmap = null!;

[SetUp]
public void Setup()
{
check = new CheckVideoResolution();
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
{
BeatmapSet = new BeatmapSetInfo
{
Files =
{
CheckTestHelpers.CreateMockFile("mp4"),
}
}
}
};
}

[Test]
public void TestNoVideo()
{
beatmap.BeatmapInfo.BeatmapSet?.Files.Clear();

var issues = check.Run(getContext(null)).ToList();

Assert.That(issues, Has.Count.EqualTo(0));
}

[Test]
public void TestVideoAcceptableResolution()
{
using (var resourceStream = TestResources.OpenResource("Videos/test-video.mp4"))
{
var issues = check.Run(getContext(resourceStream)).ToList();
Assert.That(issues, Has.Count.EqualTo(0));
}
}

[Test]
public void TestVideoHighResolution()
{
using (var resourceStream = TestResources.OpenResource("Videos/test-video-resolution-high.mp4"))
{
var issues = check.Run(getContext(resourceStream)).ToList();

Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckVideoResolution.IssueTemplateHighResolution);
}
}

private BeatmapVerifierContext getContext(Stream? resourceStream)
{
var storyboard = new Storyboard();
var layer = storyboard.GetLayer("Video");
layer.Add(new StoryboardVideo("abc123.mp4", 0));

var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
mockWorkingBeatmap.As<IWorkingBeatmap>().SetupGet(w => w.Storyboard).Returns(storyboard);

return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
}
}
}
Binary file not shown.
1 change: 1 addition & 0 deletions osu.Game/Rulesets/Edit/BeatmapVerifier.cs
Expand Up @@ -18,6 +18,7 @@ public class BeatmapVerifier : IBeatmapVerifier
// Resources
new CheckBackgroundPresence(),
new CheckBackgroundQuality(),
new CheckVideoResolution(),

// Audio
new CheckAudioPresence(),
Expand Down
116 changes: 116 additions & 0 deletions osu.Game/Rulesets/Edit/Checks/CheckVideoResolution.cs
@@ -0,0 +1,116 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.IO;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.IO.FileAbstraction;
using osu.Game.Rulesets.Edit.Checks.Components;
using osu.Game.Storyboards;
using TagLib;
using File = TagLib.File;

namespace osu.Game.Rulesets.Edit.Checks
{
public class CheckVideoResolution : ICheck
{
private const int max_video_width = 1280;

private const int max_video_height = 720;

public CheckMetadata Metadata => new CheckMetadata(CheckCategory.Resources, "Too high video resolution.");

public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
{
new IssueTemplateHighResolution(this),
new IssueTemplateFileError(this),
};

public IEnumerable<Issue> Run(BeatmapVerifierContext context)
{
var beatmapSet = context.Beatmap.BeatmapInfo.BeatmapSet;
var videoPaths = getVideoPaths(context.WorkingBeatmap.Storyboard);

foreach (string filename in videoPaths)
{
string? storagePath = beatmapSet?.GetPathForFile(filename);

// Don't report any issues for missing video here since another check is already doing that (CheckAudioInVideo)
if (storagePath == null) continue;

Issue issue;

try
{
using (Stream data = context.WorkingBeatmap.GetStream(storagePath))
using (File tagFile = File.Create(new StreamFileAbstraction(filename, data)))
{
int height = tagFile.Properties.VideoHeight;
int width = tagFile.Properties.VideoWidth;

if (height <= max_video_height || width <= max_video_width)
continue;

issue = new IssueTemplateHighResolution(this).Create(filename, width, height);
}
}
catch (CorruptFileException)
{
issue = new IssueTemplateFileError(this).Create(filename, "Corrupt file");
}
catch (UnsupportedFormatException)
{
issue = new IssueTemplateFileError(this).Create(filename, "Unsupported format");
}
catch (Exception ex)
{
issue = new IssueTemplateFileError(this).Create(filename, "Internal failure - see logs for more info");
Logger.Log($"Failed when running {nameof(CheckVideoResolution)}: {ex}");
}

yield return issue;
}
}

private List<string> getVideoPaths(Storyboard storyboard)
{
var videoPaths = new List<string>();

foreach (var layer in storyboard.Layers)
{
foreach (var element in layer.Elements)
{
if (element is not StoryboardVideo video)
continue;

if (!videoPaths.Contains(video.Path))
videoPaths.Add(video.Path);
}
}

return videoPaths;
}

public class IssueTemplateHighResolution : IssueTemplate
{
public IssueTemplateHighResolution(ICheck check)
: base(check, IssueType.Problem, "\"{0}\" resolution exceeds 1280x720 ({1}x{2})")
{
}

public Issue Create(string filename, int width, int height) => new Issue(this, filename, width, height);
}

public class IssueTemplateFileError : IssueTemplate
{
public IssueTemplateFileError(ICheck check)
: base(check, IssueType.Error, "Could not check resolution for \"{0}\" ({1}).")
{
}

public Issue Create(string filename, string errorReason) => new Issue(this, filename, errorReason);
}
}
}