Compare commits

...

4 Commits

Author SHA1 Message Date
Skulking Scavenger 74094447b9
Merge 933d7da3ee into fbb266adc2 2024-03-15 06:07:46 +01:00
Clementine Buildbot fbb266adc2 Automatic merge of translations from Transifex 2024-03-15 02:29:14 +00:00
Clementine Buildbot 9638ac70b3 Automatic merge of translations from Transifex 2024-03-13 02:29:55 +00:00
Klaus von Nachtstein 933d7da3ee Add Bulk Import option for playlists 2023-04-28 00:48:45 -05:00
12 changed files with 201 additions and 34 deletions

View File

@ -1241,6 +1241,7 @@ void Playlist::UpdateItems(const SongList& songs) {
}
}
}
connect(backend_,SIGNAL(PlaylistSaved(int)),SIGNAL(PlaylistSongsLoaded(int)));
Save();
}

View File

@ -373,6 +373,9 @@ class Playlist : public QAbstractListModel {
// Signals that the queue has changed, meaning that the remaining queued
// items should update their position.
void QueueChanged();
// Signals that the playlist has finished asynchronously loading songs
// the playlist can now be safely processed or closed
void PlaylistSongsLoaded(int id);
private:
void SetCurrentIsPaused(bool paused);

View File

@ -343,6 +343,7 @@ void PlaylistBackend::SavePlaylist(int playlist, const PlaylistItemList& items,
if (db_->CheckErrors(update)) return;
transaction.Commit();
emit PlaylistSaved(playlist);
}
int PlaylistBackend::CreatePlaylist(const QString& name,

View File

@ -79,7 +79,9 @@ class PlaylistBackend : public QObject {
public slots:
void SavePlaylist(int playlist, const PlaylistItemList& items,
int last_played, smart_playlists::GeneratorPtr dynamic);
signals:
void PlaylistSaved(int id);
private:
struct NewSongFromQueryState {
QHash<QString, SongList> cached_cues_;

View File

@ -24,16 +24,22 @@
#include <QPainter>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QFileDialog>
#include <QDirIterator>
#include <iostream>
#include "core/application.h"
#include "core/logging.h"
#include "core/player.h"
#include "core/utilities.h"
#include "playlist.h"
#include "playlistlistmodel.h"
#include "playlistmanager.h"
#include "ui/iconloader.h"
#include "ui_playlistlistcontainer.h"
#include "playlistparsers/playlistparser.h"
const char* PlaylistListContainer::kSettingsGroup = "PlaylistList";
/* This filter proxy will:
- Accept all ancestors if at least a single child matches
@ -146,6 +152,7 @@ PlaylistListContainer::PlaylistListContainer(QWidget* parent)
action_new_folder_(new QAction(this)),
action_remove_(new QAction(this)),
action_save_playlist_(new QAction(this)),
action_bulk_import_playlists_(new QAction(this)),
model_(new PlaylistListModel(this)),
proxy_(new PlaylistListFilterProxyModel(this)),
loaded_icons_(false),
@ -158,16 +165,20 @@ PlaylistListContainer::PlaylistListContainer(QWidget* parent)
action_remove_->setText(tr("Delete"));
action_save_playlist_->setText(
tr("Save playlist", "Save playlist menu action."));
action_bulk_import_playlists_->setText(tr("Bulk Import Playlists"));
ui_->new_folder->setDefaultAction(action_new_folder_);
ui_->remove->setDefaultAction(action_remove_);
ui_->save_playlist->setDefaultAction(action_save_playlist_);
ui_->bulk_import_playlists->setDefaultAction(action_bulk_import_playlists_);
connect(action_new_folder_, SIGNAL(triggered()), SLOT(NewFolderClicked()));
connect(action_remove_, SIGNAL(triggered()), SLOT(DeleteClicked()));
connect(action_save_playlist_, SIGNAL(triggered()), SLOT(SavePlaylist()));
connect(model_, SIGNAL(PlaylistPathChanged(int, QString)),
SLOT(PlaylistPathChanged(int, QString)));
connect(action_bulk_import_playlists_, SIGNAL(triggered()), SLOT(BulkImportPlaylists()));
proxy_->setSourceModel(model_);
proxy_->setDynamicSortFilter(true);
@ -182,6 +193,9 @@ PlaylistListContainer::PlaylistListContainer(QWidget* parent)
connect(ui_->search, SIGNAL(textChanged(QString)),
SLOT(SearchTextEdited(QString)));
//access to global settings using the QSettings object. Use this to get data about last opened folder etc.
settings_.beginGroup(kSettingsGroup);
}
PlaylistListContainer::~PlaylistListContainer() { delete ui_; }
@ -198,6 +212,8 @@ void PlaylistListContainer::showEvent(QShowEvent* e) {
action_remove_->setIcon(IconLoader::Load("edit-delete", IconLoader::Base));
action_save_playlist_->setIcon(
IconLoader::Load("document-save", IconLoader::Base));
action_bulk_import_playlists_->setIcon(
IconLoader::Load("document-open-folder", IconLoader::Base));
model_->SetIcons(IconLoader::Load("view-media-playlist", IconLoader::Base),
IconLoader::Load("folder", IconLoader::Base));
@ -330,6 +346,86 @@ void PlaylistListContainer::SavePlaylist() {
}
}
/*
Open filepicker and Use QDirIterator to recursively find subdirectories
within the chosen directory. create an empty
*/
void PlaylistListContainer::BulkImportPlaylists() {
QString base_path(settings_.value("last_path", Utilities::GetConfigPath(
Utilities::Path_DefaultMusicLibrary)).toString());
base_path = QFileDialog::getExistingDirectory(this, tr("Add directory..."), base_path);
if (base_path.isNull()) {
return;
}
//Create Root Folder
QFileInfo child_info(base_path);
QStandardItem* root_folder = model_->NewFolder(child_info.fileName());
model_->invisibleRootItem()->appendRow(root_folder);
bulk_imported_id_list_ = new QList<int>();
bulk_imported_count_ = 0;
RecursivelyCreateSubfolders(root_folder, base_path, child_info.baseName());
settings_.setValue("last_path", base_path);
}
void PlaylistListContainer::RecursivelyCreateSubfolders(QStandardItem* parent_folder, QString path, QString ui_path){
QStringList filters;
filters = app_->playlist_manager()->parser()->file_extensions();
for(int i=0; i<filters.count(); i++){
filters[i] = "*." + filters[i];
}
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot);
while(it.hasNext()) {
QString child(it.next());
QFileInfo child_info(child);
if (child_info.isDir()) {
QStandardItem* folder = model_->NewFolder(child_info.fileName());
parent_folder->appendRow(folder);
RecursivelyCreateSubfolders(folder, child_info.absoluteFilePath(), ui_path + "/" + child_info.baseName());
}else if(child_info.isFile()){
if(QDir::match(filters, child_info.fileName())){
bulk_imported_count_++;
int id = app_->playlist_manager()->LoadBulkPlaylists(child, ui_path);
Playlist* playlist = app_->playlist_manager()->playlist(id);
connect(playlist, SIGNAL(PlaylistSongsLoaded(int)), SLOT(BulkImportPlaylistsCallback(int)));
PlaylistPathChanged(id, ui_path);
}
}
}
}
/*
* After the bulk imported playlist has asynchronously had its songs
* loaded, add it to the list of finished playlists
* once the quota has been filled, close the temporary tabs
*/
void PlaylistListContainer::BulkImportPlaylistsCallback (int id){
if(bulk_imported_count_ == 0){
return;
}
if(bulk_imported_id_list_->contains(id)){
return;
}
app_->playlist_manager()->BulkImportPlaylistsCallback(id);
const QString& name = app_->playlist_manager()->GetPlaylistName(id);
AddPlaylist(id, name, true);
bulk_imported_id_list_->append(id);
if(bulk_imported_id_list_->count() == bulk_imported_count_){
//clean up tabs
for(qsizetype i =0; i < bulk_imported_id_list_->count(); i++){
app_->playlist_manager()->Close(bulk_imported_id_list_->at(i));
}
app_->playlist_manager()->ChangePlaylistOrder(app_->playlist_manager()->GetAllPlaylistIds());
bulk_imported_count_ = 0;
bulk_imported_id_list_ = new QList<int>();
}
}
void PlaylistListContainer::PlaylistFavoriteStateChanged(int id,
bool favorite) {
if (favorite) {
@ -473,6 +569,7 @@ void PlaylistListContainer::contextMenuEvent(QContextMenuEvent* e) {
menu_->addAction(action_remove_);
menu_->addSeparator();
menu_->addAction(action_save_playlist_);
menu_->addAction(action_bulk_import_playlists_);
}
menu_->popup(e->globalPos());
}

View File

@ -18,6 +18,7 @@
#ifndef PLAYLISTLISTCONTAINER_H
#define PLAYLISTLISTCONTAINER_H
#include <QSettings>
#include <QWidget>
#include "playlistbackend.h"
@ -42,6 +43,8 @@ class PlaylistListContainer : public QWidget {
void SetApplication(Application* app);
static const char* kSettingsGroup;
protected:
void showEvent(QShowEvent* e);
void contextMenuEvent(QContextMenuEvent* e);
@ -63,6 +66,9 @@ class PlaylistListContainer : public QWidget {
const QString* ui_path = nullptr);
void RemovePlaylist(int id);
void SavePlaylist();
void BulkImportPlaylists();
void RecursivelyCreateSubfolders(QStandardItem* parent_folder, QString path, QString ui_path);
void BulkImportPlaylistsCallback (int id);
void PlaylistFavoriteStateChanged(int id, bool favorite);
void CurrentChanged(Playlist* new_playlist);
void ActiveChanged(Playlist* new_playlist);
@ -89,6 +95,7 @@ class PlaylistListContainer : public QWidget {
QAction* action_new_folder_;
QAction* action_remove_;
QAction* action_save_playlist_;
QAction* action_bulk_import_playlists_;
PlaylistListModel* model_;
PlaylistListFilterProxyModel* proxy_;
@ -97,6 +104,12 @@ class PlaylistListContainer : public QWidget {
QIcon padded_play_icon_;
int active_playlist_id_;
QSettings settings_;
QList<int>* bulk_imported_id_list_;
int bulk_imported_count_;
int debug_count_;
};
#endif // PLAYLISTLISTCONTAINER_H

View File

@ -72,6 +72,13 @@
<item>
<widget class="QToolButton" name="save_playlist"/>
</item>
<item>
<widget class="QToolButton" name="bulk_import_playlists">
<property name="toolTip">
<string>Bulk Import Playlists</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">

View File

@ -101,6 +101,16 @@ QList<Playlist*> PlaylistManager::GetAllPlaylists() const {
return result;
}
QList<int> PlaylistManager::GetAllPlaylistIds() const {
QList<int> result;
for (const int data : playlists_.keys()) {
result.append(data);
}
return result;
}
QItemSelection PlaylistManager::selection(int id) const {
QMap<int, Data>::const_iterator it = playlists_.find(id);
return it->selection;
@ -196,6 +206,35 @@ void PlaylistManager::Save(int id, const QString& filename,
}
}
int PlaylistManager::LoadBulkPlaylists(const QString& filename, const QString& ui_path){
QFileInfo info(filename);
int id = playlist_backend_->CreatePlaylist(info.baseName(), QString());
if (id == -1) {
emit Error(tr("Couldn't create playlist"));
return id;
}
Playlist* playlist =
AddPlaylist(id, info.baseName(), QString(), ui_path, false);
QList<QUrl> urls;
playlist->InsertUrls(urls << QUrl::fromLocalFile(filename));
return id;
}
void PlaylistManager::BulkImportPlaylistsCallback(int id){
if (playlists_.contains(id)) {
// If playlists_ contains this playlist, its means it's opened: star or
// unstar it.
bool favorite = true;
playlist_backend_->FavoritePlaylist(id, favorite);
playlists_[id].p->set_favorite(favorite);
}
}
void PlaylistManager::ItemsLoadedForSavePlaylist(QFuture<SongList> future,
const QString& filename,
Playlist::Path path_type) {

View File

@ -55,6 +55,7 @@ class PlaylistManagerInterface : public QObject {
// Returns the collection of playlists managed by this PlaylistManager.
virtual QList<Playlist*> GetAllPlaylists() const = 0;
virtual QList<int> GetAllPlaylistIds() const = 0;
// Grays out and reloads all deleted songs in all playlists.
virtual void InvalidateDeletedSongs() = 0;
// Removes all deleted songs from all playlists.
@ -151,6 +152,7 @@ class PlaylistManager : public PlaylistManagerInterface {
// Returns the collection of playlists managed by this PlaylistManager.
QList<Playlist*> GetAllPlaylists() const;
QList<int> GetAllPlaylistIds() const;
// Grays out and reloads all deleted songs in all playlists.
void InvalidateDeletedSongs();
// Removes all deleted songs from all playlists.
@ -185,6 +187,8 @@ class PlaylistManager : public PlaylistManagerInterface {
const QString& special_type = QString());
void Load(const QString& filename);
void Save(int id, const QString& filename, Playlist::Path path_type);
int LoadBulkPlaylists(const QString& filename, const QString& ui_path);
void BulkImportPlaylistsCallback(int id);
// Display a file dialog to let user choose a file before saving the file
void SaveWithUI(int id, const QString& playlist_name);
void Rename(int id, const QString& new_name);

View File

@ -24,7 +24,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: he\n"
"Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n"
"Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;\n"
"X-Generator: Launchpad (build 13168)\n"
"X-Launchpad-Export-Date: 2011-06-27 04:53+0000\n"

View File

@ -15,7 +15,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: he_IL\n"
"Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n"
"Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;\n"
"X-Generator: Launchpad (build 13168)\n"
"X-Launchpad-Export-Date: 2011-06-27 04:53+0000\n"

View File

@ -8,14 +8,14 @@
# orange2141 <ataraxia90@gmail.com>, 2014
# 현구 임 <bradlim980@gmail.com>, 2012
# 현구 임 <bradlim980@gmail.com>, 2012
# Junghee Lee <daemul72@gmail.com>, 2021
# Junghee Lee <daemul72@gmail.com>, 2020
# JungHee Lee <daemul72@gmail.com>, 2021
# JungHee Lee <daemul72@gmail.com>, 2020
# FIRST AUTHOR <EMAIL@ADDRESS>, 2011
# 현구 임 <bradlim980@gmail.com>, 2012
# Ji yul Kim <kjy00302@gmail.com>, 2015
# Junghee Lee <daemul72@gmail.com>, 2023
# Junghee Lee <daemul72@gmail.com>, 2021-2022
# Junghee Lee <daemul72@gmail.com>, 2020-2021,2023
# JungHee Lee <daemul72@gmail.com>, 2023
# JungHee Lee <daemul72@gmail.com>, 2021-2022
# JungHee Lee <daemul72@gmail.com>, 2020-2021,2023-2024
# 1763f4a4329a2376c933c5e919a36cbc_341ca53 <1f851310383599d03339229d772e1290_119292>, 2016,2019,2022
# Park Jeongyong <kladess@gmail.com>, 2013
# Park Jeongyong <kladess@gmail.com>, 2014-2015
@ -30,7 +30,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-04-09 10:24+0000\n"
"PO-Revision-Date: 2011-10-27 18:53+0000\n"
"Last-Translator: Junghee Lee <daemul72@gmail.com>, 2020-2021,2023\n"
"Last-Translator: JungHee Lee <daemul72@gmail.com>, 2020-2021,2023-2024\n"
"Language-Team: Korean (http://app.transifex.com/davidsansome/clementine/language/ko/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -57,7 +57,7 @@ msgstr "곡"
#, qt-format
msgid "%1 albums"
msgstr "%1 앨범"
msgstr "%1 앨범"
#, qt-format
msgid "%1 days"
@ -89,7 +89,7 @@ msgstr "%1곡 찾음"
#, qt-format
msgid "%1 songs found (showing %2)"
msgstr "%1곡 찾음 (%2개 표시 중)"
msgstr "%1곡 찾음 (%2 표시하기)"
#, qt-format
msgid "%1 tracks"
@ -97,7 +97,7 @@ msgstr "%1개 트랙"
#, qt-format
msgid "%1 transferred"
msgstr "%1 전송됨"
msgstr "%1 전송됨"
#, qt-format
msgid "%1: Wiimotedev module"
@ -185,10 +185,10 @@ msgid "128k MP3"
msgstr "128k MP3"
msgid "50 random tracks"
msgstr "50개의 무작위 트랙"
msgstr "무작위 트랙 50곡"
msgid "<a href=\"http://www.di.fm/premium/\">Upgrade to Premium now</a>"
msgstr "<a href=\"http://www.di.fm/premium/\">지금 프리미엄으로 업그레이드하</a>"
msgstr "<a href=\"http://www.di.fm/premium/\">지금 프리미엄으로 업그레이드하세요</a>"
msgid ""
"<p>Tokens start with %, for example: %artist %album %title </p>\n"
@ -596,7 +596,7 @@ msgid "Cancel"
msgstr "취소하기"
msgid "Change cover art"
msgstr "커버 아트 바꾸기"
msgstr "표지 디자인 바꾸기"
msgid "Change font size..."
msgstr "글꼴 크기 바꾸기..."
@ -806,28 +806,28 @@ msgid "Couldn't open output file %1"
msgstr "출력 파일 %1를 열 수 없습니다"
msgid "Cover Manager"
msgstr "커버 관리자"
msgstr "표지 관리자"
msgid "Cover art from embedded image"
msgstr "내장된 이미지로부터 커버 아트 사용"
msgstr "삽입된 이미지의 표지 디자인"
#, qt-format
msgid "Cover art loaded automatically from %1"
msgstr "%1에서 자동으로 커버 아트를 불러옴"
msgstr "%1에서 자동으로 표지 디자인을 불러옴"
msgid "Cover art manually unset"
msgstr "커버 아트 수동으로 설정 해제"
msgstr "직접 지정 해제한 표지 디자인"
msgid "Cover art not set"
msgstr "커버 아트를 설정하지 않음"
msgstr "표지 디자인 지정 안함"
#, qt-format
msgid "Cover art set from %1"
msgstr "%1에서 커버 아트를 설정함"
msgstr "%1에서 지정한 표지 디자인"
#, qt-format
msgid "Covers from %1"
msgstr "%1에서 커버"
msgstr "%1의 표지"
msgid "Cross-fade when changing tracks automatically"
msgstr "트랙을 자동으로 변경할 때 크로스 페이드"
@ -1137,7 +1137,7 @@ msgid "Encoding mode"
msgstr "인코딩 모드"
msgid "Enter a URL to download a cover from the Internet:"
msgstr "인터넷에서 다운로드할 커버의 URL 주소를 입력하세요"
msgstr "인터넷에서 표지를 다운로드하려면 URL을 입력하세요:"
msgid "Enter a new name for this playlist"
msgstr "새로운 재생목록 이름을 입력하세요"
@ -1239,7 +1239,7 @@ msgid "Favourite tracks"
msgstr "좋아하는 트랙"
msgid "Fetch Missing Covers"
msgstr "누락된 커버 가져오기"
msgstr "누락된 표지 가져오기"
msgid "Fetch automatically"
msgstr "자동으로 가져오기"
@ -1248,7 +1248,7 @@ msgid "Fetch completed"
msgstr "가져오기 완료"
msgid "Fetching cover error"
msgstr "커버 가져오기 오류"
msgstr "표지 가져오는 중 오류"
msgid "File extension"
msgstr "파일 확장자"
@ -1377,7 +1377,7 @@ msgstr "Google 사용자이름"
#, qt-format
msgid "Got %1 covers out of %2 (%3 failed)"
msgstr "%2 중 %1개 커버 가져옴 (%3 실패 됨)"
msgstr "%2개 중 %1개 표지 받았음 (%3개 실패함)"
msgid "Grey out non existent songs in my playlists"
msgstr "재생 목록에 존재하지 않는 곡이 회색으로 표시됨"
@ -1669,13 +1669,13 @@ msgid "Load"
msgstr "열기"
msgid "Load cover from URL"
msgstr "URL로 부터 커버 열기"
msgstr "URL에서 표지 불러오기"
msgid "Load cover from URL..."
msgstr "URL로 부터 커버 열기..."
msgstr "URL에서 표지 불러오기..."
msgid "Load cover from disk..."
msgstr "디스크로부터 커버열기..."
msgstr "디스크에서 표지 불러오기..."
msgid "Load playlist"
msgstr "재생목록 불러오기"
@ -2386,10 +2386,10 @@ msgid "Samplerate"
msgstr "샘플 레이트"
msgid "Save album cover"
msgstr "앨범 표지 저장"
msgstr "앨범 표지 저장하기"
msgid "Save cover to disk..."
msgstr "커버를 디스크에 저장..."
msgstr "디스크에 표지 저장하기..."
msgid "Save image"
msgstr "그림 저장"
@ -2446,7 +2446,7 @@ msgid "Search Spotify (opens a new tab)..."
msgstr "Spotify 검색하기 (새 탭 열림)..."
msgid "Search for album covers..."
msgstr "앨범 표지 검색..."
msgstr "앨범 표지 검색하기..."
msgid "Search mode"
msgstr "모드 검색"
@ -2554,7 +2554,7 @@ msgid "Show all the songs"
msgstr "모든 곡 표시"
msgid "Show cover art in library"
msgstr "라이브러리에서 표지 아트 표시"
msgstr "라이브러리에 표지 디자인 표시하기"
msgid "Show dividers"
msgstr "구분선 표시"
@ -2974,7 +2974,7 @@ msgid "Unknown error"
msgstr "알 수 없는 오류"
msgid "Unset cover"
msgstr "커버 해제"
msgstr "표지 지정해제"
msgid "Update changed library folders"
msgstr "변경된 라이브러리 폴더 업데이트"