Compare commits

..

5 Commits

Author SHA1 Message Date
Jonas Kvinge
dd61b131fe Add uncrustify config 2025-12-29 00:38:17 +01:00
Strawberry Bot
da2f28811a New translations 2025-12-29 00:02:45 +01:00
Jonas Kvinge
0bfa736081 GstEnginePipeline: Add audioresample elements 2025-12-28 22:01:42 +01:00
Jonas Kvinge
1392bcbbe1 FilesystemMusicStorage: Fallback to delete if moving to trash fails
Fixes #1679
2025-12-28 21:28:49 +01:00
Jonas Kvinge
11705889f1 Show playlist load errors
Fixes #1470
2025-12-28 20:54:36 +01:00
15 changed files with 3831 additions and 420 deletions

View File

@@ -295,12 +295,6 @@ if(UNIX AND NOT APPLE)
)
endif()
if(MSVC)
optional_component(WINDOWS_MEDIA_CONTROLS ON "Windows Media Transport Controls"
DEPENDS "MSVC compiler" MSVC
)
endif()
optional_component(SONGFINGERPRINTING ON "Song fingerprinting and tracking"
DEPENDS "chromaprint" CHROMAPRINT_FOUND
)
@@ -1300,7 +1294,6 @@ endif()
optional_source(HAVE_ALSA SOURCES src/engine/alsadevicefinder.cpp src/engine/alsapcmdevicefinder.cpp)
optional_source(HAVE_PULSE SOURCES src/engine/pulsedevicefinder.cpp)
optional_source(MSVC SOURCES src/engine/uwpdevicefinder.cpp src/engine/asiodevicefinder.cpp)
optional_source(HAVE_WINDOWS_MEDIA_CONTROLS SOURCES src/core/windowsmediacontroller.cpp HEADERS src/core/windowsmediacontroller.h)
optional_source(HAVE_CHROMAPRINT SOURCES src/engine/chromaprinter.cpp)
optional_source(HAVE_MUSICBRAINZ

View File

@@ -14,7 +14,6 @@
#cmakedefine HAVE_GIO_UNIX
#cmakedefine HAVE_DBUS
#cmakedefine HAVE_MPRIS2
#cmakedefine HAVE_WINDOWS_MEDIA_CONTROLS
#cmakedefine HAVE_UDISKS2
#cmakedefine HAVE_AUDIOCD
#cmakedefine HAVE_MTP

View File

@@ -110,21 +110,32 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job, QString &error_te
bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) {
QString path = job.metadata_.url().toLocalFile();
QFileInfo fileInfo(path);
const QString path = job.metadata_.url().toLocalFile();
const QFileInfo fileInfo(path);
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
if (job.use_trash_ && QFile::supportsMoveToTrash()) {
#else
if (job.use_trash_) {
#endif
return QFile::moveToTrash(path);
if (QFile::moveToTrash(path)) {
return true;
}
qLog(Warning) << "Moving file to trash failed for" << path << ", falling back to direct deletion";
}
bool success = false;
if (fileInfo.isDir()) {
return Utilities::RemoveRecursive(path);
success = Utilities::RemoveRecursive(path);
}
else {
success = QFile::remove(path);
}
return QFile::remove(path);
if (!success) {
qLog(Error) << "Failed to delete file" << path;
}
return success;
}

View File

@@ -98,6 +98,9 @@ SongLoader::SongLoader(const SharedPtr<UrlHandlers> url_handlers,
QObject::connect(timeout_timer_, &QTimer::timeout, this, &SongLoader::Timeout);
QObject::connect(playlist_parser_, &PlaylistParser::Error, this, &SongLoader::ParserError);
QObject::connect(cue_parser_, &CueParser::Error, this, &SongLoader::ParserError);
}
SongLoader::~SongLoader() {
@@ -106,6 +109,10 @@ SongLoader::~SongLoader() {
}
void SongLoader::ParserError(const QString &error) {
errors_ << error;
}
SongLoader::Result SongLoader::Load(const QUrl &url) {
if (url.isEmpty()) return Result::Error;
@@ -287,6 +294,7 @@ SongLoader::Result SongLoader::LoadLocalAsync(const QString &filename) {
}
if (parser) { // It's a playlist!
QObject::connect(parser, &ParserBase::Error, this, &SongLoader::ParserError, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
qLog(Debug) << "Parsing using" << parser->name();
LoadPlaylist(parser, filename);
return Result::Success;
@@ -706,6 +714,10 @@ void SongLoader::MagicReady() {
StopTypefindAsync(true);
}
if (parser_) {
QObject::connect(parser_, &ParserBase::Error, this, &SongLoader::ParserError, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
}
state_ = State::WaitingForData;
if (!IsPipelinePlaying()) {

View File

@@ -99,6 +99,7 @@ class SongLoader : public QObject {
void ScheduleTimeout();
void Timeout();
void StopTypefind();
void ParserError(const QString &error);
#ifdef HAVE_AUDIOCD
void AudioCDTracksLoadErrorSlot(const QString &error);

View File

@@ -1,303 +0,0 @@
/*
* Strawberry Music Player
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <windows.h>
#include <QObject>
#include <QString>
#include <QUrl>
// Undefine 'interface' macro from windows.h before including WinRT headers
#pragma push_macro("interface")
#undef interface
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Media.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Storage.Streams.h>
#pragma pop_macro("interface")
// Include the interop header for ISystemMediaTransportControlsInterop
#include <systemmediatransportcontrolsinterop.h>
#include "core/logging.h"
#include "windowsmediacontroller.h"
#include "core/song.h"
#include "core/player.h"
#include "engine/enginebase.h"
#include "playlist/playlistmanager.h"
#include "covermanager/currentalbumcoverloader.h"
#include "covermanager/albumcoverloaderresult.h"
using namespace winrt;
using namespace Windows::Foundation;
using namespace Windows::Media;
using namespace Windows::Storage;
using namespace Windows::Storage::Streams;
// Helper struct to hold the WinRT object
struct WindowsMediaControllerPrivate {
SystemMediaTransportControls smtc{nullptr};
};
WindowsMediaController::WindowsMediaController(HWND hwnd,
const SharedPtr<Player> player,
const SharedPtr<PlaylistManager> playlist_manager,
const SharedPtr<CurrentAlbumCoverLoader> current_albumcover_loader,
QObject *parent)
: QObject(parent),
player_(player),
playlist_manager_(playlist_manager),
current_albumcover_loader_(current_albumcover_loader),
smtc_(nullptr),
apartment_initialized_(false) {
try {
// Initialize WinRT apartment if not already initialized
// Qt or other components may have already initialized it
try {
winrt::init_apartment(winrt::apartment_type::single_threaded);
apartment_initialized_ = true;
}
catch (const hresult_error &e) {
// Apartment already initialized - this is fine, continue
if (e.code() != RPC_E_CHANGED_MODE) {
throw;
}
}
// Create private implementation
auto *priv = new WindowsMediaControllerPrivate();
smtc_ = priv;
// Get the SystemMediaTransportControls instance for this window
// Use the interop interface
auto interop = winrt::get_activation_factory<SystemMediaTransportControls, ISystemMediaTransportControlsInterop>();
if (!interop) {
qLog(Warning) << "Failed to get ISystemMediaTransportControlsInterop";
delete priv;
smtc_ = nullptr;
return;
}
// Get SMTC for the window
winrt::com_ptr<IInspectable> inspectable;
HRESULT hr = interop->GetForWindow(hwnd, winrt::guid_of<SystemMediaTransportControls>(), inspectable.put_void());
if (FAILED(hr) || !inspectable) {
qLog(Warning) << "Failed to get SystemMediaTransportControls for window, HRESULT:" << Qt::hex << static_cast<unsigned int>(hr);
delete priv;
smtc_ = nullptr;
return;
}
// Convert to SystemMediaTransportControls
priv->smtc = inspectable.as<SystemMediaTransportControls>();
if (!priv->smtc) {
qLog(Warning) << "Failed to cast to SystemMediaTransportControls";
delete priv;
smtc_ = nullptr;
return;
}
// Enable the controls
priv->smtc.IsEnabled(true);
priv->smtc.IsPlayEnabled(true);
priv->smtc.IsPauseEnabled(true);
priv->smtc.IsStopEnabled(true);
priv->smtc.IsNextEnabled(true);
priv->smtc.IsPreviousEnabled(true);
// Setup button handlers
SetupButtonHandlers();
// Connect signals from Player
QObject::connect(&*player_->engine(), &EngineBase::StateChanged, this, &WindowsMediaController::EngineStateChanged);
QObject::connect(&*playlist_manager_, &PlaylistManager::CurrentSongChanged, this, &WindowsMediaController::CurrentSongChanged);
QObject::connect(&*current_albumcover_loader_, &CurrentAlbumCoverLoader::AlbumCoverLoaded, this, &WindowsMediaController::AlbumCoverLoaded);
qLog(Info) << "Windows Media Transport Controls initialized successfully";
}
catch (const hresult_error &e) {
qLog(Warning) << "Failed to initialize Windows Media Transport Controls:" << QString::fromWCharArray(e.message().c_str());
if (smtc_) {
delete static_cast<WindowsMediaControllerPrivate*>(smtc_);
smtc_ = nullptr;
}
}
catch (...) {
qLog(Warning) << "Failed to initialize Windows Media Transport Controls: unknown error";
if (smtc_) {
delete static_cast<WindowsMediaControllerPrivate*>(smtc_);
smtc_ = nullptr;
}
}
}
WindowsMediaController::~WindowsMediaController() {
if (smtc_) {
auto *priv = static_cast<WindowsMediaControllerPrivate*>(smtc_);
if (priv->smtc) {
priv->smtc.IsEnabled(false);
}
delete priv;
smtc_ = nullptr;
}
// Only uninit if we initialized the apartment
if (apartment_initialized_) {
winrt::uninit_apartment();
}
}
void WindowsMediaController::SetupButtonHandlers() {
if (!smtc_) return;
auto *priv = static_cast<WindowsMediaControllerPrivate*>(smtc_);
if (!priv->smtc) return;
// Handle button pressed events
priv->smtc.ButtonPressed([this](const SystemMediaTransportControls &, const SystemMediaTransportControlsButtonPressedEventArgs &args) {
switch (args.Button()) {
case SystemMediaTransportControlsButton::Play:
player_->Play();
break;
case SystemMediaTransportControlsButton::Pause:
player_->Pause();
break;
case SystemMediaTransportControlsButton::Stop:
player_->Stop();
break;
case SystemMediaTransportControlsButton::Next:
player_->Next();
break;
case SystemMediaTransportControlsButton::Previous:
player_->Previous();
break;
default:
break;
}
});
}
void WindowsMediaController::EngineStateChanged(EngineBase::State newState) {
UpdatePlaybackStatus(newState);
}
void WindowsMediaController::UpdatePlaybackStatus(EngineBase::State state) {
if (!smtc_) return;
auto *priv = static_cast<WindowsMediaControllerPrivate*>(smtc_);
if (!priv->smtc) return;
try {
switch (state) {
case EngineBase::State::Playing:
priv->smtc.PlaybackStatus(MediaPlaybackStatus::Playing);
break;
case EngineBase::State::Paused:
priv->smtc.PlaybackStatus(MediaPlaybackStatus::Paused);
break;
case EngineBase::State::Empty:
case EngineBase::State::Idle:
priv->smtc.PlaybackStatus(MediaPlaybackStatus::Stopped);
break;
}
}
catch (const hresult_error &e) {
qLog(Warning) << "Failed to update playback status:" << QString::fromWCharArray(e.message().c_str());
}
}
void WindowsMediaController::CurrentSongChanged(const Song &song) {
if (!song.is_valid()) {
return;
}
// Update metadata immediately with what we have
UpdateMetadata(song, QUrl());
// Album cover will be updated via AlbumCoverLoaded signal
}
void WindowsMediaController::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
if (!song.is_valid()) {
return;
}
// Update metadata with album cover
UpdateMetadata(song, result.temp_cover_url.isEmpty() ? result.album_cover.cover_url : result.temp_cover_url);
}
void WindowsMediaController::UpdateMetadata(const Song &song, const QUrl &art_url) {
if (!smtc_) return;
auto *priv = static_cast<WindowsMediaControllerPrivate*>(smtc_);
if (!priv->smtc) return;
try {
// Get the updater
SystemMediaTransportControlsDisplayUpdater updater = priv->smtc.DisplayUpdater();
updater.Type(MediaPlaybackType::Music);
// Get the music properties
auto musicProperties = updater.MusicProperties();
// Set basic metadata
if (!song.title().isEmpty()) {
musicProperties.Title(winrt::hstring(song.title().toStdWString()));
}
if (!song.artist().isEmpty()) {
musicProperties.Artist(winrt::hstring(song.artist().toStdWString()));
}
if (!song.album().isEmpty()) {
musicProperties.AlbumTitle(winrt::hstring(song.album().toStdWString()));
}
// Set album art if available
if (art_url.isValid() && art_url.isLocalFile()) {
QString artPath = art_url.toLocalFile();
if (!artPath.isEmpty()) {
try {
// Use file:// URI to avoid async blocking in STA thread
QString fileUri = QUrl::fromLocalFile(artPath).toString();
auto thumbnailStream = RandomAccessStreamReference::CreateFromUri(
winrt::Windows::Foundation::Uri(winrt::hstring(fileUri.toStdWString()))
);
updater.Thumbnail(thumbnailStream);
current_song_art_url_ = artPath;
}
catch (const hresult_error &e) {
qLog(Debug) << "Failed to set album art:" << QString::fromWCharArray(e.message().c_str());
}
}
}
// Update the display
updater.Update();
}
catch (const hresult_error &e) {
qLog(Warning) << "Failed to update metadata:" << QString::fromWCharArray(e.message().c_str());
}
}

View File

@@ -1,69 +0,0 @@
/*
* Strawberry Music Player
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef WINDOWSMEDIACONTROLLER_H
#define WINDOWSMEDIACONTROLLER_H
#include "config.h"
#include <windows.h>
#include <QObject>
#include <QString>
#include "includes/shared_ptr.h"
#include "engine/enginebase.h"
#include "covermanager/albumcoverloaderresult.h"
class Player;
class PlaylistManager;
class CurrentAlbumCoverLoader;
class Song;
class WindowsMediaController : public QObject {
Q_OBJECT
public:
explicit WindowsMediaController(HWND hwnd,
const SharedPtr<Player> player,
const SharedPtr<PlaylistManager> playlist_manager,
const SharedPtr<CurrentAlbumCoverLoader> current_albumcover_loader,
QObject *parent = nullptr);
~WindowsMediaController() override;
private Q_SLOTS:
void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result = AlbumCoverLoaderResult());
void EngineStateChanged(EngineBase::State newState);
void CurrentSongChanged(const Song &song);
private:
void UpdatePlaybackStatus(EngineBase::State state);
void UpdateMetadata(const Song &song, const QUrl &art_url);
void SetupButtonHandlers();
private:
const SharedPtr<Player> player_;
const SharedPtr<PlaylistManager> playlist_manager_;
const SharedPtr<CurrentAlbumCoverLoader> current_albumcover_loader_;
void *smtc_; // Pointer to SystemMediaTransportControls (opaque to avoid WinRT headers in public header)
QString current_song_art_url_;
bool apartment_initialized_; // Track if we initialized the WinRT apartment
};
#endif // WINDOWSMEDIACONTROLLER_H

View File

@@ -178,7 +178,6 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
audiobin_(nullptr),
audiosink_(nullptr),
audioqueue_(nullptr),
audioqueueconverter_(nullptr),
volume_(nullptr),
volume_sw_(nullptr),
volume_fading_(nullptr),
@@ -187,6 +186,7 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
equalizer_(nullptr),
equalizer_preamp_(nullptr),
eventprobe_(nullptr),
bufferprobe_(nullptr),
logged_unsupported_analyzer_format_(false),
about_to_finish_(false),
finish_requested_(false),
@@ -436,7 +436,7 @@ void GstEnginePipeline::Disconnect() {
}
if (buffer_probe_cb_id_.has_value()) {
GstPad *pad = gst_element_get_static_pad(audioqueueconverter_, "src");
GstPad *pad = gst_element_get_static_pad(bufferprobe_, "src");
if (pad) {
gst_pad_remove_probe(pad, buffer_probe_cb_id_.value());
gst_object_unref(pad);
@@ -674,8 +674,13 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
return false;
}
audioqueueconverter_ = CreateElement(u"audioconvert"_s, u"audioqueueconverter"_s, audiobin_, error);
if (!audioqueueconverter_) {
GstElement *audioqueueconverter = CreateElement(u"audioconvert"_s, u"audioqueueconverter"_s, audiobin_, error);
if (!audioqueueconverter) {
return false;
}
GstElement *audioqueueresampler = CreateElement(u"audioresample"_s, u"audioqueueresampler"_s, audiobin_, error);
if (!audioqueueresampler) {
return false;
}
@@ -684,6 +689,11 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
return false;
}
GstElement *audiosinkresampler = CreateElement(u"audioresample"_s, u"audiosinkresampler"_s, audiobin_, error);
if (!audiosinkresampler) {
return false;
}
// Create the volume element if it's enabled.
if (volume_enabled_ && !volume_) {
volume_sw_ = CreateElement(u"volume"_s, u"volume_sw"_s, audiobin_, error);
@@ -761,7 +771,8 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
}
eventprobe_ = audioqueueconverter_;
eventprobe_ = audioqueueconverter;
bufferprobe_ = audioqueueconverter;
// Create the replaygain elements if it's enabled.
GstElement *rgvolume = nullptr;
@@ -847,12 +858,17 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
// Link all elements
if (!gst_element_link(audioqueue_, audioqueueconverter_)) {
if (!gst_element_link(audioqueue_, audioqueueconverter)) {
error = u"Failed to link audio queue to audio queue converter."_s;
return false;
}
GstElement *element_link = audioqueueconverter_; // The next element to link from.
if (!gst_element_link(audioqueueconverter, audioqueueresampler)) {
error = u"Failed to link audio queue converter to audio queue resampler."_s;
return false;
}
GstElement *element_link = audioqueueresampler; // The next element to link from.
// Link replaygain elements if enabled.
if (rg_enabled_ && rgvolume && rglimiter && rgconverter) {
@@ -928,6 +944,11 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
return false;
}
if (!gst_element_link(audiosinkconverter, audiosinkresampler)) {
error = "Failed to link audio sink converter to audio sink resampler."_L1;
return false;
}
{
GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw");
if (!caps) {
@@ -938,16 +959,16 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
qLog(Debug) << "Setting channels to" << channels_;
gst_caps_set_simple(caps, "channels", G_TYPE_INT, channels_, nullptr);
}
const bool link_filtered_result = gst_element_link_filtered(audiosinkconverter, audiosink_, caps);
const bool link_filtered_result = gst_element_link_filtered(audiosinkresampler, audiosink_, caps);
gst_caps_unref(caps);
if (!link_filtered_result) {
error = "Failed to link audio sink converter to audio sink with filter for "_L1 + output_;
error = "Failed to link audio sink resampler to audio sink with filter for "_L1 + output_;
return false;
}
}
{ // Add probes and handlers.
GstPad *pad = gst_element_get_static_pad(audioqueueconverter_, "src");
GstPad *pad = gst_element_get_static_pad(bufferprobe_, "src");
if (pad) {
buffer_probe_cb_id_ = gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, BufferProbeCallback, this, nullptr);
gst_object_unref(pad);

View File

@@ -355,7 +355,6 @@ class GstEnginePipeline : public QObject {
GstElement *audiobin_;
GstElement *audiosink_;
GstElement *audioqueue_;
GstElement *audioqueueconverter_;
GstElement *volume_;
GstElement *volume_sw_;
GstElement *volume_fading_;
@@ -364,6 +363,7 @@ class GstEnginePipeline : public QObject {
GstElement *equalizer_;
GstElement *equalizer_preamp_;
GstElement *eventprobe_;
GstElement *bufferprobe_;
std::optional<gulong> upstream_events_probe_cb_id_;
std::optional<gulong> buffer_probe_cb_id_;

View File

@@ -93,10 +93,6 @@
# include "discord/richpresence.h"
#endif
#ifdef HAVE_WINDOWS_MEDIA_CONTROLS
# include "core/windowsmediacontroller.h"
#endif
#include "core/iconloader.h"
#include "core/commandlineoptions.h"
#include "core/networkproxyfactory.h"
@@ -369,11 +365,6 @@ int main(int argc, char *argv[]) {
#endif
options);
#ifdef HAVE_WINDOWS_MEDIA_CONTROLS
// Initialize Windows Media Transport Controls
WindowsMediaController windows_media_controller(reinterpret_cast<HWND>(w.winId()), app.player(), app.playlist_manager(), app.current_albumcover_loader());
#endif
#ifdef Q_OS_MACOS
mac::EnableFullScreen(w);
#endif // Q_OS_MACOS

View File

@@ -80,12 +80,13 @@ void SongLoaderInserter::Load(Playlist *destination, const int row, const bool p
songs_ << loader->songs();
playlist_name_ = loader->playlist_name();
}
else {
const QStringList errors = loader->errors();
for (const QString &error : errors) {
Q_EMIT Error(error);
}
// Always check for errors, even on success (e.g., playlist parsed but some songs failed to load)
const QStringList errors = loader->errors();
for (const QString &error : errors) {
Q_EMIT Error(error);
}
delete loader;
}
@@ -192,11 +193,13 @@ void SongLoaderInserter::AsyncLoad() {
const SongLoader::Result result = loader->LoadFilenamesBlocking();
task_manager_->SetTaskProgress(async_load_id, static_cast<quint64>(++async_progress));
// Always check for errors, even on success (e.g., playlist parsed but some songs failed to load)
const QStringList errors = loader->errors();
for (const QString &error : errors) {
Q_EMIT Error(error);
}
if (result == SongLoader::Result::Error) {
const QStringList errors = loader->errors();
for (const QString &error : errors) {
Q_EMIT Error(error);
}
continue;
}

View File

@@ -112,10 +112,18 @@ void ParserBase::LoadSong(const QString &filename_or_url, const qint64 beginning
}
}
// Check if the file exists before trying to read it
if (!QFile::exists(filename)) {
qLog(Error) << "File does not exist:" << filename;
Q_EMIT Error(tr("File %1 does not exist").arg(filename));
return;
}
if (tagreader_client_) {
const TagReaderResult result = tagreader_client_->ReadFileBlocking(filename, song);
if (!result.success()) {
qLog(Error) << "Could not read file" << filename << result.error_string();
Q_EMIT Error(tr("Could not read file %1: %2").arg(filename, result.error_string()));
}
}

View File

@@ -5526,7 +5526,7 @@ Are you sure you want to continue?</source>
<name>RadioParadiseService</name>
<message>
<source>Getting %1 channels</source>
<translation>Получение %1 каналов</translation>
<translation>Получение каналов %1</translation>
</message>
</context>
<context>
@@ -6191,7 +6191,7 @@ Are you sure you want to continue?</source>
<name>SomaFMService</name>
<message>
<source>Getting %1 channels</source>
<translation>Получение %1 каналов</translation>
<translation>Получение каналов %1</translation>
</message>
</context>
<context>

View File

@@ -1187,7 +1187,7 @@ If there are no matches then it will use the largest image in the directory.</tr
</message>
<message>
<source>Queue to play next</source>
<translation>Sıradaki Yap</translation>
<translation>Sıradaki yap</translation>
</message>
<message>
<source>Search for this</source>
@@ -3496,7 +3496,7 @@ If there are no matches then it will use the largest image in the directory.</tr
</message>
<message>
<source>Queue to play next</source>
<translation>Sıradaki Yap</translation>
<translation>Sıradaki yap</translation>
</message>
<message>
<source>Unskip track</source>
@@ -4431,7 +4431,7 @@ If there are no matches then it will use the largest image in the directory.</tr
</message>
<message>
<source>&amp;Hide %1</source>
<translation>&amp;%1&apos;i sakla</translation>
<translation>&amp;%1 ögesini sakla</translation>
</message>
</context>
<context>
@@ -6407,7 +6407,7 @@ Devam etmek istediğinizden emin misiniz?</translation>
</message>
<message>
<source>Queue to play next</source>
<translation>Sıradaki Yap</translation>
<translation>Sıradaki yap</translation>
</message>
<message>
<source>Remove from favorites</source>

3744
uncrustify.cfg Normal file

File diff suppressed because it is too large Load Diff