173 lines
5.8 KiB
PHP
173 lines
5.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Controller\Api\Stations\Reports;
|
|
|
|
use App\Entity;
|
|
use App\Http\Response;
|
|
use App\Http\ServerRequest;
|
|
use App\Service\MusicBrainz;
|
|
use Carbon\CarbonImmutable;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Produce a report in SoundExchange (the US webcaster licensing agency) format.
|
|
*/
|
|
final class SoundExchangeAction
|
|
{
|
|
public function __construct(
|
|
private readonly EntityManagerInterface $em,
|
|
private readonly MusicBrainz $musicBrainz
|
|
) {
|
|
}
|
|
|
|
public function __invoke(
|
|
ServerRequest $request,
|
|
Response $response,
|
|
string $station_id
|
|
): ResponseInterface {
|
|
$station = $request->getStation();
|
|
$tzObject = $station->getTimezoneObject();
|
|
|
|
$defaultStartDate = CarbonImmutable::parse('first day of last month', $tzObject)->format('Y-m-d');
|
|
$defaultEndDate = CarbonImmutable::parse('last day of last month', $tzObject)->format('Y-m-d');
|
|
|
|
$data = $request->getParams();
|
|
|
|
$data['start_date'] ??= $defaultStartDate;
|
|
$data['end_date'] ??= $defaultEndDate;
|
|
|
|
$startDate = CarbonImmutable::parse($data['start_date'] . ' 00:00:00', $tzObject);
|
|
$endDate = CarbonImmutable::parse($data['end_date'] . ' 23:59:59', $tzObject);
|
|
|
|
$fetchIsrc = 'true' === ($data['fetch_isrc'] ?? 'false');
|
|
|
|
$export = [
|
|
[
|
|
'NAME_OF_SERVICE',
|
|
'TRANSMISSION_CATEGORY',
|
|
'FEATURED_ARTIST',
|
|
'SOUND_RECORDING_TITLE',
|
|
'ISRC',
|
|
'ALBUM_TITLE',
|
|
'MARKETING_LABEL',
|
|
'ACTUAL_TOTAL_PERFORMANCES',
|
|
],
|
|
];
|
|
|
|
$all_media = $this->em->createQuery(
|
|
<<<'DQL'
|
|
SELECT sm, spm, sp, smcf
|
|
FROM App\Entity\StationMedia sm
|
|
LEFT JOIN sm.custom_fields smcf
|
|
LEFT JOIN sm.playlists spm
|
|
LEFT JOIN spm.playlist sp
|
|
WHERE sm.storage_location = :storageLocation
|
|
AND sp.station IS NULL OR sp.station = :station
|
|
DQL
|
|
)->setParameter('station', $station)
|
|
->setParameter('storageLocation', $station->getMediaStorageLocation())
|
|
->getArrayResult();
|
|
|
|
$media_by_id = array_column($all_media, null, 'id');
|
|
|
|
$history_rows = $this->em->createQuery(
|
|
<<<'DQL'
|
|
SELECT sh.song_id AS song_id, sh.text, sh.artist, sh.title, sh.media_id, COUNT(sh.id) AS plays,
|
|
SUM(sh.unique_listeners) AS unique_listeners
|
|
FROM App\Entity\SongHistory sh
|
|
WHERE sh.station = :station
|
|
AND sh.timestamp_start <= :time_end
|
|
AND sh.timestamp_end >= :time_start
|
|
GROUP BY sh.song_id
|
|
DQL
|
|
)->setParameter('station', $station)
|
|
->setParameter('time_start', $startDate->getTimestamp())
|
|
->setParameter('time_end', $endDate->getTimestamp())
|
|
->getArrayResult();
|
|
|
|
// TODO: Fix this (not all song rows have a media_id)
|
|
$history_rows_by_id = array_column($history_rows, null, 'media_id');
|
|
|
|
// Remove any reference to the "Stream Offline" song.
|
|
$offline_song_hash = Entity\Song::createOffline()->getSongId();
|
|
unset($history_rows_by_id[$offline_song_hash]);
|
|
|
|
// Assemble report items
|
|
$station_name = $station->getName();
|
|
|
|
$set_isrc_query = $this->em->createQuery(
|
|
<<<'DQL'
|
|
UPDATE App\Entity\StationMedia sm
|
|
SET sm.isrc = :isrc
|
|
WHERE sm.id = :media_id
|
|
DQL
|
|
);
|
|
|
|
foreach ($history_rows_by_id as $song_id => $history_row) {
|
|
$song_row = $media_by_id[$song_id] ?? $history_row;
|
|
|
|
// Try to find the ISRC if it's not already listed.
|
|
if ($fetchIsrc && empty($song_row['isrc'])) {
|
|
$isrc = $this->findISRC($song_row);
|
|
$song_row['isrc'] = $isrc;
|
|
|
|
if (null !== $isrc && isset($song_row['media_id'])) {
|
|
$set_isrc_query->setParameter('isrc', $isrc)
|
|
->setParameter('media_id', $song_row['media_id'])
|
|
->execute();
|
|
}
|
|
}
|
|
|
|
$export[] = [
|
|
$station_name,
|
|
'A',
|
|
$song_row['artist'] ?? '',
|
|
$song_row['title'] ?? '',
|
|
$song_row['isrc'] ?? '',
|
|
$song_row['album'] ?? '',
|
|
'',
|
|
$history_row['unique_listeners'],
|
|
];
|
|
}
|
|
|
|
// Assemble export into SoundExchange format
|
|
$export_txt_raw = [];
|
|
foreach ($export as $export_row) {
|
|
foreach ($export_row as $i => $export_col) {
|
|
if (!is_numeric($export_col)) {
|
|
$export_row[$i] = '^' . str_replace(['^', '|'], ['', ''], strtoupper($export_col)) . '^';
|
|
}
|
|
}
|
|
$export_txt_raw[] = implode('|', $export_row);
|
|
}
|
|
$export_txt = implode("\n", $export_txt_raw);
|
|
|
|
// Example: WABC01012009-31012009_A.txt
|
|
$export_filename = strtoupper($station->getShortName())
|
|
. $startDate->format('dmY') . '-'
|
|
. $endDate->format('dmY') . '_A.txt';
|
|
|
|
return $response->renderStringAsFile($export_txt, 'text/plain', $export_filename);
|
|
}
|
|
|
|
private function findISRC(array $song_row): ?string
|
|
{
|
|
$song = Entity\Song::createFromArray($song_row);
|
|
|
|
try {
|
|
foreach ($this->musicBrainz->findRecordingsForSong($song, 'isrcs') as $recording) {
|
|
if (!empty($recording['isrcs'])) {
|
|
return $recording['isrcs'][0];
|
|
}
|
|
}
|
|
return null;
|
|
} catch (Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|