Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#512 Added Feature to Rename Artist Names #532

Merged
merged 10 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion airsonic-main/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.4.0</version>
<version>9.0.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
This file is part of Airsonic.

Airsonic is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Airsonic is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Airsonic. If not, see <http://www.gnu.org/licenses/>.

Copyright 2024 (C) Y.Tory
*/
package org.airsonic.player.controller;

import org.airsonic.player.domain.MediaFile;
import org.airsonic.player.service.MediaFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import jakarta.servlet.http.HttpServletRequest;

/**
* Controller for updating Media file
*
* @author Y. Tory
*/
@Controller
@RequestMapping({"/editMediaDir"})
public class EditMediaDirController {

@Autowired
private MediaFileService mediaFileService;

@PostMapping
protected ModelAndView handleRequestInternal(HttpServletRequest request) throws Exception {
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
String action = request.getParameter("action");

MediaFile mediaFile = mediaFileService.getMediaFile(id);

if ("editMediaDirTitle".equals(action) && mediaFile != null && mediaFile.isDirectory()) {
mediaFile.setTitle(request.getParameter("mediaDirTitle"));
mediaFileService.updateMediaFile(mediaFile);
}

String url = "main.view?id=" + id;
return new ModelAndView(new RedirectView(url));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ private List<SearchResultArtist> createArtistResults(SearchResult artists) {
Map<Pair<String, Integer>, SearchResultArtist> artistMap = new LinkedHashMap<>();

artists.getMediaFiles().stream().forEach(m -> {
String artist = Optional.ofNullable(m.getArtist())
String artist = Optional.ofNullable(m.getTitle())
.or(() -> Optional.ofNullable(m.getArtist()))
.or(() -> Optional.ofNullable(m.getAlbumArtist()))
.orElse("(Unknown)");
SearchResultArtist artistResult = artistMap.computeIfAbsent(Pair.of(artist, m.getFolder().getId()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ private <T extends ArtistID3> T createJaxbArtist(T jaxbArtist, org.airsonic.play
private org.subsonic.restapi.Artist createJaxbArtist(MediaFile artist, String username) {
org.subsonic.restapi.Artist result = new org.subsonic.restapi.Artist();
result.setId(String.valueOf(artist.getId()));
result.setName(artist.getArtist());
result.setName(artist.getTitle() != null ? artist.getTitle() : artist.getArtist());
Instant starred = mediaFileService.getMediaFileStarredDate(artist, username);
result.setStarred(jaxbWriter.convertDate(starred));
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ public SecurityFilterChain webSecurityFilterChain(HttpSecurity http, Authenticat
"/databaseSettings*", "/transcodeSettings*", "/rest/startScan*").hasRole("ADMIN")
.requestMatchers("/deletePlaylist*", "/savePlaylist*").hasRole("PLAYLIST").requestMatchers("/download*").hasRole("DOWNLOAD")
.requestMatchers("/upload*").hasRole("UPLOAD").requestMatchers("/createShare*").hasRole("SHARE")
.requestMatchers("/changeCoverArt*", "/editTags*").hasRole("COVERART").requestMatchers("/setMusicFileInfo*").hasRole("COMMENT")
.requestMatchers("/changeCoverArt*", "/editTags*", "/editMediaDir*").hasRole("COVERART").requestMatchers("/setMusicFileInfo*").hasRole("COMMENT")
.requestMatchers("/podcastReceiverAdmin*", "/podcastEpisodes*").hasRole("PODCAST")
.requestMatchers("/**").hasRole("USER").anyRequest().authenticated())
.formLogin((login) -> login
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;

import java.awt.Graphics2D;
Expand Down Expand Up @@ -231,9 +232,13 @@ public BufferedImage createImage(CoverArtRequest coverArtRequest, int size) {
return ImageUtil.scaleToSquare(bimg, size);
}
}
LOG.warn("Failed to process cover art {}: {} failed", coverArt, reason);
LOG.warn("Failed to process cover art {}: {} failed", coverArt.getFullPath(), reason);
} catch (IIOException x) {
LOG.warn("Failed to process cover art {}: {}", coverArt.getFullPath(), "Bad image file");
LOG.debug(x.getMessage(), x);
} catch (Throwable x) {
LOG.warn("Failed to process cover art {}", coverArt, x);
LOG.warn("Failed to process cover art {}", coverArt.getFullPath());
LOG.debug(x.getMessage(), x);
}
}
return createAutoCover(coverArtRequest, size, size);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ public Document createAlbumDocument(MediaFile mediaFile, MusicFolder musicFolder
public Document createArtistDocument(MediaFile mediaFile, MusicFolder musicFolder) {
Document doc = new Document();
fieldId.accept(doc, mediaFile.getId());
fieldWords.accept(doc, FieldNames.ARTIST, mediaFile.getArtist());
fieldWords.accept(doc, FieldNames.ARTIST, mediaFile.getName());
fieldFolderPath.accept(doc, musicFolder.getPath().toString());
return doc;
}
Expand Down
45 changes: 43 additions & 2 deletions airsonic-main/src/main/resources/templates/mediaMain.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,14 @@
#mediaDirTitle {
font-size: 150%;
padding: 0;
border: 0;
border: 1px solid transparent;
margin: 0;
}
#mediaDirTitleForm input[type="text"] {
font-size: 150%;
padding: 0;
margin: 0;
}
#mediaDirController span {
margin-right: 0.3em;
}
Expand Down Expand Up @@ -122,6 +127,12 @@
// comments
updateComments();

// directory name
// only for artist directories
if (mediaDir.contentType == 'artist') {
updateMediaDirTitle();
}

// search components
if (mediaDir.album == null && mediaDir.artist == null) {
$('.external-search').hide();
Expand Down Expand Up @@ -274,6 +285,11 @@
$('#commentForm input[name="id"]').val(mediaDir.id);
}

function updateMediaDirTitle() {
$('#mediaDirTitleForm input[name="mediaDirTitle"]').val(mediaDir.title);
$('#mediaDirTitleForm input[name="id"]').val(mediaDir.id);
}

function subDirsHeading() {
var subDirHeading = 'Subdirectories';

Expand Down Expand Up @@ -839,6 +855,8 @@

if (artistInfo.artistBio && artistInfo.artistBio.biography) {
$("#artistBio").html(artistInfo.artistBio.biography);
} else {
$("#artistBio").empty();
}

this.topSongs = artistInfo.topSongs;
Expand All @@ -861,6 +879,9 @@
var ancestor = mediaDir.ancestors[i];
ancestors.append(feather.icons.folder.toSvg({class: 'feather-sm'}));
ancestors.append($("<a>").attr("href", "#").attr("onclick", "getMediaDirectory(" + ancestor.id + ")").text(ancestor.title));
if (ancestor.title != ancestor.artist && ancestor.entryType == 'DIRECTORY' && ancestor.artist != null) {
ancestors.append(" (").append(ancestor.artist).append(")");
}
ancestors.append(" » ");
}
}
Expand All @@ -873,6 +894,9 @@
$("#mediaDirTitle").append(feather.icons.folder.toSvg());
}
$("#mediaDirTitle").append(mediaDir.title);
if (mediaDir.title != mediaDir.artist && mediaDir.contentType == 'artist') {
$("#mediaDirTitle").append(" (").append(mediaDir.artist).append(")");
}
}

function toggleMediaDirStar(status) {
Expand Down Expand Up @@ -956,6 +980,11 @@
$("#commentForm").toggle();
$("#comment").toggle();
}

function toggleMediaDirTitle() {
$("#mediaDirTitleForm").toggle();
$("#mediaDirTitle").toggle();
}

/** Albums Only **/
// actionSelected() is invoked when the users selects from the "More actions..." combo box.
Expand Down Expand Up @@ -1149,7 +1178,14 @@
<div style="float:left" id="mediaInfo">
<p id="ancestors" style="vertical-align: middle"></p>
<p id="mediaDirTitle"></p>
<div id="avgRating"></div>
<form method="post" th:action="@{/editMediaDir}" style="display:none" id="mediaDirTitleForm">
<i data-feather="folder"></i>
<input type="hidden" name="action" value="editMediaDirTitle">
<input type="hidden" id="mediaFileId" name="id" value="">
<input type="text" name="mediaDirTitle"></input>
<input type="submit" th:value="#{common.save}">
</form>
<div id="avgRating"></div>
</div>

<div style="float:right;padding-right:1em">
Expand All @@ -1175,6 +1211,10 @@
<span><a href="javascript:editTagsPage()"><i data-feather="edit" role="img" th:aria-label="#{main.tags}" th:title="#{main.tags}"></i></a></span>
</div>

<div th:if="${model.user.coverArtRole}" class="pagetype-dependent type-artist" style="display: inline-block;">
<span><a href="javascript:toggleMediaDirTitle()"><i data-feather="edit" role="img" th:aria-label="#{common.edit}" th:title="#{common.edit}"></i></a></span>
</div>

<span th:if="${model.user.commentRole}"><a href="javascript:toggleComment()"><i data-feather="message-square" role="img" th:aria-label="#{main.comment}" th:title="#{main.comment}"></i></a></span>
</th:block>
<div th:if=${model.user.shareRole} class="detail pagetype-dependent type-album" style="display: inline-block;">
Expand Down Expand Up @@ -1205,6 +1245,7 @@
</form>
</div>


<div class="tableSpacer"></div>

<table id="filesTable" class="music indent hover nowrap stripe compact" th:classappend="${model.visibility.headerVisible != true?'hide-table-header':''}" style="width: 100%;">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,11 @@ <h2>
<th class="fit center" th:text="#{top.status}"></th>
<th class="truncate left" th:text="#{sharesettings.description}"></th>
</tr>
<tr th:each="episode, loopStatus: ${command.episodes}">
<input type="hidden" th:field="${command.episodes[__${loopStatus.index}__].id}" form="form-selected"/>
<input type="hidden" th:field="${command.episodes[__${loopStatus.index}__].status}" form="form-selected"/>
<tr th:each="episode: ${command.episodes}">
<input type="hidden" th:field="${command.episodes[__${episodeStat.index}__].id}" form="form-selected"/>
<input type="hidden" th:field="${command.episodes[__${episodeStat.index}__].status}" form="form-selected"/>

<td class="fit center"><input type="checkbox" th:field="${command.episodes[__${loopStatus.index}__].selected}" form="form-selected" onchange="countSelected()"/></td>
<td class="fit center"><input type="checkbox" th:field="${command.episodes[__${episodeStat.index}__].selected}" form="form-selected" onchange="countSelected()"/></td>

<th:block th:if="${#strings.isEmpty(episode.mediaFile?.id) or episode.status.name ne 'COMPLETED'}">
<td colspan="4"></td>
Expand Down
8 changes: 3 additions & 5 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@
## Contents

- [First start](./first_start/README.md)

- [WebUI](./webui/README.md)
- [Podcast](./webui/podcast.md)

- [Media](./webui/media.md)
- [Podcast](./webui/podcast.md)
- [Configures](./configures/README.md)
- [Detail Configuration](./configures/detail.md)
- Proxy
- [Prerequisites](./proxy/README.md)
- [Apache](./proxy/apache.md)

- Media
- [Rule](./media/rule.md)
- [Cover Art/ Artist Image](./media/coverart.md)
- [Jukebox](./media/jukebox.md)

- [TroubleShooting](./troubleshooting.md)
Binary file added docs/figures/webui-media-artist-edit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/figures/webui-media-artist.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions docs/media/rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Rule

Airsonic Advanced categorizes directories and files into Album, Artist, Song, and Video using the following logic:

| Type | Description |
| --- | --- |
| Song | A single audio file. If it has the specified extensions which defined in the `Settings` > `General` > `Music files` field, it is considered a song. |
| Video | A single video file. If it has the specified extensions which defined in the `Settings` > `General` > `Video files` field, it is considered a video. |
| Album | A parent directory containing at least one Song or Video. |
| Artist | A parent directory containing albums. If it contains songs or videos directly, it is considered an album. |


Therefore, it is understood as follows:

```
.
├── Artist1
│ ├── Album1
│ │ ├── Song1.flac
│ │ ├── Song2.mp3
│ │ └── Folder.jpg
│ ├── Artist2
│ │ ├── Album2
│ │ │ ├── Song3.ogg
│ │ │ ├── Song4.ogg
│ │ │ └── Folder.jpg
│ │ └──Album3
│ │ ├── Song5.mp3
│ │ ├── Song6.mp3
│ │ └── Folder.jpeg
├── Album2
│ ├── Song7.mp3
│ ├── Video1.mp4
│ └── Folder.jpg
```
1 change: 1 addition & 0 deletions docs/webui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ This document describes the features of the Airsonic Advanced web UI.

## Contents

- [Media](./media.md)
- [Podcast](./podcast.md)
39 changes: 39 additions & 0 deletions docs/webui/media.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Media

The media section of the web UI allows you to browse and play your music.

## Artist

### Artist View

![webui-media-artist](../figures/webui-media-artist.png)

| Number | Description | Role |
| --- | --- | --- |
| 1 | Parent Directory Links. You can click on the name to navigate to the parent directories. | User |
| 2 | Artist name. If you edit the artist name, the original name will be displayed in `( )`. | User |
| 3 | Star. You can click on the star to add the artist to your favorites. | User |
| 4 | Play. You can click on the play button to play all songs by the artist. | User |
| 5 | Shuffle. You can click on the shuffle button to shuffle all songs by the artist. | User |
| 6 | Add to player. You can click on the add to player button to add all songs by the artist to a player. | User |
| 7 | Edit artist. You can click on the edit artist button to edit the artist name. | CoverArt |
| 8 | Comment. You can click on the comment button to add a comment to the artist. | Comment |
| 9 | List view. You can click on the list view button to switch to the list view. | User |
| 10 | Grid view. You can click on the grid view button to switch to the grid view. | User |
| 11 | Albums. You can see the albums by the artist. | User |
| 12 | Artist Information. You can see the artist information from Last.fm. | User |

### Edit Artist

![webui-media-artist-edit](../figures/webui-media-artist-edit.png)

1. Click on the edit artist button.
2. Edit the artist name.
3. Click on the save button.

If you want to cancel, click on the Edit Artist button again.
To update the search results, performing a library rescan is necessary, but a full scan is unnecessary.

## Related Documentation

- [Media/Rule](../media/rule.md)
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.44.0</version>
<version>3.45.0</version>
</dependency>
<dependency>
<groupId>com.google.j2objc</groupId>
Expand Down Expand Up @@ -280,7 +280,7 @@
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>9.2.0</version>
<version>10.0.1</version>
<inherited>true</inherited>
<configuration>
<cveValidForHours>24</cveValidForHours>
Expand Down