Add back ability to import playlist file from existing M3U/PLS file.
This commit is contained in:
parent
7c19adc03d
commit
51f82d1ceb
|
@ -227,14 +227,17 @@ return function (App $app) {
|
|||
$group->put('/toggle', Controller\Api\Stations\PlaylistsController::class . ':toggleAction')
|
||||
->setName('api:stations:playlist:toggle');
|
||||
|
||||
$group->get('/order', Controller\Api\Stations\PlaylistsController::class . ':getOrderAction')
|
||||
->setName('api:stations:playlist:order');
|
||||
|
||||
$group->put('/reshuffle', Controller\Api\Stations\PlaylistsController::class . ':reshuffleAction')
|
||||
->setName('api:stations:playlist:reshuffle');
|
||||
|
||||
$group->get('/order', Controller\Api\Stations\PlaylistsController::class . ':getOrderAction')
|
||||
->setName('api:stations:playlist:order');
|
||||
|
||||
$group->put('/order', Controller\Api\Stations\PlaylistsController::class . ':putOrderAction');
|
||||
|
||||
$group->post('/import', Controller\Api\Stations\PlaylistsController::class . ':importAction')
|
||||
->setName('api:stations:playlist:import');
|
||||
|
||||
$group->get('/export[/{format}]',
|
||||
Controller\Api\Stations\PlaylistsController::class . ':exportAction')
|
||||
->setName('api:stations:playlist:export');
|
||||
|
|
|
@ -36,6 +36,10 @@
|
|||
<b-dropdown-item @click.prevent="doModify(row.item.links.toggle)">
|
||||
{{ langToggleButton(row.item) }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item @click.prevent="doImport(row.item.links.import)"
|
||||
v-if="row.item.source === 'songs'">
|
||||
{{ langImportButton }}
|
||||
</b-dropdown-item>
|
||||
<b-dropdown-item @click.prevent="doReorder(row.item.links.order)"
|
||||
v-if="row.item.source === 'songs' && row.item.order === 'sequential'">
|
||||
{{ langReorderButton }}
|
||||
|
@ -102,6 +106,7 @@
|
|||
<edit-modal ref="editModal" :create-url="listUrl" :station-time-zone="stationTimeZone"
|
||||
@relist="relist"></edit-modal>
|
||||
<reorder-modal ref="reorderModal"></reorder-modal>
|
||||
<import-modal ref="importModal" @relist="relist"></import-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -110,11 +115,12 @@
|
|||
import Schedule from './components/ScheduleView';
|
||||
import EditModal from './station_playlists/PlaylistEditModal';
|
||||
import ReorderModal from './station_playlists/PlaylistReorderModal';
|
||||
import ImportModal from './station_playlists/PlaylistImportModal';
|
||||
import axios from 'axios';
|
||||
|
||||
export default {
|
||||
name: 'StationPlaylists',
|
||||
components: { ReorderModal, EditModal, Schedule, DataTable },
|
||||
components: { ImportModal, ReorderModal, EditModal, Schedule, DataTable },
|
||||
props: {
|
||||
listUrl: String,
|
||||
scheduleUrl: String,
|
||||
|
@ -147,6 +153,9 @@
|
|||
},
|
||||
langReshuffleButton () {
|
||||
return this.$gettext('Reshuffle');
|
||||
},
|
||||
langImportButton () {
|
||||
return this.$gettext('Import from PLS/M3U');
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
@ -212,6 +221,9 @@
|
|||
doReorder (url) {
|
||||
this.$refs.reorderModal.open(url);
|
||||
},
|
||||
doImport (url) {
|
||||
this.$refs.importModal.open(url);
|
||||
},
|
||||
doModify (url) {
|
||||
notify('<b>' + this.$gettext('Applying changes...') + '</b>', 'warning', {
|
||||
delay: 3000
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<b-modal id="import_modal" ref="modal" :title="langTitle">
|
||||
<b-form class="form" @submit.prevent="doSubmit">
|
||||
<b-form-group label-for="import_modal_playlist_file">
|
||||
<template v-slot:label>
|
||||
<translate>Select PLS/M3U File to Import</translate>
|
||||
</template>
|
||||
<template v-slot:description>
|
||||
<translate>AzuraCast will scan the uploaded file for matches in this station's music library. Media should already be uploaded before running this step. You can re-run this tool as many times as needed.</translate>
|
||||
</template>
|
||||
<b-form-file id="import_modal_playlist_file" v-model="playlistFile"></b-form-file>
|
||||
</b-form-group>
|
||||
|
||||
<invisible-submit-button/>
|
||||
</b-form>
|
||||
<template v-slot:modal-footer>
|
||||
<b-button variant="default" type="button" @click="close">
|
||||
<translate>Close</translate>
|
||||
</b-button>
|
||||
<b-button variant="primary" type="submit" @click="doSubmit">
|
||||
<translate>Import from PLS/M3U</translate>
|
||||
</b-button>
|
||||
</template>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import InvisibleSubmitButton from '../components/InvisibleSubmitButton';
|
||||
|
||||
export default {
|
||||
name: 'PlaylistImportModal',
|
||||
components: { InvisibleSubmitButton },
|
||||
data () {
|
||||
return {
|
||||
importPlaylistUrl: null,
|
||||
playlistFile: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
langTitle () {
|
||||
return this.$gettext('Import from PLS/M3U');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open (importPlaylistUrl) {
|
||||
this.playlistFile = null;
|
||||
this.importPlaylistUrl = importPlaylistUrl;
|
||||
|
||||
this.$refs.modal.show();
|
||||
},
|
||||
doSubmit () {
|
||||
let formData = new FormData();
|
||||
formData.append('playlist_file', this.playlistFile);
|
||||
|
||||
axios.post(this.importPlaylistUrl, formData).then((resp) => {
|
||||
if (resp.data.success) {
|
||||
notify('<b>' + resp.data.message + '</b>', 'success', false);
|
||||
} else {
|
||||
notify('<b>' + resp.data.message + '</b>', 'danger', false);
|
||||
}
|
||||
|
||||
this.$emit('relist');
|
||||
this.close();
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
|
||||
let notifyMessage = this.$gettext('An error occurred and your request could not be completed.');
|
||||
notify('<b>' + notifyMessage + '</b>', 'danger', false);
|
||||
|
||||
this.$emit('relist');
|
||||
this.close();
|
||||
});
|
||||
},
|
||||
close () {
|
||||
this.$refs.modal.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -5,7 +5,7 @@ msgstr ""
|
|||
"Generated-By: easygettext\n"
|
||||
"Project-Id-Version: \n"
|
||||
|
||||
#: ./vue/StationPlaylists.vue:131
|
||||
#: ./vue/StationPlaylists.vue:137
|
||||
msgid "# Songs"
|
||||
msgstr ""
|
||||
|
||||
|
@ -26,7 +26,7 @@ msgid "Account List"
|
|||
msgstr ""
|
||||
|
||||
#: ./vue/StationMedia.vue:189
|
||||
#: ./vue/StationPlaylists.vue:128
|
||||
#: ./vue/StationPlaylists.vue:134
|
||||
#: ./vue/StationStreamers.vue:83
|
||||
#: ./vue/station_playlists/PlaylistReorderModal.vue:11
|
||||
#: ./vue/station_streamers/StreamerBroadcastsModal.vue:71
|
||||
|
@ -76,7 +76,7 @@ msgstr ""
|
|||
msgid "Album Art"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:137
|
||||
#: ./vue/StationPlaylists.vue:143
|
||||
msgid "All Playlists"
|
||||
msgstr ""
|
||||
|
||||
|
@ -92,11 +92,12 @@ msgstr ""
|
|||
#: ./vue/station_media/MediaMoveFilesModal.vue:92
|
||||
#: ./vue/station_media/MediaNewDirectoryModal.vue:76
|
||||
#: ./vue/station_playlists/PlaylistEditModal.vue:179
|
||||
#: ./vue/station_playlists/PlaylistImportModal.vue:68
|
||||
#: ./vue/station_streamers/StreamerEditModal.vue:152
|
||||
msgid "An error occurred and your request could not be completed."
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:216
|
||||
#: ./vue/StationPlaylists.vue:228
|
||||
#: ./vue/station_media/MediaToolbar.vue:194
|
||||
msgid "Applying changes..."
|
||||
msgstr ""
|
||||
|
@ -107,10 +108,14 @@ msgstr ""
|
|||
msgid "Artist"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:75
|
||||
#: ./vue/StationPlaylists.vue:79
|
||||
msgid "Auto-Assigned"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/station_playlists/PlaylistImportModal.vue:8
|
||||
msgid "AzuraCast will scan the uploaded file for matches in this station's music library. Media should already be uploaded before running this step. You can re-run this tool as many times as needed."
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/station_media/MediaMoveFilesModal.vue:6
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
@ -143,6 +148,7 @@ msgstr ""
|
|||
#: ./vue/station_media/MediaNewDirectoryModal.vue:15
|
||||
#: ./vue/station_media/MediaRenameModal.vue:16
|
||||
#: ./vue/station_playlists/PlaylistEditModal.vue:18
|
||||
#: ./vue/station_playlists/PlaylistImportModal.vue:17
|
||||
#: ./vue/station_streamers/StreamerBroadcastsModal.vue:20
|
||||
#: ./vue/station_streamers/StreamerEditModal.vue:15
|
||||
msgid "Close"
|
||||
|
@ -170,7 +176,7 @@ msgstr ""
|
|||
msgid "Cue"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:192
|
||||
#: ./vue/StationPlaylists.vue:201
|
||||
msgid "Custom"
|
||||
msgstr ""
|
||||
|
||||
|
@ -203,7 +209,7 @@ msgid "Default"
|
|||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:31
|
||||
#: ./vue/StationPlaylists.vue:232
|
||||
#: ./vue/StationPlaylists.vue:244
|
||||
#: ./vue/StationStreamers.vue:43
|
||||
#: ./vue/StationStreamers.vue:121
|
||||
#: ./vue/station_media/MediaToolbar.vue:53
|
||||
|
@ -225,7 +231,7 @@ msgstr ""
|
|||
msgid "Delete broadcast?"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:233
|
||||
#: ./vue/StationPlaylists.vue:245
|
||||
msgid "Delete playlist?"
|
||||
msgstr ""
|
||||
|
||||
|
@ -242,12 +248,12 @@ msgstr ""
|
|||
msgid "Directory Name"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:161
|
||||
#: ./vue/StationPlaylists.vue:170
|
||||
msgid "Disable"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:78
|
||||
#: ./vue/StationPlaylists.vue:172
|
||||
#: ./vue/StationPlaylists.vue:82
|
||||
#: ./vue/StationPlaylists.vue:181
|
||||
#: ./vue/StationStreamers.vue:30
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
@ -290,7 +296,7 @@ msgstr ""
|
|||
msgid "Edit Streamer"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:162
|
||||
#: ./vue/StationPlaylists.vue:171
|
||||
msgid "Enable"
|
||||
msgstr ""
|
||||
|
||||
|
@ -321,7 +327,7 @@ msgstr ""
|
|||
msgid "Enforce Schedule Times"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:48
|
||||
#: ./vue/StationPlaylists.vue:52
|
||||
msgid "Export %{format}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -351,7 +357,7 @@ msgstr ""
|
|||
msgid "Full Volume"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:177
|
||||
#: ./vue/StationPlaylists.vue:186
|
||||
#: ./vue/station_playlists/form/PlaylistFormBasicInfo.vue:45
|
||||
#: ./vue/station_playlists/form/PlaylistFormBasicInfo.vue:87
|
||||
msgid "General Rotation"
|
||||
|
@ -401,6 +407,12 @@ msgstr ""
|
|||
msgid "If the end time is before the start time, the schedule entry will continue overnight."
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:158
|
||||
#: ./vue/station_playlists/PlaylistImportModal.vue:20
|
||||
#: ./vue/station_playlists/PlaylistImportModal.vue:42
|
||||
msgid "Import from PLS/M3U"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/station_playlists/form/PlaylistFormBasicInfo.vue:96
|
||||
msgid "Include in Automated Assignment"
|
||||
msgstr ""
|
||||
|
@ -425,7 +437,7 @@ msgstr ""
|
|||
msgid "ISRC"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:68
|
||||
#: ./vue/StationPlaylists.vue:72
|
||||
msgid "Jingle Mode"
|
||||
msgstr ""
|
||||
|
||||
|
@ -492,7 +504,7 @@ msgstr ""
|
|||
msgid "Monday"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:143
|
||||
#: ./vue/StationPlaylists.vue:149
|
||||
msgid "More"
|
||||
msgstr ""
|
||||
|
||||
|
@ -566,11 +578,11 @@ msgstr ""
|
|||
msgid "Number of Songs Between Plays"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:184
|
||||
#: ./vue/StationPlaylists.vue:193
|
||||
msgid "Once per %{minutes} Minutes"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:180
|
||||
#: ./vue/StationPlaylists.vue:189
|
||||
msgid "Once per %{songs} Songs"
|
||||
msgstr ""
|
||||
|
||||
|
@ -579,7 +591,7 @@ msgstr ""
|
|||
msgid "Once per Hour"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:188
|
||||
#: ./vue/StationPlaylists.vue:197
|
||||
msgid "Once per Hour (at %{minute})"
|
||||
msgstr ""
|
||||
|
||||
|
@ -630,7 +642,7 @@ msgstr ""
|
|||
msgid "Play/Pause"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:129
|
||||
#: ./vue/StationPlaylists.vue:135
|
||||
msgid "Playlist"
|
||||
msgstr ""
|
||||
|
||||
|
@ -692,7 +704,7 @@ msgstr ""
|
|||
msgid "Remote Playback Buffer (Seconds)"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:63
|
||||
#: ./vue/StationPlaylists.vue:67
|
||||
#: ./vue/station_playlists/form/PlaylistFormSource.vue:80
|
||||
msgid "Remote URL"
|
||||
msgstr ""
|
||||
|
@ -720,7 +732,7 @@ msgstr ""
|
|||
msgid "Rename File/Directory"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:146
|
||||
#: ./vue/StationPlaylists.vue:152
|
||||
msgid "Reorder"
|
||||
msgstr ""
|
||||
|
||||
|
@ -736,7 +748,7 @@ msgstr ""
|
|||
msgid "Replace Album Cover Art"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:149
|
||||
#: ./vue/StationPlaylists.vue:155
|
||||
msgid "Reshuffle"
|
||||
msgstr ""
|
||||
|
||||
|
@ -769,7 +781,7 @@ msgstr ""
|
|||
msgid "Schedule"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:140
|
||||
#: ./vue/StationPlaylists.vue:146
|
||||
#: ./vue/StationStreamers.vue:95
|
||||
msgid "Schedule View"
|
||||
msgstr ""
|
||||
|
@ -784,7 +796,7 @@ msgstr ""
|
|||
msgid "Scheduled Time #%{num}"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:130
|
||||
#: ./vue/StationPlaylists.vue:136
|
||||
msgid "Scheduling"
|
||||
msgstr ""
|
||||
|
||||
|
@ -816,11 +828,15 @@ msgstr ""
|
|||
msgid "Select File"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/station_playlists/PlaylistImportModal.vue:5
|
||||
msgid "Select PLS/M3U File to Import"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/components/DataTable.vue:181
|
||||
msgid "Select this row"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:72
|
||||
#: ./vue/StationPlaylists.vue:76
|
||||
msgid "Sequential"
|
||||
msgstr ""
|
||||
|
||||
|
@ -881,7 +897,7 @@ msgstr ""
|
|||
msgid "Song Title"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:60
|
||||
#: ./vue/StationPlaylists.vue:64
|
||||
msgid "Song-based"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1116,6 +1132,6 @@ msgstr ""
|
|||
msgid "Wednesday"
|
||||
msgstr ""
|
||||
|
||||
#: ./vue/StationPlaylists.vue:177
|
||||
#: ./vue/StationPlaylists.vue:186
|
||||
msgid "Weight"
|
||||
msgstr ""
|
||||
|
|
|
@ -6,10 +6,12 @@ use App\Exception;
|
|||
use App\Exception\NotFoundException;
|
||||
use App\Http\Response;
|
||||
use App\Http\ServerRequest;
|
||||
use App\Radio\PlaylistParser;
|
||||
use Cake\Chronos\Chronos;
|
||||
use InvalidArgumentException;
|
||||
use OpenApi\Annotations as OA;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||
|
||||
class PlaylistsController extends AbstractScheduledEntityController
|
||||
|
@ -285,6 +287,103 @@ class PlaylistsController extends AbstractScheduledEntityController
|
|||
));
|
||||
}
|
||||
|
||||
public function importAction(
|
||||
ServerRequest $request,
|
||||
Response $response,
|
||||
Entity\Repository\StationPlaylistMediaRepository $playlistMediaRepo,
|
||||
$id
|
||||
): ResponseInterface {
|
||||
/** @var Entity\StationPlaylist $playlist */
|
||||
$playlist = $this->getRecord($request->getStation(), $id);
|
||||
|
||||
$files = $request->getUploadedFiles();
|
||||
|
||||
if (empty($files['playlist_file'])) {
|
||||
return $response->withStatus(500)
|
||||
->withJson(new Entity\Api\Error(500, 'No "playlist_file" provided.'));
|
||||
}
|
||||
|
||||
/** @var UploadedFileInterface $file */
|
||||
$file = $files['playlist_file'];
|
||||
|
||||
if (UPLOAD_ERR_OK !== $file->getError()) {
|
||||
return $response->withStatus(500)
|
||||
->withJson(new Entity\Api\Error(500, $file->getError()));
|
||||
}
|
||||
|
||||
$playlistFile = $file->getStream()->getContents();
|
||||
|
||||
$paths = PlaylistParser::getSongs($playlistFile);
|
||||
|
||||
$totalPaths = count($paths);
|
||||
$foundPaths = 0;
|
||||
|
||||
if (!empty($paths)) {
|
||||
$station = $request->getStation();
|
||||
|
||||
// Assemble list of station media to match against.
|
||||
$media_lookup = [];
|
||||
|
||||
$media_info_raw = $this->em->createQuery(/** @lang DQL */ 'SELECT sm.id, sm.path
|
||||
FROM App\Entity\StationMedia sm
|
||||
WHERE sm.station = :station')
|
||||
->setParameter('station', $station)
|
||||
->getArrayResult();
|
||||
|
||||
foreach ($media_info_raw as $row) {
|
||||
$path_hash = md5($row['path']);
|
||||
$media_lookup[$path_hash] = $row['id'];
|
||||
}
|
||||
|
||||
// Run all paths against the lookup list of hashes.
|
||||
$matches = [];
|
||||
|
||||
foreach ($paths as $path_raw) {
|
||||
// De-Windows paths (if applicable)
|
||||
$path_raw = str_replace('\\', '/', $path_raw);
|
||||
|
||||
// Work backwards from the basename to try to find matches.
|
||||
$path_parts = explode('/', $path_raw);
|
||||
for ($i = 1, $iMax = count($path_parts); $i <= $iMax; $i++) {
|
||||
$path_attempt = implode('/', array_slice($path_parts, 0 - $i));
|
||||
$path_hash = md5($path_attempt);
|
||||
|
||||
if (isset($media_lookup[$path_hash])) {
|
||||
$matches[] = $media_lookup[$path_hash];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign all matched media to the playlist.
|
||||
if (!empty($matches)) {
|
||||
$matched_media = $this->em->createQuery(/** @lang DQL */ 'SELECT sm
|
||||
FROM App\Entity\StationMedia sm
|
||||
WHERE sm.station = :station AND sm.id IN (:matched_ids)')
|
||||
->setParameter('station', $station)
|
||||
->setParameter('matched_ids', $matches)
|
||||
->execute();
|
||||
|
||||
$weight = $playlistMediaRepo->getHighestSongWeight($playlist);
|
||||
|
||||
foreach ($matched_media as $media) {
|
||||
$weight++;
|
||||
|
||||
/** @var Entity\StationMedia $media */
|
||||
$playlistMediaRepo->addMediaToPlaylist($media, $playlist, $weight);
|
||||
|
||||
$foundPaths++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
return $response->withJson(new Entity\Api\Status(
|
||||
true,
|
||||
__('Playlist successfully imported; %d of %d files were successfully matched.', $foundPaths, $totalPaths)
|
||||
));
|
||||
}
|
||||
|
||||
protected function viewRecord($record, \App\Http\ServerRequest $request)
|
||||
{
|
||||
if (!($record instanceof $this->entityClass)) {
|
||||
|
@ -312,6 +411,7 @@ class PlaylistsController extends AbstractScheduledEntityController
|
|||
'order' => $router->fromHere('api:stations:playlist:order', ['id' => $record->getId()], [], !$isInternal),
|
||||
'reshuffle' => $router->fromHere('api:stations:playlist:reshuffle', ['id' => $record->getId()], [],
|
||||
!$isInternal),
|
||||
'import' => $router->fromHere('api:stations:playlist:import', ['id' => $record->getId()], [], !$isInternal),
|
||||
'self' => $router->fromHere($this->resourceRouteName, ['id' => $record->getId()], [], !$isInternal),
|
||||
];
|
||||
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
<?php
|
||||
namespace App\Form;
|
||||
|
||||
use App\Customization;
|
||||
use App\Entity;
|
||||
use App\Http\ServerRequest;
|
||||
use App\Radio\PlaylistParser;
|
||||
use App\Config;
|
||||
use App\Session\Flash;
|
||||
use AzuraForms\Field\Markup;
|
||||
use Cake\Chronos\Chronos;
|
||||
use DateTimeZone;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
class StationPlaylistForm extends EntityForm
|
||||
{
|
||||
protected Entity\Repository\StationPlaylistMediaRepository $playlistMediaRepo;
|
||||
|
||||
public function __construct(
|
||||
EntityManager $em,
|
||||
Entity\Repository\StationPlaylistMediaRepository $playlistMediaRepo,
|
||||
Serializer $serializer,
|
||||
ValidatorInterface $validator,
|
||||
Config $config,
|
||||
Customization $customization
|
||||
) {
|
||||
$form_config = $config->get('forms/playlist', [
|
||||
'customization' => $customization,
|
||||
]);
|
||||
|
||||
parent::__construct($em, $serializer, $validator, $form_config);
|
||||
|
||||
$this->entityClass = Entity\StationPlaylist::class;
|
||||
$this->playlistMediaRepo = $playlistMediaRepo;
|
||||
}
|
||||
|
||||
public function process(ServerRequest $request, $record = null)
|
||||
{
|
||||
// Set the "Station Time Zone" field.
|
||||
$station = $request->getStation();
|
||||
$station_tz = $station->getTimezone();
|
||||
|
||||
$now_station = Chronos::now(new DateTimeZone($station_tz))->toIso8601String();
|
||||
|
||||
$tz_string = __('This station\'s time zone is currently %s.', '<b>' . $station_tz . '</b>')
|
||||
. '<br>'
|
||||
. __('The current time in the station\'s time zone is %s.',
|
||||
'<b><time data-content="' . $now_station . '"></time></b>');
|
||||
|
||||
/** @var Markup $tz_field */
|
||||
$tz_field = $this->fields['station_time_zone'];
|
||||
$tz_field->setAttribute('markup', $tz_string);
|
||||
|
||||
// Resume regular record processing.
|
||||
$record = parent::process($request, $record);
|
||||
|
||||
if ($record instanceof Entity\StationPlaylist) {
|
||||
$files = $request->getUploadedFiles();
|
||||
|
||||
/** @var UploadedFileInterface $import_file */
|
||||
$import_file = $files['import'];
|
||||
if (UPLOAD_ERR_OK === $import_file->getError()) {
|
||||
$matches = $this->_importPlaylist($record, $import_file);
|
||||
|
||||
if (is_int($matches)) {
|
||||
$request->getFlash()->addMessage('<b>' . __('Existing playlist imported.') . '</b><br>' . __('%d song(s) were imported into the playlist.',
|
||||
$matches), Flash::INFO);
|
||||
}
|
||||
}
|
||||
|
||||
$record->setQueue(null);
|
||||
$this->em->persist($record);
|
||||
$this->em->flush($record);
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
protected function _importPlaylist(Entity\StationPlaylist $playlist, UploadedFileInterface $playlist_file)
|
||||
{
|
||||
$station_id = $this->station->getId();
|
||||
|
||||
$playlist_raw = (string)$playlist_file->getStream();
|
||||
if (empty($playlist_raw)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$paths = PlaylistParser::getSongs($playlist_raw);
|
||||
|
||||
if (empty($paths)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Assemble list of station media to match against.
|
||||
$media_lookup = [];
|
||||
|
||||
$media_info_raw = $this->em->createQuery(/** @lang DQL */ 'SELECT sm.id, sm.path
|
||||
FROM App\Entity\StationMedia sm
|
||||
WHERE sm.station_id = :station_id')
|
||||
->setParameter('station_id', $station_id)
|
||||
->getArrayResult();
|
||||
|
||||
foreach ($media_info_raw as $row) {
|
||||
$path_hash = md5($row['path']);
|
||||
$media_lookup[$path_hash] = $row['id'];
|
||||
}
|
||||
|
||||
// Run all paths against the lookup list of hashes.
|
||||
$matches = [];
|
||||
|
||||
foreach ($paths as $path_raw) {
|
||||
// De-Windows paths (if applicable)
|
||||
$path_raw = str_replace('\\', '/', $path_raw);
|
||||
|
||||
// Work backwards from the basename to try to find matches.
|
||||
$path_parts = explode('/', $path_raw);
|
||||
for ($i = 1, $iMax = count($path_parts); $i <= $iMax; $i++) {
|
||||
$path_attempt = implode('/', array_slice($path_parts, 0 - $i));
|
||||
$path_hash = md5($path_attempt);
|
||||
|
||||
if (isset($media_lookup[$path_hash])) {
|
||||
$matches[] = $media_lookup[$path_hash];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign all matched media to the playlist.
|
||||
if (!empty($matches)) {
|
||||
$matched_media = $this->em->createQuery(/** @lang DQL */ 'SELECT sm
|
||||
FROM App\Entity\StationMedia sm
|
||||
WHERE sm.station_id = :station_id AND sm.id IN (:matched_ids)')
|
||||
->setParameter('station_id', $station_id)
|
||||
->setParameter('matched_ids', $matches)
|
||||
->execute();
|
||||
|
||||
$weight = $this->playlistMediaRepo->getHighestSongWeight($playlist);
|
||||
|
||||
foreach ($matched_media as $media) {
|
||||
$weight++;
|
||||
|
||||
/** @var Entity\StationMedia $media */
|
||||
$this->playlistMediaRepo->addMediaToPlaylist($media, $playlist, $weight);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
return count($matches);
|
||||
}
|
||||
}
|
|
@ -51,7 +51,7 @@
|
|||
"dist/material.js": "dist/material-df68dbf23f.js",
|
||||
"dist/radio_player.js": "dist/radio_player-f88abea4e8.js",
|
||||
"dist/station_media.js": "dist/station_media-539908ba2c.js",
|
||||
"dist/station_playlists.js": "dist/station_playlists-a5de781fdd.js",
|
||||
"dist/station_playlists.js": "dist/station_playlists-f552dce7d5.js",
|
||||
"dist/station_streamers.js": "dist/station_streamers-2e160bdd93.js",
|
||||
"dist/vue_gettext.js": "dist/vue_gettext-5797c82c60.js",
|
||||
"dist/webcaster.js": "dist/webcaster-23f4c0f3cf.js",
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue