1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 01:52:55 +08:00

Merge pull request #12492 from Naxesss/bg-audio-quality-checks

Add background and audio checks
This commit is contained in:
Dean Herbert 2021-04-21 00:10:32 +09:00 committed by GitHub
commit 26430e3cb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 628 additions and 133 deletions

View File

@ -10,6 +10,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Checks;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
@ -30,25 +31,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
[Test]
public void TestCircleInCenter()
{
var beatmap = new Beatmap<HitObject>
assertOk(new Beatmap<HitObject>
{
HitObjects = new List<HitObject>
{
new HitCircle
{
StartTime = 3000,
Position = playfield_centre // Playfield is 640 x 480.
Position = playfield_centre
}
}
};
Assert.That(check.Run(beatmap), Is.Empty);
});
}
[Test]
public void TestCircleNearEdge()
{
var beatmap = new Beatmap<HitObject>
assertOk(new Beatmap<HitObject>
{
HitObjects = new List<HitObject>
{
@ -58,15 +57,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
Position = new Vector2(5, 5)
}
}
};
Assert.That(check.Run(beatmap), Is.Empty);
});
}
[Test]
public void TestCircleNearEdgeStackedOffscreen()
{
var beatmap = new Beatmap<HitObject>
assertOffscreenCircle(new Beatmap<HitObject>
{
HitObjects = new List<HitObject>
{
@ -77,15 +74,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
StackHeight = 5
}
}
};
assertOffscreenCircle(beatmap);
});
}
[Test]
public void TestCircleOffscreen()
{
var beatmap = new Beatmap<HitObject>
assertOffscreenCircle(new Beatmap<HitObject>
{
HitObjects = new List<HitObject>
{
@ -95,15 +90,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
Position = new Vector2(0, 0)
}
}
};
assertOffscreenCircle(beatmap);
});
}
[Test]
public void TestSliderInCenter()
{
var beatmap = new Beatmap<HitObject>
assertOk(new Beatmap<HitObject>
{
HitObjects = new List<HitObject>
{
@ -118,15 +111,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
}),
}
}
};
Assert.That(check.Run(beatmap), Is.Empty);
});
}
[Test]
public void TestSliderNearEdge()
{
var beatmap = new Beatmap<HitObject>
assertOk(new Beatmap<HitObject>
{
HitObjects = new List<HitObject>
{
@ -141,15 +132,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
}),
}
}
};
Assert.That(check.Run(beatmap), Is.Empty);
});
}
[Test]
public void TestSliderNearEdgeStackedOffscreen()
{
var beatmap = new Beatmap<HitObject>
assertOffscreenSlider(new Beatmap<HitObject>
{
HitObjects = new List<HitObject>
{
@ -165,15 +154,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
StackHeight = 5
}
}
};
assertOffscreenSlider(beatmap);
});
}
[Test]
public void TestSliderOffscreenStart()
{
var beatmap = new Beatmap<HitObject>
assertOffscreenSlider(new Beatmap<HitObject>
{
HitObjects = new List<HitObject>
{
@ -188,15 +175,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
}),
}
}
};
assertOffscreenSlider(beatmap);
});
}
[Test]
public void TestSliderOffscreenEnd()
{
var beatmap = new Beatmap<HitObject>
assertOffscreenSlider(new Beatmap<HitObject>
{
HitObjects = new List<HitObject>
{
@ -211,15 +196,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
}),
}
}
};
assertOffscreenSlider(beatmap);
});
}
[Test]
public void TestSliderOffscreenPath()
{
var beatmap = new Beatmap<HitObject>
assertOffscreenSlider(new Beatmap<HitObject>
{
HitObjects = new List<HitObject>
{
@ -236,14 +219,17 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
}),
}
}
};
});
}
assertOffscreenSlider(beatmap);
private void assertOk(IBeatmap beatmap)
{
Assert.That(check.Run(beatmap, new TestWorkingBeatmap(beatmap)), Is.Empty);
}
private void assertOffscreenCircle(IBeatmap beatmap)
{
var issues = check.Run(beatmap).ToList();
var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle);
@ -251,7 +237,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
private void assertOffscreenSlider(IBeatmap beatmap)
{
var issues = check.Run(beatmap).ToList();
var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenSlider);

View File

