Toob Player

This commit is contained in:
Robin E. R. Davies
2025-06-15 16:41:11 -04:00
parent 6ea45f46df
commit 13883f91ab
26 changed files with 1314 additions and 547 deletions
+113 -55
View File
@@ -43,6 +43,41 @@ namespace fs = std::filesystem;
static constexpr size_t INVALID_INDEX = std::numeric_limits<size_t>::max();
// All the varous cover art files I found on my personal music collection.
// These are used to find the cover art for a directory. Based on
// scanning a large music collection that have been tagged by various tools
// including iTunes, Windows Media Playe, and a variety of taggers.
//
// Files are listed in order of preference.
static std::vector<std::string> COVER_ART_FILES = {
"Folder.jpg", // window media player/explorer.
"Cover.jpg", // itunes.
"folder.jpg", // Linux audio players plex, kodi, Rythmbox, Amorok, Clementine.
"cover.jpg", // Linux audio players plex, kodi, Rythmbox, Amorok, Clementine.
"Artwork.jpg", // itunes.
"Front.jpg",
"front.jpg", // linux.
"AlbumArt.jpg",
"albumArt.jpg",
"Frontcover.jpg",
"AlbumArtSmall.jpg" // windows media player.
};
bool pipedal::isArtworkFileName(const std::string &fileName)
{
for (const auto &coverArtFile : COVER_ART_FILES)
{
if (fileName == coverArtFile)
{
return true;
}
}
return false;
}
void AudioDirectoryInfo::SetTemporaryDirectory(const std::filesystem::path &path)
{
temporaryDirectory = path;
@@ -96,10 +131,9 @@ namespace
class AudioDirectoryInfoImpl : public AudioDirectoryInfo
{
public:
AudioDirectoryInfoImpl(const std::filesystem::path &path, const std::filesystem::path&indexPath)
AudioDirectoryInfoImpl(const std::filesystem::path &path, const std::filesystem::path &indexPath)
: path(path), indexPath(
(indexPath.empty() ? path: indexPath) / ".index.pipedal"
)
(indexPath.empty() ? path : indexPath) / ".index.pipedal")
{
if (this->indexPath.parent_path() != path)
{
@@ -129,7 +163,6 @@ namespace
private:
std::vector<DbFileInfo> QueryTracks();
bool opened = false;
std::filesystem::path indexPath;
void OpenAudioDb();
@@ -139,7 +172,6 @@ namespace
fs::path path;
using id_t = int64_t;
std::vector<DbFileInfo> UpdateDbFiles();
void DbSetThumbnailType(
int64_t idFile,
@@ -156,16 +188,14 @@ namespace
void UpdateMetadata(DbFileInfo *dbFile);
static constexpr int DB_VERSION = 1;
std::filesystem::path GetFolderFile() const;
};
}
AudioDirectoryInfo::Ptr AudioDirectoryInfo::Create(const std::filesystem::path &path, const std::filesystem::path&indexPath)
AudioDirectoryInfo::Ptr AudioDirectoryInfo::Create(const std::filesystem::path &path, const std::filesystem::path &indexPath)
{
return std::make_shared<AudioDirectoryInfoImpl>(path,indexPath);
return std::make_shared<AudioDirectoryInfoImpl>(path, indexPath);
}
std::vector<DbFileInfo> AudioDirectoryInfoImpl::QueryTracks()
{
if (audioFilesDb)
@@ -264,7 +294,18 @@ std::vector<DbFileInfo> AudioDirectoryInfoImpl::UpdateDbFiles()
auto path = dirEntry.path();
std::string name = path.filename();
std::string extension = path.extension();
if (isAudioExtension(extension))
if (isAudioExtension(extension) || isArtworkFileName(name))
{
// If the file is an audio file or a cover art file, we need to check it.
if (name.empty() || name[0] == '.')
{
continue; // skip hidden files.
}
}
else
{
continue; // not an audio file or cover art file.
}
{
int64_t lastModified = fileTimeToInt64(dirEntry.last_write_time());
@@ -322,7 +363,7 @@ std::vector<DbFileInfo> AudioDirectoryInfoImpl::UpdateDbFiles()
}
for (auto &dbFile : dbFiles)
{
if (dbFile.thumbnailType() == ThumbnailType::Unknown)
if (dbFile.thumbnailType() == ThumbnailType::None)
{
if (!folderFileName.empty())
{
@@ -410,27 +451,67 @@ void AudioDirectoryInfoImpl::DbDeleteFile(DbFileInfo *dbFile)
audioFilesDb->DeleteFile(dbFile);
}
}
ThumbnailTemporaryFile AudioDirectoryInfoImpl::DefaultThumbnailTemporaryFile()
{
ThumbnailTemporaryFile tempFile;
fs::path defaultThumbnail = GetResourceDirectory() / "img/missing_thumbnail.jpg";
tempFile.SetNonDeletedPath(defaultThumbnail, "image/jpeg");
return tempFile;
}
// All the varous cover art files I found on my personal music collection.
// These are used to find the cover art for a directory. Based on
// scanning a large music collection that have been tagged by various tools
// including iTunes, Windows Media Playe, and a variety of taggers.
//
// Files are listed in order of preference.
ThumbnailTemporaryFile AudioDirectoryInfoImpl::GetUnindexedThunbnail(
const std::string &fileNameOnly, int32_t width, int32_t height)
{
fs::path file = this->path / fileNameOnly;
if (!fs::exists(file))
{
return DefaultThumbnailTemporaryFile();
}
try
{
auto tempFile = pipedal::GetAudioFileThumbnail(
file, width, height,
GetTemporaryDirectory());
auto thumbnailPath = tempFile.Path();
ThumbnailTemporaryFile result;
result.Attach(tempFile.Detach(), "image/jpeg");
static std::vector<std::filesystem::path> COVER_ART_FILES = {
"Folder.jpg", // window media player/explorer.
"Cover.jpg", // itunes.
"folder.jpg", // Linux audio players plex, kodi, Rythmbox, Amorok, Clementine.
"cover.jpg", // Linux audio players plex, kodi, Rythmbox, Amorok, Clementine.
"Artwork.jpg", // itunes.
"Front.jpg",
"front.jpg", // linux.
"AlbumArt.jpg",
"albumArt.jpg",
"Frontcover.jpg",
"AlbumArtSmall.jpg" // windows media player.
};
// Read the thumbnail data from the temporary file.
std::ifstream thumbnailStream(thumbnailPath, std::ios::binary);
if (!thumbnailStream)
{
throw std::runtime_error("Failed to open thumbnail file: " + thumbnailPath.string());
}
std::vector<uint8_t> thumbnailData(
(std::istreambuf_iterator<char>(thumbnailStream)),
std::istreambuf_iterator<char>());
if (!thumbnailData.empty())
{
audioFilesDb->AddThumbnail(
fileNameOnly,
width,
height,
thumbnailData);
// Set the MIME type for the thumbnail.
result.SetMimeType("image/jpeg");
audioFilesDb->UpdateThumbnailInfo(
fileNameOnly,
ThumbnailType::Embedded);
return result;
}
else
{
throw std::runtime_error("Thumbnail data is empty.");
}
}
catch (const std::exception &e)
{
Lv2Log::error("GetUnindexedThunbnail failed: %s", e.what());
}
return DefaultThumbnailTemporaryFile();
}
fs::path AudioDirectoryInfoImpl::GetFolderFile() const
{
@@ -604,22 +685,6 @@ ThumbnailTemporaryFile AudioDirectoryInfoImpl::GetThumbnail(const std::string &f
return GetUnindexedThunbnail(fileNameOnly, width, height);
}
ThumbnailTemporaryFile AudioDirectoryInfoImpl::GetUnindexedThunbnail(const std::string &fileNameOnly, int32_t width, int32_t height)
{
fs::path file = this->path / fileNameOnly;
ThumbnailTemporaryFile tempFile = ThumbnailTemporaryFile::CreateTemporaryFile(GetTemporaryDirectory(), "image/jpeg");
try
{
auto thumbnailPath = pipedal::GetAudioFileThumbnail(file, width, height, GetTemporaryDirectory());
tempFile.Attach(thumbnailPath.Detach(), "image/jpeg");
return tempFile;
}
catch (const std::exception &e)
{
// If we cannot get the thumbnail, return a default one.
return DefaultThumbnailTemporaryFile();
}
}
void AudioDirectoryInfoImpl::UpdateMetadata(DbFileInfo *dbFile)
{
@@ -687,13 +752,6 @@ void AudioDirectoryInfoImpl::OpenAudioDb()
}
}
ThumbnailTemporaryFile AudioDirectoryInfoImpl::DefaultThumbnailTemporaryFile()
{
ThumbnailTemporaryFile tempFile;
fs::path defaultThumbnail = GetResourceDirectory() / "img/missing_thumbnail.jpg";
tempFile.SetNonDeletedPath(defaultThumbnail, "image/jpeg");
return tempFile;
}
std::string AudioDirectoryInfoImpl::GetNextAudioFile(const std::string &fileNameOnly)
{
@@ -801,8 +859,8 @@ void AudioDirectoryInfoImpl::MoveAudioFile(
transaction->commit();
}
namespace pipedal {
namespace pipedal
{
std::filesystem::path IndexPathToShadowIndexPath(const std::filesystem::path &audioRootDirectory, const std::filesystem::path &path)
{
std::filesystem::path relativePath = MakeRelativePath(path, audioRootDirectory);
+1
View File
@@ -124,4 +124,5 @@ namespace pipedal
std::filesystem::path ShadowIndexPathToIndexPath(const std::filesystem::path &audioRootDirectory, const std::filesystem::path &path);
std::filesystem::path GetShadowIndexDirectory(const std::filesystem::path &audioRootDirectory, const std::filesystem::path &path);
bool isArtworkFileName(const std::string &fileName);
}
+11
View File
@@ -2407,6 +2407,17 @@ std::string PiPedalModel::RenameFilePropertyFile(
return storage.RenameFilePropertyFile(oldRelativePath, newRelativePath, uiFileProperty);
}
std::string PiPedalModel::CopyFilePropertyFile(
const std::string &oldRelativePath,
const std::string &newRelativePath,
const UiFileProperty &uiFileProperty,
bool overwrite)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
return storage.CopyFilePropertyFile(oldRelativePath, newRelativePath, uiFileProperty,overwrite);
}
void PiPedalModel::DeleteSampleFile(const std::filesystem::path &fileName)
{
std::lock_guard<std::recursive_mutex> lock(mutex);
+1
View File
@@ -454,6 +454,7 @@ namespace pipedal
void DeleteSampleFile(const std::filesystem::path &fileName);
std::string CreateNewSampleDirectory(const std::string &relativePath, const UiFileProperty &uiFileProperty);
std::string RenameFilePropertyFile(const std::string &oldRelativePath, const std::string &newRelativePath, const UiFileProperty &uiFileProperty);
std::string CopyFilePropertyFile(const std::string &oldRelativePath, const std::string &newRelativePath, const UiFileProperty &uiFileProperty,bool overwrite);
FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty &uiFileProperty,const std::string&selectedPath);
bool IsInUploadsDirectory(const std::string &path);
+25
View File
@@ -115,6 +115,23 @@ JSON_MAP_REFERENCE(RenameSampleFileArgs, newRelativePath)
JSON_MAP_REFERENCE(RenameSampleFileArgs, uiFileProperty)
JSON_MAP_END()
class CopySampleFileArgs
{
public:
std::string oldRelativePath_;
std::string newRelativePath_;
UiFileProperty uiFileProperty_;
bool overwrite_ = false;
DECLARE_JSON_MAP(CopySampleFileArgs);
};
JSON_MAP_BEGIN(CopySampleFileArgs)
JSON_MAP_REFERENCE(CopySampleFileArgs, oldRelativePath)
JSON_MAP_REFERENCE(CopySampleFileArgs, newRelativePath)
JSON_MAP_REFERENCE(CopySampleFileArgs, uiFileProperty)
JSON_MAP_REFERENCE(CopySampleFileArgs, overwrite)
JSON_MAP_END()
class GetFilePropertyDirectoryTreeArgs
{
public:
@@ -1645,6 +1662,14 @@ public:
std::string newFileName = this->model.RenameFilePropertyFile(args.oldRelativePath_, args.newRelativePath_, args.uiFileProperty_);
this->Reply(replyTo, "renameFilePropertyFile", newFileName);
}
else if (message == "copyFilePropertyFile")
{
CopySampleFileArgs args;
pReader->read(&args);
std::string newFileName = this->model.CopyFilePropertyFile(args.oldRelativePath_, args.newRelativePath_, args.uiFileProperty_, args.overwrite_);
this->Reply(replyTo, "copyFilePropertyFile", newFileName);
}
else if (message == "getFilePropertyDirectoryTree")
{
GetFilePropertyDirectoryTreeArgs args;
+68 -2
View File
@@ -1705,6 +1705,13 @@ static void AddTracksToResult(
std::set<std::string> validExtensions = fileProperty.GetPermittedFileExtensions(
modDirectoryInfo ? modDirectoryInfo->modType : "");
if (validExtensions.size() == 0)
{
const auto &audioExtensions = MimeTypes::instance().AudioExtensions();
validExtensions.insert(audioExtensions.begin(), audioExtensions.end());
}
validExtensions.insert(".jpg");
validExtensions.insert(".png");
try
{
// Add directories first.
@@ -1828,9 +1835,11 @@ FileRequestResult Storage::GetModFileList2(const std::string &relativePath, cons
++iRp;
}
}
fs::path cumulativePath = modDirectoryPath;
while (iRp != rp.end())
{
result.breadcrumbs_.push_back({*iRp, *iRp});
cumulativePath /= (*iRp);
result.breadcrumbs_.push_back({cumulativePath, *iRp});
++iRp;
}
}
@@ -2036,6 +2045,17 @@ std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, co
}
return result;
}
bool Storage::IsValidArtworkFile(const std::filesystem::path& fullPath)
{
if (IsInAudioTracksDirectory(fullPath) && isArtworkFileName(fullPath.filename().string()))
{
// allow artwork files.
return true;
}
return false;
}
std::string Storage::UploadUserFile(const std::string &directory,
UiFileProperty::ptr uiFileProperty,
const std::string &filename,
@@ -2060,7 +2080,7 @@ std::string Storage::UploadUserFile(const std::string &directory,
throw std::logic_error("Permission denied. Path is outside the upload storage directory.");
}
if (!uiFileProperty->IsValidExtension(relativePath))
if (!(uiFileProperty->IsValidExtension(relativePath) || IsValidArtworkFile(path)))
{
throw std::logic_error("Permission denied. Invalid file extension for this directory.");
}
@@ -2162,6 +2182,52 @@ std::string Storage::RenameFilePropertyFile(
std::filesystem::rename(oldPath, newPath);
return newPath;
}
std::string Storage::CopyFilePropertyFile(
const std::string &oldRelativePath,
const std::string &newRelativePath,
const UiFileProperty &uiFileProperty,
bool overwrite)
{
if (uiFileProperty.directory().empty())
{
throw std::runtime_error("Invalid UI File Property.");
}
std::filesystem::path oldPath = this->GetPluginUploadDirectory() / uiFileProperty.directory() / oldRelativePath;
if (!this->IsValidSampleFileName(oldPath))
{
throw std::runtime_error("Invalid file name.");
}
if (!std::filesystem::exists(oldPath))
{
throw std::runtime_error("Original path does not exist.");
}
std::filesystem::path newPath = this->GetPluginUploadDirectory() / uiFileProperty.directory() / newRelativePath;
if (!this->IsValidSampleFileName(newPath))
{
throw std::runtime_error("Invalid file name.");
}
if (std::filesystem::exists(newPath))
{
if (std::filesystem::is_directory(newPath))
{
throw std::runtime_error("A directory with that name already exists.");
}
else
{
if (!overwrite) {
return ""; // signal a portential overwrite.
} else {
fs::remove(newPath);
}
}
}
std::filesystem::create_hard_link(oldPath, newPath);
return newPath;
}
void Storage::FillSampleDirectoryTree(FilePropertyDirectoryTree *node, const std::filesystem::path &directory) const
{
+6
View File
@@ -156,6 +156,7 @@ public:
bool IsInUploadsDirectory(const std::filesystem::path&path) const;
bool IsInAudioTracksDirectory(const std::filesystem::path&path) const;
bool IsValidArtworkFile(const std::filesystem::path& fullPath);
FileRequestResult GetFileList2(const std::string&relativePath,const UiFileProperty&fileProperty);
@@ -232,6 +233,11 @@ public:
const std::string&oldRelativePath,
const std::string&newRelativePath,
const UiFileProperty&uiFileProperty);
std::string CopyFilePropertyFile(
const std::string&oldRelativePath,
const std::string&newRelativePath,
const UiFileProperty&uiFileProperty,
bool overwrite = false);
FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty,const std::filesystem::path&selectedPath);
};