Changing file extensions while copying into a new sub directory C++ - c++

I have a directory that contains files (mostly images) and am trying to create a new sub directory named Thumbnails that will copy all images and replace the file extension from ".jpg" || ".png" to ".thumb".
So far my code will copy all the files into the new sub directory but I can't find a way to ignore all the files that are not images (it's copying every single file in the original directory) and to change the extension of the files that where copied.
#include <iostream>
#include <filesystem>
using namespace std;
namespace fs = std::experimental::filesystem;
//Predefined Functions
void createThumbnails(string path);
int main(void)
{
string DailyFolderPath = ".../images";
createThumbnails(DailyFolderPath);
return 0;
}
void createThumbnails(string path)
{
//Create Thumbnail Sub Directory
string newThumbDir = path + "/Thumbnails";
fs::create_directories(newThumbDir);
//Copy files into Thumbnail Directory
fs::copy(path, newThumbDir);
for (const auto & p : fs::directory_iterator(newThumbDir))
cout << p.path().filename() << endl;
}

Your attempt proposed in comment cannot work because you are passing only filenames instead the full paths to files:
fs::copy_file(p.path().filename(), p.path().filename().replace_extension(".thumb"));
^^^^^ your app doesn't see these files
You can try something like this:
void createThumbnails(string source)
{
fs::path newThumbDir = source + "/Thumbnails";
fs::create_directories(newThumbDir);
for (const auto & p : fs::directory_iterator(source)) // iterate over all files in source dir
{
fs::path fileToCopy = p; // make copy of path to source file
if (fileToCopy.extension() == ".png" || fileToCopy.extension() == ".jpg")
{
fileToCopy.replace_extension(".thumb"); // change extension
fs::path target = newThumbDir / fileToCopy.filename(); // targetDir/name.thumb
fs::copy_file(p,target); // pass full paths
}
}
}

Related

how do i read files in a directory according to their modification time in c++ on windows