@ -31,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks
new IssueTemplateOffscreenSlider(this)
};
public IEnumerable<Issue> Run(IBeatmap beatmap)
public IEnumerable<Issue> Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap)
{
foreach (var hitobject in beatmap.HitObjects)
foreach (var hitobject in playableBeatmap.HitObjects)
{
switch (hitobject)
{

View File

@ -17,6 +17,9 @@ namespace osu.Game.Rulesets.Osu.Edit
new CheckOffscreenObjects()
};
public IEnumerable<Issue> Run(IBeatmap beatmap) => checks.SelectMany(check => check.Run(beatmap));
public IEnumerable<Issue> Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap)
{
return checks.SelectMany(check => check.Run(playableBeatmap, workingBeatmap));
}
}
}

View File

@ -168,6 +168,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
protected override Texture GetBackground() => throw new NotImplementedException();
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,114 @@
// 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.Linq;
using Moq;
using NUnit.Framework;
using osu.Framework.Audio.Track;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Tests.Editing.Checks
{
[TestFixture]
public class CheckAudioQualityTest
{
private CheckAudioQuality check;
private IBeatmap beatmap;
[SetUp]
public void Setup()
{
check = new CheckAudioQuality();
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata { AudioFile = "abc123.jpg" }
}
};
}
[Test]
public void TestMissing()
{
// While this is a problem, it is out of scope for this check and is caught by a different one.
beatmap.Metadata.AudioFile = null;
var mock = new Mock<IWorkingBeatmap>();
mock.SetupGet(w => w.Beatmap).Returns(beatmap);
mock.SetupGet(w => w.Track).Returns((Track)null);
Assert.That(check.Run(beatmap, mock.Object), Is.Empty);
}
[Test]
public void TestAcceptable()
{
var mock = getMockWorkingBeatmap(192);
Assert.That(check.Run(beatmap, mock.Object), Is.Empty);
}
[Test]
public void TestNullBitrate()
{
var mock = getMockWorkingBeatmap(null);
var issues = check.Run(beatmap, mock.Object).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate);
}
[Test]
public void TestZeroBitrate()
{
var mock = getMockWorkingBeatmap(0);
var issues = check.Run(beatmap, mock.Object).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateNoBitrate);
}
[Test]
public void TestTooHighBitrate()
{
var mock = getMockWorkingBeatmap(320);
var issues = check.Run(beatmap, mock.Object).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooHighBitrate);
}
[Test]
public void TestTooLowBitrate()
{
var mock = getMockWorkingBeatmap(64);
var issues = check.Run(beatmap, mock.Object).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooLowBitrate);
}
/// <summary>
/// Returns the mock of the working beatmap with the given audio properties.
/// </summary>
/// <param name="audioBitrate">The bitrate of the audio file the beatmap uses.</param>
private Mock<IWorkingBeatmap> getMockWorkingBeatmap(int? audioBitrate)
{
var mockTrack = new Mock<Track>();
mockTrack.SetupGet(t => t.Bitrate).Returns(audioBitrate);
var mockWorkingBeatmap = new Mock<IWorkingBeatmap>();
mockWorkingBeatmap.SetupGet(w => w.Beatmap).Returns(beatmap);
mockWorkingBeatmap.SetupGet(w => w.Track).Returns(mockTrack.Object);
return mockWorkingBeatmap;
}
}
}

View File

