Compare commits
15 Commits
e6d061700c
...
69ead40727
Author | SHA1 | Date |
---|---|---|
Charles-Henri BERNARD | 69ead40727 | |
Buster Neece | c3e540eae7 | |
Buster Neece | 4cf9c626ad | |
Buster Neece | 08c5468562 | |
Buster Neece | bee576b5e7 | |
Buster Neece | f28c472568 | |
Buster Neece | e2ba8d35b8 | |
Buster Neece | 70503d8e57 | |
Charles-Henri BERNARD | fd2a0896c0 | |
Charles-Henri BERNARD | 7ba5e5a437 | |
Charles-Henri BERNARD | 9aef2cf2d2 | |
Charles-Henri BERNARD | e49db10137 | |
Charles-Henri BERNARD | 99c288a072 | |
Charles-Henri BERNARD | 460c49b01d | |
Charles-Henri BERNARD | 61db6e5735 |
|
@ -5,10 +5,6 @@ release channel, you can take advantage of these new features and fixes.
|
|||
|
||||
## New Features/Changes
|
||||
|
||||
- **Improved Media Search**: We've overhauled our media search functions to add support
|
||||
for [extended search](https://www.fusejs.io/examples.html#extended-search) queries. Searches now also include values
|
||||
stored in custom fields.
|
||||
|
||||
## Code Quality/Technical Changes
|
||||
|
||||
## Bug Fixes
|
||||
|
|
|
@ -51,7 +51,6 @@
|
|||
"league/mime-type-detection": "^1.7",
|
||||
"league/oauth2-client": "^2.6",
|
||||
"league/plates": "^3.1",
|
||||
"loilo/fuse": "^7.0",
|
||||
"lstrojny/fxmlrpc": "dev-master",
|
||||
"marcw/rss-writer": "^0.4.0",
|
||||
"matomo/device-detector": "^6",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "1a41be64f51e4bc965bcf2f53e06bc11",
|
||||
"content-hash": "5bca0c641ba21645d05ab830394898ae",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-crt-php",
|
||||
|
@ -3426,69 +3426,6 @@
|
|||
},
|
||||
"time": "2023-01-16T20:25:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "loilo/fuse",
|
||||
"version": "7.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/loilo/Fuse.git",
|
||||
"reference": "3304788d2f30d677faea700e0e466006f1d70015"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/loilo/Fuse/zipball/3304788d2f30d677faea700e0e466006f1d70015",
|
||||
"reference": "3304788d2f30d677faea700e0e466006f1d70015",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dms/phpunit-arraysubset-asserts": "^0.3.0 || ^0.4.0 || ^0.5.0",
|
||||
"phpunit/phpunit": "^8.0 || ^9.0",
|
||||
"psalm/plugin-phpunit": "^0.16.1 || ^0.17.0 || ^0.18.0",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
"vimeo/psalm": "^4.9 || ^5.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Core/computeScore.php",
|
||||
"src/Core/config.php",
|
||||
"src/Core/format.php",
|
||||
"src/Core/parse.php",
|
||||
"src/Helpers/get.php",
|
||||
"src/Helpers/sort.php",
|
||||
"src/Helpers/types.php",
|
||||
"src/Search/Bitap/computeScore.php",
|
||||
"src/Search/Bitap/convertMaskToIndices.php",
|
||||
"src/Search/Bitap/createPatternAlphabet.php",
|
||||
"src/Search/Bitap/search.php",
|
||||
"src/Search/Extended/parseQuery.php",
|
||||
"src/Transform/transformMatches.php",
|
||||
"src/Transform/transformScore.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Fuse\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Florian Reuschel",
|
||||
"email": "florian@loilo.de"
|
||||
}
|
||||
],
|
||||
"description": "Fuzzy search for PHP based on Bitap algorithm",
|
||||
"support": {
|
||||
"issues": "https://github.com/loilo/Fuse/issues",
|
||||
"source": "https://github.com/loilo/Fuse/tree/v7.0.1"
|
||||
},
|
||||
"time": "2023-11-27T17:21:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "lstrojny/fxmlrpc",
|
||||
"version": "dev-master",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -90,6 +90,27 @@
|
|||
</template>
|
||||
</form-group-field>
|
||||
|
||||
<form-group-field
|
||||
v-if="enableAdvancedFeatures"
|
||||
id="edit_form_backend_dj_port_secondary"
|
||||
class="col-md-6"
|
||||
:field="v$.backend_config.dj_port_secondary"
|
||||
input-type="number"
|
||||
:input-attrs="{ min: '0' }"
|
||||
advanced
|
||||
:label="$gettext('Customize DJ/Streamer Port Secondary')"
|
||||
>
|
||||
<template #description>
|
||||
{{
|
||||
$gettext('No other program can be using this port. Leave blank to automatically assign a port.')
|
||||
}}
|
||||
<br>
|
||||
{{
|
||||
$gettext('Note: the port after this one will automatically be used for legacy connections.')
|
||||
}}
|
||||
</template>
|
||||
</form-group-field>
|
||||
|
||||
<form-group-field
|
||||
id="edit_form_backend_dj_buffer"
|
||||
class="col-md-6"
|
||||
|
|
|
@ -4,6 +4,7 @@ body.page-minimal {
|
|||
background: var(--bs-body-bg) var(--public-page-bg);
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
background-position: center center;
|
||||
|
||||
footer {
|
||||
text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black, 0 0 4px #000;
|
||||
|
|
|
@ -64,6 +64,7 @@ export default defineConfig({
|
|||
},
|
||||
server: {
|
||||
strictPort: true,
|
||||
host: true,
|
||||
fs: {
|
||||
allow: ['..']
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ use App\Http\RouterInterface;
|
|||
use App\Http\ServerRequest;
|
||||
use App\Media\MimeType;
|
||||
use App\Paginator;
|
||||
use App\Service\FuseSearch;
|
||||
use App\Utilities\Strings;
|
||||
use App\Utilities\Types;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
|
@ -37,8 +36,7 @@ final class ListAction implements SingleActionInterface
|
|||
|
||||
public function __construct(
|
||||
private readonly CacheInterface $cache,
|
||||
private readonly StationFilesystems $stationFilesystems,
|
||||
private readonly FuseSearch $fuseSearch
|
||||
private readonly StationFilesystems $stationFilesystems
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -71,12 +69,7 @@ final class ListAction implements SingleActionInterface
|
|||
];
|
||||
|
||||
if ($isSearch) {
|
||||
if ($playlist instanceof StationPlaylist) {
|
||||
$cacheKeyParts[] = 'search_playlist_' . $playlist->getIdRequired();
|
||||
}
|
||||
if (!empty($special)) {
|
||||
$cacheKeyParts[] = 'search_special_' . rawurlencode($special);
|
||||
}
|
||||
$cacheKeyParts[] = 'search_' . rawurlencode($searchPhraseFull);
|
||||
}
|
||||
|
||||
$cacheKey = implode('.', $cacheKeyParts);
|
||||
|
@ -120,7 +113,7 @@ final class ListAction implements SingleActionInterface
|
|||
)->setParameter('storageLocation', $storageLocation)
|
||||
->setParameter('path', $pathLike);
|
||||
|
||||
// Apply special searches (string searches happen below).
|
||||
// Apply searching
|
||||
if ($isSearch) {
|
||||
if ('unprocessable' === $special) {
|
||||
$mediaQueryBuilder = null;
|
||||
|
@ -154,6 +147,12 @@ final class ListAction implements SingleActionInterface
|
|||
)->setParameter('playlist', $playlist);
|
||||
}
|
||||
|
||||
if (!empty($searchPhrase)) {
|
||||
$mediaQueryBuilder->andWhere(
|
||||
'(sm.title LIKE :query OR sm.artist LIKE :query OR sm.path LIKE :query)'
|
||||
)->setParameter('query', '%' . $searchPhrase . '%');
|
||||
}
|
||||
|
||||
$unprocessableMediaRaw = [];
|
||||
}
|
||||
|
||||
|
@ -266,41 +265,11 @@ final class ListAction implements SingleActionInterface
|
|||
$this->cache->set($cacheKey, $result, 300);
|
||||
}
|
||||
|
||||
// Apply searching
|
||||
if (!empty($searchPhrase)) {
|
||||
$result = $this->fuseSearch->search(
|
||||
$searchPhrase,
|
||||
$result,
|
||||
[
|
||||
'keys' => [
|
||||
'text',
|
||||
'path',
|
||||
'album',
|
||||
'genre',
|
||||
'custom_fields',
|
||||
],
|
||||
'threshold' => 0.2,
|
||||
'useExtendedSearch' => true,
|
||||
'getFn' => fn(FileList $record, array $key) => match ($key[0] ?? null) {
|
||||
'text' => $record->media->text,
|
||||
'path' => $record->path,
|
||||
'album' => $record->media->album,
|
||||
'genre' => $record->media->genre,
|
||||
'custom_fields' => implode(' ', $record->media->custom_fields),
|
||||
default => null,
|
||||
},
|
||||
],
|
||||
$cacheKey,
|
||||
300,
|
||||
$flushCache
|
||||
);
|
||||
}
|
||||
|
||||
$propertyAccessor = self::getPropertyAccessor();
|
||||
|
||||
// Apply sorting
|
||||
[$sort, $sortOrder] = $this->getSortFromRequest($request);
|
||||
|
||||
$propertyAccessor = self::getPropertyAccessor();
|
||||
|
||||
usort(
|
||||
$result,
|
||||
static fn(FileList $a, FileList $b) => self::sortRows(
|
||||
|
|
|
@ -65,8 +65,7 @@ final class ChartsAction extends AbstractReportAction
|
|||
$daysOfWeek = [];
|
||||
|
||||
foreach ($dailyStats as $stat) {
|
||||
/** @var CarbonImmutable $statTime */
|
||||
$statTime = $stat['moment'];
|
||||
$statTime = CarbonImmutable::instance($stat['moment']);
|
||||
$statTime = $statTime->shiftTimezone($stationTz);
|
||||
|
||||
$avgRow = new stdClass();
|
||||
|
@ -175,8 +174,7 @@ final class ChartsAction extends AbstractReportAction
|
|||
}
|
||||
|
||||
foreach ($hourlyStats as $stat) {
|
||||
/** @var CarbonImmutable $statTime */
|
||||
$statTime = $stat['moment'];
|
||||
$statTime = CarbonImmutable::instance($stat['moment']);
|
||||
$statTime = $statTime->shiftTimezone($stationTz);
|
||||
|
||||
$hour = $statTime->hour;
|
||||
|
|
|
@ -12,15 +12,6 @@ trait HasMediaSearch
|
|||
{
|
||||
use EntityManagerAwareTrait;
|
||||
|
||||
/**
|
||||
* @param Station $station
|
||||
* @param string $query
|
||||
* @return array{
|
||||
* string,
|
||||
* StationPlaylist|null,
|
||||
* string|null
|
||||
* }
|
||||
*/
|
||||
private function parseSearchQuery(
|
||||
Station $station,
|
||||
string $query
|
||||
|
|
|
@ -39,6 +39,18 @@ class StationBackendConfiguration extends AbstractStationConfiguration
|
|||
$this->set(self::DJ_PORT, $port);
|
||||
}
|
||||
|
||||
public const DJ_PORT_SECONDARY = 'dj_port_decondary';
|
||||
|
||||
public function getDjPortSecondary(): ?int
|
||||
{
|
||||
return Types::intOrNull($this->get(self::DJ_PORT_SECONDARY));
|
||||
}
|
||||
|
||||
public function setDjPortSecondary(?int $port): void
|
||||
{
|
||||
$this->set(self::DJ_PORT_SECONDARY, $port);
|
||||
}
|
||||
|
||||
public const TELNET_PORT = 'telnet_port';
|
||||
|
||||
public function getTelnetPort(): ?int
|
||||
|
|
|
@ -29,8 +29,9 @@ final class CustomUrls
|
|||
public static function getXAccelPath(string $path): ?string
|
||||
{
|
||||
$specialPaths = [
|
||||
'/var/azuracast/backups' => '/internal/backups',
|
||||
'/var/azuracast/stations' => '/internal/stations',
|
||||
'/var/azuracast/storage' => '/internal/storage',
|
||||
'/var/azuracast/backups' => '/internal/backups',
|
||||
];
|
||||
|
||||
foreach ($specialPaths as $diskPath => $nginxPath) {
|
||||
|
|
|
@ -59,6 +59,27 @@ final class Liquidsoap extends AbstractLocalAdapter
|
|||
return $frontendPort + 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the port used for a second DJs/Streamers to connect to LiquidSoap for broadcasting.
|
||||
*
|
||||
* @param Station $station
|
||||
*
|
||||
* @return int The port number to use for this station.
|
||||
*/
|
||||
public function getStreamPortSecondary(Station $station): int
|
||||
{
|
||||
$djPortSecondary = $station->getBackendConfig()->getDjPortSecondary();
|
||||
if (null !== $djPortSecondary) {
|
||||
return $djPortSecondary;
|
||||
}
|
||||
|
||||
// Default to frontend port + 7
|
||||
$frontendConfig = $station->getFrontendConfig();
|
||||
$frontendPort = $frontendConfig->getPort() ?? (8000 + (($station->getId() - 1) * 10));
|
||||
|
||||
return $frontendPort + 7;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the specified remote command on LiquidSoap via the telnet API.
|
||||
*
|
||||
|
|
|
@ -1030,7 +1030,41 @@ final class ConfigWriter implements EventSubscriberInterface
|
|||
|
||||
# Live Broadcasting
|
||||
live = input.harbor({$harborParams})
|
||||
LIQ
|
||||
);
|
||||
|
||||
|
||||
// Live Secondary Port
|
||||
$streamPortSecondary = $this->liquidsoap->getStreamPortSecondary($station);
|
||||
// Paramètres pour live Secondary
|
||||
$harborSecondary_params = [
|
||||
'"' . self::cleanUpString($dj_mount) . '"', // Assurez-vous que $dj_mount est défini correctement
|
||||
'id = "input_streamer_live_secondary"', // ID unique pour live1
|
||||
'port = ' . $streamPortSecondary, // Utilisation de getStreamPort1 pour le port
|
||||
'auth = dj_auth', // Authentification, assurez-vous que 'dj_auth' est correctement défini
|
||||
'icy = true', // Paramètres ICY
|
||||
'icy_metadata_charset = "' . $charset . '"', // Charset pour les métadonnées ICY, assurez-vous que $charset est défini
|
||||
'metadata_charset = "' . $charset . '"', // Charset pour les métadonnées, assurez-vous que $charset est défini
|
||||
'on_connect = live_connected', // Action à la connexion
|
||||
'on_disconnect = live_disconnected', // Action à la déconnexion
|
||||
];
|
||||
|
||||
// Ajouter des paramètres de buffer si nécessaire
|
||||
$djBuffer = $settings->getDjBuffer(); // Assurez-vous que $settings est défini et a une méthode getDjBuffer
|
||||
if (0 !== $djBuffer) {
|
||||
$harborSecondary_params[] = 'buffer = ' . self::toFloat($djBuffer);
|
||||
$harborSecondary_params[] = 'max = ' . self::toFloat(max($djBuffer + 5, 10));
|
||||
}
|
||||
|
||||
// Concaténer les paramètres pour former la chaîne de configuration live1
|
||||
$harborSecondaryParams = implode(', ', $harborSecondary_params);
|
||||
|
||||
// Ajout de la configuration live1 au fichier de configuration LiquidSoap
|
||||
// Assurez-vous que cette ligne est placée au bon endroit dans votre script pour écrire dans le fichier de configuration
|
||||
$event->appendBlock("live1 = input.harbor({$harbor1Params})\n");
|
||||
|
||||
$event->appendBlock(
|
||||
<<<LIQ
|
||||
def insert_missing(m) =
|
||||
if m == [] then
|
||||
[("title", "{$liveBroadcastText}"), ("is_live", "true")]
|
||||
|
@ -1039,8 +1073,9 @@ final class ConfigWriter implements EventSubscriberInterface
|
|||
end
|
||||
end
|
||||
live = metadata.map(insert_missing, live)
|
||||
live1 = metadata.map(insert_missing, live1)
|
||||
|
||||
radio = fallback(id="live_fallback", track_sensitive=false, replay_metadata=true, [live, radio])
|
||||
radio = fallback(id="live_fallback", track_sensitive=false, replay_metadata=true, [live, live1, radio])
|
||||
|
||||
# Skip non-live track when live DJ goes live.
|
||||
def check_live() =
|
||||
|
|
|
@ -289,6 +289,12 @@ final class Configuration
|
|||
$station->setBackendConfig($backendConfig);
|
||||
}
|
||||
|
||||
$djPortSecondary = $backendConfig->getDjPortSecondary();
|
||||
if ($force || null === $djPortSecondary) {
|
||||
$backendConfig->setDjPortSecondary($basePort + 7);
|
||||
$station->setBackendConfig($backendConfig);
|
||||
}
|
||||
|
||||
$telnetPort = $backendConfig->getTelnetPort();
|
||||
if ($force || null === $telnetPort) {
|
||||
$backendConfig->setTelnetPort($basePort + 4);
|
||||
|
@ -374,6 +380,11 @@ final class Configuration
|
|||
$usedPorts[$port] = $stationReference;
|
||||
$usedPorts[$port + 1] = $stationReference;
|
||||
}
|
||||
if (!empty($backendConfig['dj_port_secondary'])) {
|
||||
$port = (int)$backendConfig['dj_port_secondary'];
|
||||
$usedPorts[$port] = $stationReference;
|
||||
$usedPorts[$port + 1] = $stationReference;
|
||||
}
|
||||
if (!empty($backendConfig['telnet_port'])) {
|
||||
$port = (int)$backendConfig['telnet_port'];
|
||||
$usedPorts[$port] = $stationReference;
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Fuse\Fuse;
|
||||
use InvalidArgumentException;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
|
||||
final class FuseSearch
|
||||
{
|
||||
public const int DEFAULT_TTL = 60;
|
||||
|
||||
public function __construct(
|
||||
private readonly CacheInterface $cache,
|
||||
) {
|
||||
}
|
||||
|
||||
public function search(
|
||||
?string $searchPhrase,
|
||||
array $records,
|
||||
array $options,
|
||||
string $cacheKey,
|
||||
int $cacheTtl = self::DEFAULT_TTL,
|
||||
bool $flushCache = false
|
||||
): array {
|
||||
$fuse = $this->buildSearch($records, $options, $cacheKey, $cacheTtl, $flushCache);
|
||||
return array_column($fuse->search($searchPhrase), 'item');
|
||||
}
|
||||
|
||||
public function buildSearch(
|
||||
array $records,
|
||||
array $options,
|
||||
string $cacheKey,
|
||||
int $cacheTtl = self::DEFAULT_TTL,
|
||||
bool $flushCache = false
|
||||
): Fuse {
|
||||
$keys = $options['keys'] ?? null;
|
||||
if (null === $keys) {
|
||||
throw new InvalidArgumentException('No keys provided for search.');
|
||||
}
|
||||
|
||||
$keysHash = substr(md5(json_encode($keys, JSON_THROW_ON_ERROR)), 0, 6);
|
||||
$cacheKey .= '_fuse_' . $keysHash;
|
||||
|
||||
if (!$flushCache && $this->cache->has($cacheKey)) {
|
||||
$indexData = $this->cache->get($cacheKey);
|
||||
$index = Fuse::parseIndex(
|
||||
$indexData,
|
||||
$options
|
||||
);
|
||||
} else {
|
||||
$index = Fuse::createIndex($keys, $records, $options);
|
||||
|
||||
$this->cache->set(
|
||||
$cacheKey,
|
||||
$index->jsonSerialize(),
|
||||
$cacheTtl
|
||||
);
|
||||
}
|
||||
|
||||
return new Fuse($records, $options, $index);
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ final class StationPortCheckerValidator extends ConstraintValidator
|
|||
$portsToCheck = [
|
||||
'frontend_config_port' => $frontendConfig->getPort(),
|
||||
'backend_config_dj_port' => $backendConfig->getDjPort(),
|
||||
'backend_config_dj_port_secondary' => $backendConfig->getDjPortSecondary(),
|
||||
'backend_config_telnet_port' => $backendConfig->getTelnetPort(),
|
||||
];
|
||||
|
||||
|
|
|
@ -149,17 +149,19 @@ server {
|
|||
# Internal handlers used by the application to perform X-Accel-Redirect's for higher performance.
|
||||
location /internal/backups/ {
|
||||
internal;
|
||||
|
||||
add_header Access-Control-Allow-Origin "$upstream_http_access_control_allow_origin";
|
||||
|
||||
alias /var/azuracast/backups/;
|
||||
}
|
||||
|
||||
location /internal/storage/ {
|
||||
internal;
|
||||
add_header Access-Control-Allow-Origin "$upstream_http_access_control_allow_origin";
|
||||
alias /var/azuracast/storage/;
|
||||
}
|
||||
|
||||
location /internal/stations/ {
|
||||
internal;
|
||||
|
||||
add_header Access-Control-Allow-Origin "$upstream_http_access_control_allow_origin";
|
||||
|
||||
alias /var/azuracast/stations/;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,8 +16,12 @@ $diBuilder = AppFactory::createContainerBuilder($environment);
|
|||
|
||||
$httpFactory = new App\Http\HttpFactory();
|
||||
$worker = \Spiral\RoadRunner\Worker::create();
|
||||
|
||||
$psr7Worker = new Spiral\RoadRunner\Http\PSR7Worker($worker, $httpFactory, $httpFactory, $httpFactory);
|
||||
|
||||
// Allow for streaming larger responses back to Roadrunner.
|
||||
$psr7Worker->chunkSize = 1024 * 25;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
$request = $psr7Worker->waitRequest();
|
||||
|
|
Loading…
Reference in New Issue