Skip to content

Commit

Permalink
Add optional "strict" mode for the history filters of the smart playlist
Browse files Browse the repository at this point in the history
In the "strict" mode the history filters are interpreted in absolute sense:
E.g. requesting 100 "recently played" songs returns 100 most recently played
songs with no element of randomness. The non-strict mode works as previously
and returns instead 100 songs among the most recently played ones.

refs #1099
  • Loading branch information
paulijar committed Jan 14, 2024
1 parent b4bb53b commit 5825c03
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 13 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
- MusicBrainz link from Last.fm to the artist/album/track details pane, when available
- Filters "Recently added" and "Not recently added" for the smart playlist
[#1098](https://github.com/owncloud/music/issues/1098)
- Optional "strict" mode for the history filters of the smart playlist
[#1099](https://github.com/owncloud/music/issues/1099)

### Changed
- Ampache API:
Expand Down
24 changes: 19 additions & 5 deletions css/music-sidebar.css
Original file line number Diff line number Diff line change
Expand Up @@ -375,21 +375,21 @@
#app-sidebar #smartlist-filters .chosen-choices .search-choice {
margin-top: 6px;
background-color: var(--color-background-dark, #eee);
color: var(--color-main-text, #333);
background-image: unset;
color: var(--color-main-text, #333);
background-image: unset;
}

#app-sidebar #smartlist-filters .chosen-drop {
background-color: var(--color-main-background, #fff);
}

#app-sidebar #smartlist-filters .chosen-container .chosen-results {
color: var(--color-main-text, #444);
color: var(--color-main-text, #444);
}

#app-sidebar #smartlist-filters .chosen-container .chosen-results li.no-results {
color: var(--color-main-text, #333);
background: var(--color-background-dark, #eee);
color: var(--color-main-text, #333);
background: var(--color-background-dark, #eee);
opacity: .5;
}

Expand All @@ -398,6 +398,20 @@
opacity: .5;
}

#app-sidebar #smartlist-filters #filter-history {
width: calc(100% - 178px);
}

#app-sidebar #smartlist-filters #filter-history-strict-label {
width: 50px;
text-align: right;
}

#app-sidebar #smartlist-filters #filter-history-strict {
vertical-align: middle;
width: 16px;
}

#app-sidebar #smartlist-filters #update-button {
width: 100%;
}
15 changes: 10 additions & 5 deletions lib/BusinessLayer/PlaylistBusinessLayer.php
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,19 @@ public function getDuration(int $playlistId, string $userId) : int {
* Generate and return a playlist matching the given criteria. The playlist is not persisted.
*
* @param string|null $history One of: 'recently-played', 'not-recently-played', 'often-played', 'rarely-played', 'recently-added', 'not-recently-added'
* @param bool $historyStrict In the "strict" mode, there's no element of randomness when applying the history filter and e.g.
* 'recently-played' meas "The most recently played" instead of "Among the most recently played"
* @param int[] $genres Array of genre IDs
* @param int[] $artists Array of artist IDs
* @param int|null $fromYear Earliest release year to include
* @param int|null $toYear Latest release year to include
* @param int $size Size of the playlist to generate, provided that there are enough matching tracks
* @param string $userId the name of the user
*/
public function generate(?string $history, array $genres, array $artists, ?int $fromYear, ?int $toYear, int $size, string $userId) : Playlist {
public function generate(
?string $history, bool $historyStrict, array $genres, array $artists,
?int $fromYear, ?int $toYear, int $size, string $userId) : Playlist {

$now = new \DateTime();
$nowStr = $now->format(PlaylistMapper::SQL_DATE_FORMAT);

Expand All @@ -205,13 +210,13 @@ public function generate(?string $history, array $genres, array $artists, ?int $
$playlist->setUserId($userId);

list('sortBy' => $sortBy, 'invert' => $invertSort) = self::sortRulesForHistory($history);
$limit = ($sortBy === SortBy::None) ? null : $size * 4;
$limit = ($sortBy === SortBy::None) ? null : ($historyStrict ? $size : $size * 4);

$tracks = $this->trackMapper->findAllByCriteria($genres, $artists, $fromYear, $toYear, $sortBy, $invertSort, $userId, $limit);

if ($sortBy !== SortBy::None) {
// When generating by play-rate, use a pool of tracks at maximum twice the size of final list. However, don't use
// more than half of the matching tracks unless that is required to satisfy the required list size.
if ($sortBy !== SortBy::None && !$historyStrict) {
// When generating by non-strict history, use a pool of tracks at maximum twice the size of final list.
// However, don't use more than half of the matching tracks unless that is required to satisfy the required list size.
$poolSize = max($size, \count($tracks) / 2);
$tracks = \array_slice($tracks, 0, $poolSize);
}
Expand Down
11 changes: 9 additions & 2 deletions lib/Controller/PlaylistApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,33 +163,40 @@ private function toFullTree($playlist) {
* @NoAdminRequired
* @NoCSRFRequired
*/
public function generate(?bool $useLatestParams, ?string $history, ?string $genres, ?string $artists, ?int $fromYear, ?int $toYear, int $size=100) {
public function generate(
?bool $useLatestParams, ?string $history, ?string $genres, ?string $artists,
?int $fromYear, ?int $toYear, int $size=100, string $historyStrict='false') {

if ($useLatestParams) {
$history = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_history') ?: null;
$genres = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_genres') ?: null;
$artists = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_artists') ?: null;
$fromYear = (int)$this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_from_year') ?: null;
$toYear = (int)$this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_to_year') ?: null;
$size = (int)$this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_size', 100);
$historyStrict = $this->configManager->getUserValue($this->userId, $this->appName, 'smartlist_history_strict', 'false');
} else {
$this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_history', $history ?? '');
$this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_genres', $genres ?? '');
$this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_artists', $artists ?? '');
$this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_from_year', (string)$fromYear);
$this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_to_year', (string)$toYear);
$this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_size', (string)$size);
$this->configManager->setUserValue($this->userId, $this->appName, 'smartlist_history_strict', $historyStrict);
}
$historyStrict = \filter_var($historyStrict, FILTER_VALIDATE_BOOLEAN);

// ensure the artists and genres contain only valid IDs
$genres = $this->genreBusinessLayer->findAllIds($this->userId, self::toIntArray($genres));
$artists = $this->artistBusinessLayer->findAllIds($this->userId, self::toIntArray($artists));

$playlist = $this->playlistBusinessLayer->generate(
$history, $genres, $artists, $fromYear, $toYear, $size, $this->userId);
$history, $historyStrict, $genres, $artists, $fromYear, $toYear, $size, $this->userId);
$result = $playlist->toAPI();

$result['params'] = [
'history' => $history ?: null,
'historyStrict' => $historyStrict,
'genres' => \implode(',', $genres) ?: null,
'artists' => \implode(',', $artists) ?: null,
'fromYear' => $fromYear ?: null,
Expand Down
4 changes: 3 additions & 1 deletion templates/partials/sidebar/smartlistfilters.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
</select>
</div>
<div title="{{ 'Note that this selection makes any difference only when the library has more than requested number of matches' | translate }}">
<div title="{{ 'Note that this selection makes any difference only when the library has more than the requested number of matches. In the strict mode, only the best matching songs are included with no element of randomness.' | translate }}">
<label for="filter-history" translate>History</label>
<select id="filter-history" ng-model="smartListParams.history">
<option value=""></option>
Expand All @@ -42,6 +42,8 @@
<option value="recently-added" translate>Recently added</option>
<option value="not-recently-added" translate>Not recently added</option>
</select>
<label for="filter-history-strict" id="filter-history-strict-label" translate>Strict</label>
<input id="filter-history-strict" type="checkbox" ng-model="smartListParams.historyStrict" />
</div>
<div><button id="update-button" ng-click="onUpdateButton()" ng-disabled="!fieldsValid" translate>Update</button></div>
Expand Down

0 comments on commit 5825c03

Please sign in to comment.