@ -0,0 +1,130 @@
// 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.Collections.Generic;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using Moq;
using NUnit.Framework;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
using FileInfo = osu.Game.IO.FileInfo;
namespace osu.Game.Tests.Editing.Checks
{
[TestFixture]
public class CheckBackgroundQualityTest
{
private CheckBackgroundQuality check;
private IBeatmap beatmap;
[SetUp]
public void Setup()
{
check = new CheckBackgroundQuality();
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" },
BeatmapSet = new BeatmapSetInfo
{
Files = new List<BeatmapSetFileInfo>(new[]
{
new BeatmapSetFileInfo
{
Filename = "abc123.jpg",
FileInfo = new FileInfo
{
Hash = "abcdef"
}
}
})
}
}
};
}
[Test]
public void TestMissing()
{
// While this is a problem, it is out of scope for this check and is caught by a different one.
beatmap.Metadata.BackgroundFile = null;
var mock = getMockWorkingBeatmap(null, System.Array.Empty<byte>());
Assert.That(check.Run(beatmap, mock.Object), Is.Empty);
}
[Test]
public void TestAcceptable()
{
var mock = getMockWorkingBeatmap(new Texture(1920, 1080));
Assert.That(check.Run(beatmap, mock.Object), Is.Empty);
}
[Test]
public void TestTooHighResolution()
{
var mock = getMockWorkingBeatmap(new Texture(3840, 2160));
var issues = check.Run(beatmap, mock.Object).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooHighResolution);
}
[Test]
public void TestLowResolution()
{
var mock = getMockWorkingBeatmap(new Texture(640, 480));
var issues = check.Run(beatmap, mock.Object).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateLowResolution);
}
[Test]
public void TestTooLowResolution()
{
var mock = getMockWorkingBeatmap(new Texture(100, 100));
var issues = check.Run(beatmap, mock.Object).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooLowResolution);
}
[Test]
public void TestTooUncompressed()
{
var mock = getMockWorkingBeatmap(new Texture(1920, 1080), new byte[1024 * 1024 * 3]);
var issues = check.Run(beatmap, mock.Object).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBackgroundQuality.IssueTemplateTooUncompressed);
}
/// <summary>
/// Returns the mock of the working beatmap with the given background and filesize.
/// </summary>
/// <param name="background">The texture of the background.</param>
/// <param name="fileBytes">The bytes that represent the background file.</param>
private Mock<IWorkingBeatmap> getMockWorkingBeatmap(Texture background, [CanBeNull] byte[] fileBytes = null)
{
var stream = new MemoryStream(fileBytes ?? new byte[1024 * 1024]);
var mock = new Mock<IWorkingBeatmap>();
mock.SetupGet(w => w.Beatmap).Returns(beatmap);
mock.SetupGet(w => w.Background).Returns(background);
mock.Setup(w => w.GetStream(It.IsAny<string>())).Returns(stream);
return mock;
}
}
}

View File

@ -5,21 +5,23 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.IO;
using osu.Game.Rulesets.Edit.Checks;
using osu.Game.Rulesets.Objects;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Editing.Checks
{
[TestFixture]
public class CheckBackgroundTest
public class CheckFilePresenceTest
{
private CheckBackground check;
private CheckBackgroundPresence check;
private IBeatmap beatmap;
[SetUp]
public void Setup()
{
check = new CheckBackground();
check = new CheckBackgroundPresence();
beatmap = new Beatmap<HitObject>
{
BeatmapInfo = new BeatmapInfo
@ -29,7 +31,11 @@ namespace osu.Game.Tests.Editing.Checks
{
Files = new List<BeatmapSetFileInfo>(new[]
{
new BeatmapSetFileInfo { Filename = "abc123.jpg" }
new BeatmapSetFileInfo
{
Filename = "abc123.jpg",
FileInfo = new FileInfo { Hash = "abcdef" }
}
})
}
}
@ -39,7 +45,7 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestBackgroundSetAndInFiles()
{
Assert.That(check.Run(beatmap), Is.Empty);
Assert.That(check.Run(beatmap, new TestWorkingBeatmap(beatmap)), Is.Empty);
}
[Test]
@ -47,10 +53,10 @@ namespace osu.Game.Tests.Editing.Checks
{
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
var issues = check.Run(beatmap).ToList();
var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBackground.IssueTemplateDoesNotExist);
Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateDoesNotExist);
}
[Test]
@ -58,10 +64,10 @@ namespace osu.Game.Tests.Editing.Checks
{
beatmap.Metadata.BackgroundFile = null;
var issues = check.Run(beatmap).ToList();
var issues = check.Run(beatmap, new TestWorkingBeatmap(beatmap)).ToList();
Assert.That(issues, Has.Count.EqualTo(1));
Assert.That(issues.Single().Template is CheckBackground.IssueTemplateNoneSet);
Assert.That(issues.Single().Template is CheckFilePresence.IssueTemplateNoneSet);
}
}
}

View File

@ -52,6 +52,8 @@ namespace osu.Game.Tests
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
public override Stream GetStream(string storagePath) => null;
protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile);
private string firstAudioFile

View File

@ -526,6 +526,7 @@ namespace osu.Game.Beatmaps
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
protected override Track GetBeatmapTrack() => null;
public override Stream GetStream(string storagePath) => null;
}
}

View File

