Skip to content
This repository has been archived by the owner on Oct 13, 2024. It is now read-only.

fix(themes): fix incorrect audio container and add unit testing #98

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ jobs:
dotnet-target: net6.0
verbosity: debug

- name: Unit Test
id: test
run: |
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover

- name: Upload coverage
# any except cancelled or skipped
if: always() && (steps.test.outcome == 'success' || steps.test.outcome == 'failure')
uses: codecov/codecov-action@v3

- name: Rename artifacts
run: |
mkdir -p artifacts
Expand Down
32 changes: 32 additions & 0 deletions Jellyfin.Plugin.Themerr.Tests/Jellyfin.Plugin.Themerr.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.msbuild" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Jellyfin.Plugin.Themerr\Jellyfin.Plugin.Themerr.csproj" />
</ItemGroup>

</Project>
103 changes: 103 additions & 0 deletions Jellyfin.Plugin.Themerr.Tests/TestThemerrManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using Xunit.Abstractions;

namespace Jellyfin.Plugin.Themerr.Tests;

public class TestThemerrManager
{
private readonly ITestOutputHelper _testOutputHelper;

public TestThemerrManager(ITestOutputHelper testOutputHelper)
{
_testOutputHelper = testOutputHelper;
}

[Fact]
public void TestSaveMp3()
{
// set destination with themerr_jellyfin_tests as the folder name
var destinationFile = Path.Combine(
// "themerr_jellyfin_tests",
"theme.mp3"
);

// create a list of youtube urls
var videoUrls = new List<string>
{
"https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"https://www.youtube.com/watch?v=yPYZpwSpKmA",
"https://www.youtube.com/watch?v=Ghmd4QzT9YY",
"https://www.youtube.com/watch?v=LVEWkghDh9A"
};
foreach (var videoUrl in videoUrls)
{
// log
_testOutputHelper.WriteLine($"Attempting to download {videoUrl}");

// run and wait
ThemerrManager.SaveMp3(destinationFile, videoUrl);

// wait until the file is downloaded
Thread.Sleep(5000); // 5 seconds

// check if file exists
Assert.True(File.Exists(destinationFile));

// check if the file is an actual mp3
// https://en.wikipedia.org/wiki/List_of_file_signatures
var fileBytes = File.ReadAllBytes(destinationFile);
var fileBytesHex = BitConverter.ToString(fileBytes);

// make sure the file does is not WebM, starts with `1A 45 DF A3`
var isNotWebM = !fileBytesHex.StartsWith("1A-45-DF-A3");
Assert.True(isNotWebM);

// valid mp3 signatures dictionary with offsets
var validMp3Signatures = new Dictionary<string, int>
{
{"66-74-79-70-64-61-73-68", 4}, // Mp4 container?
{"66-74-79-70-69-73-6F-6D", 4}, // Mp4 container
{"49-44-33", 0}, // ID3
{"FF-FB", 0}, // MPEG-1 Layer 3
{"FF-F3", 0}, // MPEG-1 Layer 3
{"FF-F2", 0} // MPEG-1 Layer 3
};

// log beginning of fileBytesHex
_testOutputHelper.WriteLine($"Beginning of fileBytesHex: {fileBytesHex.Substring(0, 40)}");

// check if the file is an actual mp3
var isMp3 = false;

// loop through validMp3Signatures
foreach (var (signature, offset) in validMp3Signatures)
{
// log
_testOutputHelper.WriteLine($"Checking for {signature} at offset of {offset} bytes");

// remove the offset bytes
var fileBytesHexWithoutOffset = fileBytesHex.Substring(offset * 3);

// check if the beginning of the fileBytesHexWithoutOffset matches the signature
var isSignature = fileBytesHexWithoutOffset.StartsWith(signature);
if (isSignature)
{
// log
_testOutputHelper.WriteLine($"Found {signature} at offset {offset}");

// set isMp3 to true
isMp3 = true;

// break out of loop
break;
}

// log
_testOutputHelper.WriteLine($"Did not find {signature} at offset {offset}");
}
Assert.True(isMp3);

// delete file
File.Delete(destinationFile);
}
}
}
1 change: 1 addition & 0 deletions Jellyfin.Plugin.Themerr.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using Xunit;
6 changes: 6 additions & 0 deletions Jellyfin.Plugin.Themerr.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.29905.134
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Plugin.Themerr", "Jellyfin.Plugin.Themerr\Jellyfin.Plugin.Themerr.csproj", "{A6C0029B-F304-4577-92D3-32153DCE1FDC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Plugin.Themerr.Tests", "Jellyfin.Plugin.Themerr.Tests\Jellyfin.Plugin.Themerr.Tests.csproj", "{C9967D5D-3EB3-4F41-AB71-0C0330B21644}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -15,6 +17,10 @@ Global
{A6C0029B-F304-4577-92D3-32153DCE1FDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6C0029B-F304-4577-92D3-32153DCE1FDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6C0029B-F304-4577-92D3-32153DCE1FDC}.Release|Any CPU.Build.0 = Release|Any CPU
{C9967D5D-3EB3-4F41-AB71-0C0330B21644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9967D5D-3EB3-4F41-AB71-0C0330B21644}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9967D5D-3EB3-4F41-AB71-0C0330B21644}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9967D5D-3EB3-4F41-AB71-0C0330B21644}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
9 changes: 6 additions & 3 deletions Jellyfin.Plugin.Themerr/ThemerrManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,18 @@ public ThemerrManager(ILibraryManager libraryManager, ILogger<ThemerrManager> lo
}


private static void SaveMp3(string destination, string videoUrl)
public static void SaveMp3(string destination, string videoUrl)
{
Task.Run(async () =>
{
var youtube = new YoutubeClient();
var streamManifest = await youtube.Videos.Streams.GetManifestAsync(videoUrl);

// highest bitrate audio stream
var streamInfo = streamManifest.GetAudioOnlyStreams().GetWithHighestBitrate();
// highest bitrate audio mp3 stream
var streamInfo = streamManifest
.GetAudioOnlyStreams()
.Where(s => s.Container == Container.Mp4)
.GetWithHighestBitrate();

// Download the stream to a file
await youtube.Videos.Streams.DownloadAsync(streamInfo, destination);
Expand Down
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ Integrations
:alt: Read the Docs
:target: http://themerr-jellyfin.readthedocs.io/

.. image:: https://img.shields.io/codecov/c/gh/LizardByte/Themerr-jellyfin?token=E7LQZ34U04&style=for-the-badge&logo=codecov&label=codecov
:alt: Codecov
:target: https://codecov.io/gh/LizardByte/Themerr-jellyfin

Downloads
---------

Expand Down
15 changes: 15 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
codecov:
branch: master

coverage:
status:
project:
default:
target: auto
threshold: 10%

comment:
layout: "diff, flags, files"
behavior: default
require_changes: false # if true: only post the comment if coverage changes
Loading