mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
parent
f3e113e8a8
commit
4306d1e6f6
13 changed files with 118 additions and 14 deletions
6
.github/workflows/unit-backend.yml
vendored
6
.github/workflows/unit-backend.yml
vendored
|
@ -3,25 +3,19 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
paths:
|
||||
- ./*
|
||||
- '!resources/assets/**'
|
||||
- .github/workflows/unit-backend.yml
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
paths:
|
||||
- ./*
|
||||
- '!resources/assets/**'
|
||||
- .github/workflows/unit-backend.yml
|
||||
workflow_dispatch:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
paths:
|
||||
- ./*
|
||||
- '!resources/assets/**'
|
||||
- .github/workflows/unit-backend.yml
|
||||
jobs:
|
||||
|
|
1
.github/workflows/unit-frontend.yml
vendored
1
.github/workflows/unit-frontend.yml
vendored
|
@ -3,7 +3,6 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- next
|
||||
paths:
|
||||
- resources/assets/**
|
||||
- .github/workflows/unit-frontend.yml
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace App\Http\Requests\API;
|
|||
use App\Rules\ValidSmartPlaylistRulePayload;
|
||||
|
||||
/**
|
||||
* @property-read $name
|
||||
* @property-read string $name
|
||||
* @property-read array $rules
|
||||
*/
|
||||
class PlaylistUpdateRequest extends Request
|
||||
|
|
|
@ -98,8 +98,13 @@ class Song extends Model
|
|||
|
||||
protected function lyrics(): Attribute
|
||||
{
|
||||
// Since we're displaying the lyrics using <pre>, replace breaks with newlines and strip all tags.
|
||||
$normalizer = static fn (?string $value): string => strip_tags(preg_replace('#<br\s*/?>#i', PHP_EOL, $value));
|
||||
$normalizer = static function (?string $value): string {
|
||||
// Since we're displaying the lyrics using <pre>, replace breaks with newlines and strip all tags.
|
||||
$value = strip_tags(preg_replace('#<br\s*/?>#i', PHP_EOL, $value));
|
||||
|
||||
// also remove the timestamps that often come with LRC files
|
||||
return preg_replace('/\[\d{2}:\d{2}.\d{2}]\s*/m', '', $value);
|
||||
};
|
||||
|
||||
return new Attribute(get: $normalizer, set: $normalizer);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ class FileSynchronizer
|
|||
private getID3 $getID3,
|
||||
private MediaMetadataService $mediaMetadataService,
|
||||
private SongRepository $songRepository,
|
||||
private SimpleLrcReader $lrcReader,
|
||||
private Cache $cache,
|
||||
private Finder $finder
|
||||
) {
|
||||
|
@ -51,7 +52,14 @@ class FileSynchronizer
|
|||
$info = $this->getID3->analyze($this->filePath);
|
||||
$this->syncError = Arr::get($info, 'error.0') ?: (Arr::get($info, 'playtime_seconds') ? null : 'Empty file');
|
||||
|
||||
return $this->syncError ? null : SongScanInformation::fromGetId3Info($info);
|
||||
if ($this->syncError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$info = SongScanInformation::fromGetId3Info($info);
|
||||
$info->lyrics = $info->lyrics ?: $this->lrcReader->tryReadForMediaFile($this->filePath);
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
32
app/Services/SimpleLrcReader.php
Normal file
32
app/Services/SimpleLrcReader.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class SimpleLrcReader
|
||||
{
|
||||
public function tryReadForMediaFile(string $mediaFilePath): string
|
||||
{
|
||||
$lrcFilePath = self::getLrcFilePath($mediaFilePath);
|
||||
|
||||
try {
|
||||
return $lrcFilePath ? trim(file_get_contents($lrcFilePath)) : '';
|
||||
} catch (Throwable) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private static function getLrcFilePath(string $mediaFilePath): ?string
|
||||
{
|
||||
foreach (['.lrc', '.LRC'] as $extension) {
|
||||
$lrcFilePath = preg_replace('/\.[^.]+$/', $extension, $mediaFilePath);
|
||||
|
||||
if (is_file($lrcFilePath) && is_readable($lrcFilePath)) {
|
||||
return $lrcFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ const deepMerge = (first: object, second: object) => {
|
|||
return mergeWith(first, second, (a, b) => {
|
||||
if (!isObject(b)) return b
|
||||
|
||||
// @ts-ignore
|
||||
return Array.isArray(a) ? [...a, ...b] : { ...a, ...b }
|
||||
})
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ new class extends UnitTestCase {
|
|||
}
|
||||
|
||||
protected test () {
|
||||
it('quques and plays', async () => {
|
||||
it('queues and plays', async () => {
|
||||
const queueMock = this.mock(queueStore, 'queueIfNotQueued')
|
||||
const playMock = this.mock(playbackService, 'play')
|
||||
const song = factory<Song>('song', { playback_state: 'Stopped' })
|
||||
|
|
|
@ -15,7 +15,7 @@ interface Preferences extends Record<string, any> {
|
|||
transcodeOnMobile: boolean
|
||||
supportBarNoBugging: boolean
|
||||
showAlbumArtOverlay: boolean
|
||||
theme: Theme['id'] | null
|
||||
theme?: Theme['id'] | null
|
||||
}
|
||||
|
||||
const preferenceStore = {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace Tests\Integration\Services;
|
||||
|
||||
use App\Services\FileSynchronizer;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
|
||||
class FileSynchronizerTest extends TestCase
|
||||
|
@ -46,11 +47,34 @@ class FileSynchronizerTest extends TestCase
|
|||
self::assertEqualsWithDelta(10, $info->length, 0.1);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function testSongWithoutTitleHasFileNameAsTitle(): void
|
||||
{
|
||||
$this->fileSynchronizer->setFile(__DIR__ . '/../../songs/blank.mp3');
|
||||
|
||||
self::assertSame('blank', $this->fileSynchronizer->getFileScanInformation()->title);
|
||||
}
|
||||
|
||||
public function testIgnoreLrcFileIfEmbeddedLyricsAvailable(): void
|
||||
{
|
||||
$base = sys_get_temp_dir() . '/' . Str::uuid();
|
||||
$mediaFile = $base . '.mp3';
|
||||
$lrcFile = $base . '.lrc';
|
||||
copy(__DIR__ . '/../../songs/full.mp3', $mediaFile);
|
||||
copy(__DIR__ . '/../../blobs/simple.lrc', $lrcFile);
|
||||
|
||||
self::assertSame("Foo\rbar", $this->fileSynchronizer->setFile($mediaFile)->getFileScanInformation()->lyrics);
|
||||
}
|
||||
|
||||
public function testReadLrcFileIfEmbeddedLyricsNotAvailable(): void
|
||||
{
|
||||
$base = sys_get_temp_dir() . '/' . Str::uuid();
|
||||
$mediaFile = $base . '.mp3';
|
||||
$lrcFile = $base . '.lrc';
|
||||
copy(__DIR__ . '/../../songs/blank.mp3', $mediaFile);
|
||||
copy(__DIR__ . '/../../blobs/simple.lrc', $lrcFile);
|
||||
|
||||
$info = $this->fileSynchronizer->setFile($mediaFile)->getFileScanInformation();
|
||||
|
||||
self::assertSame("Line 1\nLine 2\nLine 3", $info->lyrics);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,4 +14,12 @@ class SongTest extends TestCase
|
|||
|
||||
self::assertEquals(['bucket' => 'foo', 'key' => 'bar'], $song->s3_params);
|
||||
}
|
||||
|
||||
public function testLyricsDoNotContainTimestamps(): void
|
||||
{
|
||||
/** @var Song $song */
|
||||
$song = Song::factory()->create(['lyrics' => "[00:00.00]Line 1\n[00:01.00]Line 2\n[00:02.00]Line 3"]);
|
||||
|
||||
self::assertEquals("Line 1\nLine 2\nLine 3", $song->lyrics);
|
||||
}
|
||||
}
|
||||
|
|
30
tests/Unit/Services/SimpleLrcReaderTest.php
Normal file
30
tests/Unit/Services/SimpleLrcReaderTest.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Services;
|
||||
|
||||
use App\Services\SimpleLrcReader;
|
||||
use Illuminate\Support\Str;
|
||||
use Tests\TestCase;
|
||||
|
||||
class SimpleLrcReaderTest extends TestCase
|
||||
{
|
||||
private SimpleLrcReader $reader;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->reader = new SimpleLrcReader();
|
||||
}
|
||||
|
||||
public function testTryReadForMediaFile(): void
|
||||
{
|
||||
$base = sys_get_temp_dir() . '/' . Str::uuid();
|
||||
$lrcFile = $base . '.lrc';
|
||||
|
||||
copy(__DIR__ . '/../../blobs/simple.lrc', $lrcFile);
|
||||
|
||||
self::assertSame("Line 1\nLine 2\nLine 3", $this->reader->tryReadForMediaFile($base . '.mp3'));
|
||||
@unlink($lrcFile);
|
||||
}
|
||||
}
|
3
tests/blobs/simple.lrc
Normal file
3
tests/blobs/simple.lrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
Line 1
|
||||
Line 2
|
||||
Line 3
|
Loading…
Reference in a new issue