@ -3,7 +3,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.IO;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
@ -36,7 +36,7 @@ namespace osu.Game.Beatmaps
try
{
using (var stream = new LineBufferedReader(resources.Files.GetStream(getPathForFile(BeatmapInfo.Path))))
using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path))))
return Decoder.GetDecoder<Beatmap>(stream).Decode(stream);
}
catch (Exception e)
@ -46,8 +46,6 @@ namespace osu.Game.Beatmaps
}
}
private string getPathForFile(string filename) => BeatmapSetInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
protected override bool BackgroundStillValid(Texture b) => false; // bypass lazy logic. we want to return a new background each time for refcounting purposes.
protected override Texture GetBackground()
@ -57,7 +55,7 @@ namespace osu.Game.Beatmaps
try
{
return resources.LargeTextureStore.Get(getPathForFile(Metadata.BackgroundFile));
return resources.LargeTextureStore.Get(BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile));
}
catch (Exception e)
{
@ -73,7 +71,7 @@ namespace osu.Game.Beatmaps
try
{
return resources.Tracks.Get(getPathForFile(Metadata.AudioFile));
return resources.Tracks.Get(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile));
}
catch (Exception e)
{
@ -89,7 +87,7 @@ namespace osu.Game.Beatmaps
try
{
var trackData = resources.Files.GetStream(getPathForFile(Metadata.AudioFile));
var trackData = GetStream(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile));
return trackData == null ? null : new Waveform(trackData);
}
catch (Exception e)
@ -105,7 +103,7 @@ namespace osu.Game.Beatmaps
try
{
using (var stream = new LineBufferedReader(resources.Files.GetStream(getPathForFile(BeatmapInfo.Path))))
using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path))))
{
var decoder = Decoder.GetDecoder<Storyboard>(stream);
@ -114,7 +112,7 @@ namespace osu.Game.Beatmaps
storyboard = decoder.Decode(stream);
else
{
using (var secondaryStream = new LineBufferedReader(resources.Files.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile))))
using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapSetInfo.StoryboardFile))))
storyboard = decoder.Decode(stream, secondaryStream);
}
}
@ -142,6 +140,8 @@ namespace osu.Game.Beatmaps
return null;
}
}
public override Stream GetStream(string storagePath) => resources.Files.GetStream(storagePath);
}
}
}

View File

@ -59,6 +59,13 @@ namespace osu.Game.Beatmaps
public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename;
/// <summary>
/// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null.
/// The path returned is relative to the user file storage.
/// </summary>
/// <param name="filename">The name of the file to get the storage path of.</param>
public string GetPathForFile(string filename) => Files?.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
public List<BeatmapSetFileInfo> Files { get; set; }
public override string ToString() => Metadata?.ToString() ?? base.ToString();

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using JetBrains.Annotations;
using osu.Framework.Audio;
@ -48,6 +49,8 @@ namespace osu.Game.Beatmaps
protected override Track GetBeatmapTrack() => GetVirtualTrack();
public override Stream GetStream(string storagePath) => null;
private class DummyRulesetInfo : RulesetInfo
{
public override Ruleset CreateInstance() => new DummyRuleset();

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets;
@ -41,6 +42,11 @@ namespace osu.Game.Beatmaps
/// </summary>
ISkin Skin { get; }
/// <summary>
/// Retrieves the <see cref="Track"/> which this <see cref="WorkingBeatmap"/> has loaded.
/// </summary>
Track Track { get; }
/// <summary>
/// Constructs a playable <see cref="IBeatmap"/> from <see cref="Beatmap"/> using the applicable converters for a specific <see cref="RulesetInfo"/>.
/// <para>
@ -67,5 +73,11 @@ namespace osu.Game.Beatmaps
/// </remarks>
/// <returns>A fresh track instance, which will also be available via <see cref="Track"/>.</returns>
Track LoadTrack();
/// <summary>
/// Returns the stream of the file from the given storage path.
/// </summary>
/// <param name="storagePath">The storage path to the file.</param>
Stream GetStream(string storagePath);
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -326,6 +327,8 @@ namespace osu.Game.Beatmaps
protected virtual ISkin GetSkin() => new DefaultSkin();
private readonly RecyclableLazy<ISkin> skin;
public abstract Stream GetStream(string storagePath);
public class RecyclableLazy<T>
{
private Lazy<T> lazy;

View File

@ -16,9 +16,18 @@ namespace osu.Game.Rulesets.Edit
{
private readonly List<ICheck> checks = new List<ICheck>
{
new CheckBackground(),
// Resources
new CheckBackgroundPresence(),
new CheckBackgroundQuality(),
// Audio
new CheckAudioPresence(),
new CheckAudioQuality()
};
public IEnumerable<Issue> Run(IBeatmap beatmap) => checks.SelectMany(check => check.Run(beatmap));
public IEnumerable<Issue> Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap)
{
return checks.SelectMany(check => check.Run(playableBeatmap, workingBeatmap));
}
}
}