Currently I am using dirent.h to iterativly access files in a directory and its sub-directories. It accesses them according to their file names (alphabetically). For some reason I want to access the subdirectories and the files according to their modification time (with the latest modified file being accessed at last). Is it possible to do so?
I am thinking to first traverse through the directories and create a sorted list of the files according to their modification time and use this list to read them accordingly. But I was hoping if there is a better way.
Based on my first thinking and as per the suggestion in the comments.
#include<dirent.h>
typedef struct files {
long long mtime;
string file_path;
bool operator<(const files &rhs)const {
return mtime < rhs.mtime;
}
}files;
using namespace std;
vector<struct files> files_list;
void build_files_list(const char *path) {
struct dirent *entry;
DIR *dp;
if (dp = opendir(path)) {
struct _stat64 buf;
while ((entry = readdir(dp))){
std::string p(path);
p += "\\";
p += entry->d_name;
if (!_stat64(p.c_str(), &buf)){
if (S_ISREG(buf.st_mode)){
files new_file;
new_file.mtime = buf.st_mtime;
new_file.file_path = entry->d_name;
files.push_back(new_file);
}
if (S_ISDIR(buf.st_mode) &&
// the following is to ensure we do not dive into directories "." and ".."
strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")){
//do nothing
}
}
}
closedir(dp);
}
else{
printf_s("\n\tERROR in openning dir %s\n\n\tPlease enter a correct
directory", path);
}
}
void main(){
char *dir_path="dir_path";
build_files_list(dir_path);
sort(files_list.begin(), files_list.end());
//the rest of things to do
}

load/save multiple images from/in a directory - opencv c++

i want to load a lot of images (not sequential names though) from a directory. edit them and then save them in a different directory with their original names if possible.
I load them like that:
glob("/photos/field_new/*.jpg", fn, false);
size_t count = fn.size(); //number of jpg files in images folder
for (size_t i=0; i<count; i++)
images.push_back(imread(fn[i]));
any ideas how i can save them in the directory /photos/results/ ?
and if possible with their original names?
If you have C++17 it will be easy as there is filesystem library in standard (std::filesystem). Other wise I would recomend you to get boost::filesystem which is very similar (you should be good with replacing all of std::filesystem to boost::filesystem).
To load all images from certain folder there are 2 helper functions:
#include <filesystem> //for boost change this to #include <boost/filesystem> and all std:: to boost::
#include <opencv2/opencv.hpp>
bool isSupportedFileType(const std::filesystem::path& pathToFile,
const std::vector<std::string>& extensions)
{
auto extension = pathToFile.extension().string();
std::transform(extension.begin(), extension.end(), extension.begin(), [](char c)
{
return static_cast<char>(std::tolower(c));
});
return std::find(extensions.begin(), extensions.end(), extension) != extensions.end();
}
std::tuple<std::vector<cv::Mat>, std::vector<std::filesystem::path>> loadImages(const std::filesystem::path& path,
const std::vector<std::string>& extensions)
{
std::vector<cv::Mat> images;
std::vector<std::filesystem::path> names;
for (const auto& dirIt : filesystem::DirectoryIterator(path))
{
if (std::filesystem::is_regular_file(dirIt.path()) && isSupportedFileType(dirIt.path(), extensions))
{
auto mask = cv::imread(dirIt.path().string(), cv::IMREAD_UNCHANGED);
if (mask.data != nullptr) //there can be problem and image is not loaded
{
images.emplace_back(std::move(mask));
names.emplace_back(dirIt.path().stem());
}
}
}
return {images, names};
}
You can use it like this (assuming C++17):
auto [images, names] = loadImages("/photos/field_new/", {".jpg", ".jpeg"});
Or (C++11)
auto tupleImageName = loadImages("/photos/field_new/", {".jpg", ".jpeg"});
auto images = std::get<0>(tupleImageName);
auto names = std::get<1>(tupleImageName);
To save you can use this function:
void saveImages(const std::filesystem::path& path,
const std::vector<cv::Mat>& images,
const std::vector<std::filesystem::path>& names)
{
for(auto i = 0u; i < images.size(); ++i)
{
cv::imwrite((path / names[i]).string(), images[i]);
}
}
Like this:
saveImages("pathToResults",images,names);
In this save function it would be good to perform some validation if the number of images is the same as names, otherwise there might be a problem with stepping outside vector boundary.
Since C++ is compatible with C, and if you are using a Unix system, you could use dirent.h (https://pubs.opengroup.org/onlinepubs/7908799/xsh/dirent.h.html). It should be something like this:
#include <dirent.h>
#include <opencv2/opencv.hpp>
#include <string.h>
using namespace std;
using namespace cv;
int main(){
DIR *d;
struct dirent *dir;
string dirname_input="dirname/input";
string dirname_output="dirname/output";
d=opendir(dirname_input.c_str());
if (d){
while ((dir=readdir(d))!=NULL){
string filename=dir->d_name;
if (filename.substr(filename.size() - 4)==".jpg"){
Mat image;
string fullpath_input=dirname_input+'/'+filename;
string fullpath_output=dirname_output+'/'+filename;
image=imread(fullpath_input,1);
// process image
imwrite(fullpath_output,image);
}
}
}
return 0;
}

The best way to ignore files with other extensions when using the C++ experimental <filesystem>?

With the future C++, is there a better way to ignore files with other than wanted extensions than the one shown in the code snippet below?
I am learning the C++ experimental <filesystem> (http://en.cppreference.com/w/cpp/experimental/fs) while writing a simple program that transforms text files from one directory to text file in another directory. The program takes input and output directories via command-line arguments. Only the files with certain extensions (like .csv, .txt, ...) should be processed. The output files should have the .xxx extension.
#include <filesystem>
namespace fs = std::tr2::sys; // the implementation from Visual Studio 2015
...
fs::path srcpath{ argv[1] };
fs::path destpath{ argv[2] };
...
for (auto name : fs::directory_iterator(srcpath))
{
if (!fs::is_regular_file(name))
continue; // ignore the non-files
fs::path fnameIn{ name }; // input file name
// Ignore unwanted extensions (here lowered because of Windows).
string ext{ lower(fnameIn.extension().string()) };
if (ext != ".txt" && ext != ".csv")
continue;
// Build the output filename path.
fs::path fnameOut{ destpath / fnameIn.filename().replace_extension(".xxx") };
... processing ...
}
Basically, your question boils down to, "given a string, how do I determine if it matches one of a number of possibilities?" That's pretty trivial: put the possibilities in a std::set:
//Before loop
std::set<std::string> wanted_exts = {".txt", ".csv"};
//In loop
string ext{ lower(fnameIn.extension().string()) };
if (wanted_exts.find(ext) == wanted_exts.end())
continue;
You can of course keep wanted_exts around for as long as you like, since it probably won't change. Also, if you have Boost.Containers, I would suggest making wanted_exts a flat_set. That will help minimize allocations.
std::tr2::sys was the namespace MSVC used in VS2013 to ship the filesystem TS, but that is actually supposed to be in the std::experimental::v1 namespace; the old namespace has been retained for backwards compatibility. v1 is an inline namespace, so you can drop that from the name and say
namespace fs = std::experimental::filesystem;
Assuming using boost is an option, you can perform filtering of the directory entries using Boost.Range adaptors. And testing for any one of several extensions can be done using boost::algorithm::any_of_equal.
#include <boost/algorithm/cxx11/any_of.hpp>
#include <boost/range/adaptors.hpp>
for(auto const& p :
boost::make_iterator_range(fs::directory_iterator(srcpath), {})
| boost::adaptors::transformed([](auto const& d) {
return fs::path(d); })
| boost::adaptors::filtered([](auto const& p) {
return fs::is_regular_file(p); })
| boost::adaptors::filtered([](auto const& p) {
auto const& exts = { ".txt", ".csv" };
return boost::algorithm::any_of_equal(exts, p.extension().string()); })
) {
// all filenames here will have one of the extensions you tested for
}
The solution of the loop that I have finally chosen...
#include <filesystem>
namespace fs = std::experimental::filesystem;
...
set<string> extensions{ ".txt", ".csv" };
for (auto const& name : fs::directory_iterator(srcpath))
{
if (!fs::is_regular_file(name))
continue;
fs::path fnameIn{ name };
string ext{ lower(fnameIn.extension().string()) };
if (extensions.find(ext) != extensions.end())
{
fs::path fnameOut{ destpath / fnameIn.filename().replace_extension(".xxx") };
processing(fnameIn, fnameOut);
}
}

How can I zip a directory/folder with quazip?

I have a directory with files and folders that I would like to zip. I'm using the qt-project quazip for it. So I thought I write a function that packs all content of a directory including the filestructure.
How can I create the folder in the zip-file? I tried it with QuaZipNewInfo but couldn't make it work.
For example I want to zip the tmp-folder with this content:
tmp/1.txt
tmp/folder1/2.txt
tmp/folder1/3.txt
tmp/folder2/4.txt
tmp/folder2/folder3/5.txt
What I get after extracting the file with a common archive-tool (Archive Utility) is this:
tmp/1.txt
tmp/2.txt
tmp/3.txt
tmp/4.txt
tmp/5.txt
This is what I have so far:
void Exporter::zipFilelist(QFileInfoList& files, QuaZipFile& outFile, QFile& inFile, QFile& inFileTmp)
{
char c;
foreach(QFileInfo file, files) {
if(file.isDir() && file.fileName() != "." && file.fileName() != "..") {
QFileInfoList infoList = QDir(file.filePath()).entryInfoList();
zipFilelist(infoList, outFile, inFile, inFileTmp);
}
if(file.isFile()) {
inFileTmp.setFileName(file.fileName());
inFile.setFileName(file.filePath());
if(!inFile.open(QIODevice::ReadOnly)) {
qDebug() << "testCreate(): inFile.open(): " << inFile.errorString().toLocal8Bit().constData();
}
QuaZipNewInfo info(inFileTmp.fileName(), inFile.fileName());
if(!outFile.open(QIODevice::WriteOnly, info)) {
qDebug() << "testCreate(): outFile.open(): " << outFile.getZipError();
}
while(inFile.getChar(&c)&&outFile.putChar(c)) ;
if(outFile.getZipError()!=UNZ_OK) {
qDebug() << "testCreate(): outFile.putChar(): %d"<< outFile.getZipError();
}
outFile.close();
if(outFile.getZipError()!=UNZ_OK) {
qDebug() << "testCreate(): outFile.close(): %d"<< outFile.getZipError();
}
inFile.close();
}
}
}
And this is how I call the function:
QFileInfoList files = QDir(sourceFolder).entryInfoList();
QFile inFile;
QFile inFileTmp;
QuaZipFile outFile(&zip);
zipFilelist(files, outFile, inFile, inFileTmp);
I don't get any error. When I want to unzip the file it doesn't extract the folders (because I probably don't pack them into the zip!?). So I get all files of all subfolders unziped into one folder.
It seems that in your function you were recursively getting the files in the folders, but not the folders themselves. Try creating a folder to zip the files into when you recurse into looking for the files in the subdirectory.
You may want to look into this answer:
https://stackoverflow.com/a/2598649/1819900
How about the utilities provided by QuaZip?
http://quazip.sourceforge.net/classJlCompress.html
When creating the QuaZipNewInfo object, specify the path and file name to your file as you want to store it in the zip as the first argument, and the path and file name to your file on disk as the second argument. Example:
Adding C:/test/myFile.txt as test/myFile.txt in zip:
QuaZipNewInfo("test/myFile.txt", "C:/test/myFile.txt")
In order to create a folder in your zip file, you need to create an empty file with a name ending with "/". The answer does not include the listing of files/folders but focuses on creating folders in zip file.
QDir sourceRootDir("/path/to/source/folder");
QStringList sourceFilesList; // list of path relative to source root folder
sourceFilesList << "relativePath.txt" << "folder" << "folder/relativePath";
QualZip zip("path/to/zip.zip");
if(!zip.open(QuaZip::mdCreate)){
return false;
}
QuaZipFile outZipFile(&zip);
// Copy file and folder to zip file
foreach (const QString &sourceFilePath, sourceFilesList) {
QFileInfo sourceFI(sourceRootDir.absoluteFilePath(sourceFilePath));
// FOLDER (this is the part that interests you!!!)
if(sourceFI.isFolder()){
QString sourceFolderPath = sourceFilePath;
if(!sourceFolderPath.endsWith("/")){
sourceFolderPath.append("/");
}
if(!outZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(sourceFolderPath, sourceFI.absoluteFilePath()))){
return false;
}
outZipFile.close();
// FILE
} else if(sourceFI.isFile()){
QFile inFile(sourceFI.absoluteFilePath());
if(!inFile.open(QIODevice::ReadOnly)){
zip.close();
return false;
}
// Note: since relative, source=dst
if(!outZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(sourceFilePath, sourceFI.absoluteFilePath()))){
inFile.close();
zip.close();
return false;
}
// Copy
qDebug() << " copy start";
QByteArray buffer;
int chunksize = 256; // Whatever chunk size you like
buffer = inFile.read(chunksize);
while(!buffer.isEmpty()){
qDebug() << " copy " << buffer.count();
outZipFile.write(buffer);
buffer = inFile.read(chunksize);
}
outZipFile.close();
inFile.close();
} else {
// Probably simlink, ignore
}
}
zip.close();
return true;

directory structures C++

C:\Projects\Logs\RTC\MNH\Debug
C:\Projects\Logs\FF
Is there an expression/string that would say go back until you find "Logs" and open it? (assuming you were always below it)
The same executable is run out of "Debug", "MNH" or "FF" at different times, the executable always should save it's log files into "Logs".
What expression would get there WITHOUT referring to the entire path C:\Projects\Logs?
Thanks.
You might have luck using the boost::filesystem library.
Without a compiler (and ninja-copies from boost documentation), something like:
#include <boost/filesystem.hpp>
namespace boost::filesystem = fs;
bool contains_folder(const fs::path& path, const std::string& folder)
{
// replace with recursive iterator to check within
// sub-folders. in your case you just want to continue
// down parents paths, though
typedef fs::directory_iterator dir_iter;
dir_iter end_iter; // default construction yields past-the-end
for (dir_iter iter(path); iter != end_iter; ++iter)
{
if (fs::is_directory(iter->status()))
{
if (iter->path().filename() == folder)
{
return true;
}
}
}
return false;
}
fs::path find_folder(const fs::path& path, const std::string& folder)
{
if (contains_folder(path, folder))
{
return path.string() + folder;
}
fs::path searchPath = path.parent_path();
while (!searchPath.empty())
{
if (contains_folder(searchPath, folder))
{
return searchPath.string() + folder;
}
searchPath = searchPath.parent_path();
}
return "":
}
int main(void)
{
fs::path logPath = find_folder(fs::initial_path(), "Log");
if (logPath.empty())
{
// not found
}
}
For now this is completely untested :)
It sounds like you're asking about a relative path.
If the working directory is C:\Projects\Logs\RTC\MNH\Debug\, the path ..\..\..\file represents a file in the Logs directory.
If you might be in either C:\Projects\Logs\RTC\MNH\ or C:\Projects\Logs\RTC\MNH\Debug\, then no single expression will get you back to Logs from either place. You could try checking for the existence of ..\..\..\..\Logs and if that doesn't exist, try ..\..\..\Logs, ..\..\Logs and ..\Logs, which one exists would tell you how "deep" you are and how many ..s are required to get you back to Logs.