View File

@ -0,0 +1,15 @@
// 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 osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Checks.Components;
namespace osu.Game.Rulesets.Edit.Checks
{
public class CheckAudioPresence : CheckFilePresence
{
protected override CheckCategory Category => CheckCategory.Audio;
protected override string TypeOfFile => "audio";
protected override string GetFilename(IBeatmap playableBeatmap) => playableBeatmap.Metadata?.AudioFile;
}
}

View File

@ -0,0 +1,75 @@
// 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.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Checks.Components;
namespace osu.Game.Rulesets.Edit.Checks
{
public class CheckAudioQuality : ICheck
{
// This is a requirement as stated in the Ranking Criteria.
// See https://osu.ppy.sh/wiki/en/Ranking_Criteria#rules.4
private const int max_bitrate = 192;
// "A song's audio file /.../ must be of reasonable quality. Try to find the highest quality source file available"
// There not existing a version with a bitrate of 128 kbps or higher is extremely rare.
private const int min_bitrate = 128;
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Audio, "Too high or low audio bitrate");
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
{
new IssueTemplateTooHighBitrate(this),
new IssueTemplateTooLowBitrate(this),
new IssueTemplateNoBitrate(this)
};
public IEnumerable<Issue> Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap)
{
var audioFile = playableBeatmap.Metadata?.AudioFile;
if (audioFile == null)
yield break;
var track = workingBeatmap.Track;
if (track?.Bitrate == null || track.Bitrate.Value == 0)
yield return new IssueTemplateNoBitrate(this).Create();
else if (track.Bitrate.Value > max_bitrate)
yield return new IssueTemplateTooHighBitrate(this).Create(track.Bitrate.Value);
else if (track.Bitrate.Value < min_bitrate)
yield return new IssueTemplateTooLowBitrate(this).Create(track.Bitrate.Value);
}
public class IssueTemplateTooHighBitrate : IssueTemplate
{
public IssueTemplateTooHighBitrate(ICheck check)
: base(check, IssueType.Problem, "The audio bitrate ({0} kbps) exceeds {1} kbps.")
{
}
public Issue Create(int bitrate) => new Issue(this, bitrate, max_bitrate);
}
public class IssueTemplateTooLowBitrate : IssueTemplate
{
public IssueTemplateTooLowBitrate(ICheck check)
: base(check, IssueType.Problem, "The audio bitrate ({0} kbps) is lower than {1} kbps.")
{
}
public Issue Create(int bitrate) => new Issue(this, bitrate, min_bitrate);
}
public class IssueTemplateNoBitrate : IssueTemplate
{
public IssueTemplateNoBitrate(ICheck check)
: base(check, IssueType.Error, "The audio bitrate could not be retrieved.")
{
}
public Issue Create() => new Issue(this);
}
}
}

View File

@ -1,61 +0,0 @@
// 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.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Checks.Components;
namespace osu.Game.Rulesets.Edit.Checks
{
public class CheckBackground : ICheck
{
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Missing background");
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
{
new IssueTemplateNoneSet(this),
new IssueTemplateDoesNotExist(this)
};
public IEnumerable<Issue> Run(IBeatmap beatmap)
{
if (beatmap.Metadata.BackgroundFile == null)
{
yield return new IssueTemplateNoneSet(this).Create();
yield break;
}
// If the background is set, also make sure it still exists.
var set = beatmap.BeatmapInfo.BeatmapSet;
var file = set.Files.FirstOrDefault(f => f.Filename == beatmap.Metadata.BackgroundFile);
if (file != null)
yield break;
yield return new IssueTemplateDoesNotExist(this).Create(beatmap.Metadata.BackgroundFile);
}
public class IssueTemplateNoneSet : IssueTemplate
{
public IssueTemplateNoneSet(ICheck check)
: base(check, IssueType.Problem, "No background has been set.")
{
}
public Issue Create() => new Issue(this);
}
public class IssueTemplateDoesNotExist : IssueTemplate
{
public IssueTemplateDoesNotExist(ICheck check)
: base(check, IssueType.Problem, "The background file \"{0}\" does not exist.")
{
}
public Issue Create(string filename) => new Issue(this, filename);
}
}
}

View File

@ -0,0 +1,15 @@
// 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 osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Checks.Components;
namespace osu.Game.Rulesets.Edit.Checks
{
public class CheckBackgroundPresence : CheckFilePresence
{
protected override CheckCategory Category => CheckCategory.Resources;
protected override string TypeOfFile => "background";
protected override string GetFilename(IBeatmap playableBeatmap) => playableBeatmap.Metadata?.BackgroundFile;
}
}

View File

@ -0,0 +1,98 @@
// 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.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Checks.Components;
namespace osu.Game.Rulesets.Edit.Checks
{
public class CheckBackgroundQuality : ICheck
{
// These are the requirements as stated in the Ranking Criteria.
// See https://osu.ppy.sh/wiki/en/Ranking_Criteria#rules.5
private const int min_width = 160;
private const int max_width = 2560;
private const int min_height = 120;
private const int max_height = 1440;
private const double max_filesize_mb = 2.5d;
// It's usually possible to find a higher resolution of the same image if lower than these.
private const int low_width = 960;
private const int low_height = 540;
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Too high or low background resolution");
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
{
new IssueTemplateTooHighResolution(this),
new IssueTemplateTooLowResolution(this),
new IssueTemplateTooUncompressed(this)
};
public IEnumerable<Issue> Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap)
{
var backgroundFile = playableBeatmap.Metadata?.BackgroundFile;
if (backgroundFile == null)
yield break;
var texture = workingBeatmap.Background;
if (texture == null)
yield break;
if (texture.Width > max_width || texture.Height > max_height)
yield return new IssueTemplateTooHighResolution(this).Create(texture.Width, texture.Height);
if (texture.Width < min_width || texture.Height < min_height)
yield return new IssueTemplateTooLowResolution(this).Create(texture.Width, texture.Height);
else if (texture.Width < low_width || texture.Height < low_height)
yield return new IssueTemplateLowResolution(this).Create(texture.Width, texture.Height);
string storagePath = playableBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(backgroundFile);
double filesizeMb = workingBeatmap.GetStream(storagePath).Length / (1024d * 1024d);
if (filesizeMb > max_filesize_mb)
yield return new IssueTemplateTooUncompressed(this).Create(filesizeMb);
}
public class IssueTemplateTooHighResolution : IssueTemplate
{
public IssueTemplateTooHighResolution(ICheck check)
: base(check, IssueType.Problem, "The background resolution ({0} x {1}) exceeds {2} x {3}.")
{
}
public Issue Create(double width, double height) => new Issue(this, width, height, max_width, max_height);
}
public class IssueTemplateTooLowResolution : IssueTemplate
{
public IssueTemplateTooLowResolution(ICheck check)
: base(check, IssueType.Problem, "The background resolution ({0} x {1}) is lower than {2} x {3}.")
{
}
public Issue Create(double width, double height) => new Issue(this, width, height, min_width, min_height);
}
public class IssueTemplateLowResolution : IssueTemplate
{
public IssueTemplateLowResolution(ICheck check)
: base(check, IssueType.Warning, "The background resolution ({0} x {1}) is lower than {2} x {3}.")
{
}
public Issue Create(double width, double height) => new Issue(this, width, height, low_width, low_height);
}
public class IssueTemplateTooUncompressed : IssueTemplate
{
public IssueTemplateTooUncompressed(ICheck check)
: base(check, IssueType.Problem, "The background filesize ({0:0.##} MB) exceeds {1} MB.")
{
}
public Issue Create(double actualMb) => new Issue(this, actualMb, max_filesize_mb);
}
}
}

View File

@ -0,0 +1,63 @@
// 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.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Checks.Components;
namespace osu.Game.Rulesets.Edit.Checks
{
public abstract class CheckFilePresence : ICheck
{
protected abstract CheckCategory Category { get; }
protected abstract string TypeOfFile { get; }
protected abstract string GetFilename(IBeatmap playableBeatmap);
public CheckMetadata Metadata => new CheckMetadata(Category, $"Missing {TypeOfFile}");
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
{
new IssueTemplateNoneSet(this),
new IssueTemplateDoesNotExist(this)
};
public IEnumerable<Issue> Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap)
{
var filename = GetFilename(playableBeatmap);
if (filename == null)
{
yield return new IssueTemplateNoneSet(this).Create(TypeOfFile);
yield break;
}
// If the file is set, also make sure it still exists.
var storagePath = playableBeatmap.BeatmapInfo.BeatmapSet.GetPathForFile(filename);
if (storagePath != null)
yield break;
yield return new IssueTemplateDoesNotExist(this).Create(TypeOfFile, filename);
}
public class IssueTemplateNoneSet : IssueTemplate
{
public IssueTemplateNoneSet(ICheck check)
: base(check, IssueType.Problem, "No {0} has been set.")
{
}
public Issue Create(string typeOfFile) => new Issue(this, typeOfFile);
}
public class IssueTemplateDoesNotExist : IssueTemplate
{
public IssueTemplateDoesNotExist(ICheck check)
: base(check, IssueType.Problem, "The {0} file \"{1}\" does not exist.")
{
}
public Issue Create(string typeOfFile, string filename) => new Issue(this, typeOfFile, filename);
}
}
}

View File

@ -24,7 +24,8 @@ namespace osu.Game.Rulesets.Edit.Checks.Components
/// <summary>
/// Runs this check and returns any issues detected for the provided beatmap.
/// </summary>
/// <param name="beatmap">The beatmap to run the check on.</param>
public IEnumerable<Issue> Run(IBeatmap beatmap);
/// <param name="playableBeatmap">The playable beatmap of the beatmap to run the check on.</param>
/// <param name="workingBeatmap">The working beatmap of the beatmap to run the check on.</param>
public IEnumerable<Issue> Run(IBeatmap playableBeatmap, IWorkingBeatmap workingBeatmap);
}
}

View File

@ -12,6 +12,6 @@ namespace osu.Game.Rulesets.Edit
/// </summary>
public interface IBeatmapVerifier
{
public IEnumerable<Issue> Run(IBeatmap beatmap);
public IEnumerable<Issue> Run(IBeatmap playableBeatmap, WorkingBeatmap workingBeatmap);
}
}

View File

@ -116,6 +116,8 @@ namespace osu.Game.Screens.Edit
protected override Texture GetBackground() => throw new NotImplementedException();
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
}
}
}

View File

@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
@ -60,7 +61,10 @@ namespace osu.Game.Screens.Edit.Verify
private EditorClock clock { get; set; }
[Resolved]
protected EditorBeatmap Beatmap { get; private set; }
private IBindable<WorkingBeatmap> workingBeatmap { get; set; }
[Resolved]
private EditorBeatmap beatmap { get; set; }
[Resolved]
private Bindable<Issue> selectedIssue { get; set; }
@ -72,7 +76,7 @@ namespace osu.Game.Screens.Edit.Verify
private void load(OverlayColourProvider colours)
{
generalVerifier = new BeatmapVerifier();
rulesetVerifier = Beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier();
rulesetVerifier = beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier();
RelativeSizeAxes = Axes.Both;
@ -118,10 +122,10 @@ namespace osu.Game.Screens.Edit.Verify
private void refresh()
{
var issues = generalVerifier.Run(Beatmap);
var issues = generalVerifier.Run(beatmap, workingBeatmap.Value);
if (rulesetVerifier != null)
issues = issues.Concat(rulesetVerifier.Run(Beatmap));
issues = issues.Concat(rulesetVerifier.Run(beatmap, workingBeatmap.Value));
table.Issues = issues
.OrderBy(issue => issue.Template.Type)

View File

@ -215,6 +215,8 @@ namespace osu.Game.Tests.Beatmaps
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
{
var converter = base.CreateBeatmapConverter(beatmap, ruleset);

View File

@ -1,6 +1,7 @@
// 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 osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
@ -35,6 +36,8 @@ namespace osu.Game.Tests.Beatmaps
protected override Storyboard GetStoryboard() => storyboard ?? base.GetStoryboard();
public override Stream GetStream(string storagePath) => null;
protected override Texture GetBackground() => null;
protected override Track GetBeatmapTrack() => null;