extract specific part from path& audio filename in teensy 4.1

charnjit

Well-known member
i have 4 files in folder name SFX_1 . and Folder name SFX_1 is in folder nameAUDIO as below
i create array to pick file name to play in wav player
Code:
char *wav_file[4] = {"/AUDIO/SFX_1/water.wav","/AUDIO/SFX_1/rain.wav","/AUDIO/SFX_1/air.wav","/AUDIO/SFX_1/crash.wav"};
.
here please look at wav_file[1] is = /AUDIO/SFX_1/rain.wav.
i want to skip /AUDIO/SFX_1/ from start (it is 13 letters) and skip .wav from last (it is 3 letters & a dot) from wav_file[1]
and want to get rain file name only as String without extention name.
if wav_file[1] is = /AUDIO/SFX_1/rain.wav. and
StringPrintable_name should be rain.
what function should be call to return Printable_name from wav_file[1] to print on Display or Serial monitor??????
Please reply!!
 
Maybe try this sketch out:
Code:
// path_parser_test.ino

/* This is an example of parsing a full directory chain
 * and filename with an extention. All four parts are
 * seperated out into seperate strings.
 * dn is String for directory chain.
 * bn is String for base filename.ext.
 * fn is String for filename only.
 * ext is String for file extention.
 * Note: The bn string is used with both stripExt() and
 *       fileExt() functions in that order as used below.
*/

//------------------------------------------------------
// Parse out the the directory chain. (/AUDIO/SFX_1)
//------------------------------------------------------
char *dirName(char *path) {
    char dirpath[256];
    char *dirchain = NULL;
    char *basename = NULL;
    strcpy(dirpath,path);
    basename = strrchr(path, '/');
    dirchain = strndup(dirpath, strlen(dirpath) - (strlen(basename)));
    return dirchain;
}

//------------------------------------------------------
// Parse out the filename and extention. (rain.wav)
//------------------------------------------------------
char *baseName(char *path) {
    char *basename = NULL;
    basename = strrchr(path, '/');
    return basename+1; // Strip leading '/'.
}

//------------------------------------------------------
// Parse out the filename only. (rain)
//------------------------------------------------------
char *stripExt(char *path) {
    char fnameExt[256];
    char *fname = NULL;
    char *basename = NULL;
    strcpy(fnameExt,path);
    basename = strrchr(path, '.');
    fname = strndup(fnameExt, strlen(fnameExt) - (strlen(basename)));
    return fname;
}

//=========================================
// Get file extension name. (wav)
//=========================================
char *fileExt(char *string) {
    char str[256];
    strcpy(str,string);
    int len = strlen(str);
    int len1 = len - strlen(strtok(str,"."))-1;
    return(string+len-len1);
}

// Full path name to be parsed.
char *path = (char *)"/AUDIO/SFX_1/rain.wav";

char *dn; // String pointer for directory chain.
char *bn; // String pointer for base filename.ext.
char *fn; // String pointer for filename only.
char *ext; // String pointer for file extention.

void setup() {
 
  // Wait for port to open:
  while (!Serial && millis() < 5000) {
    yield(); // wait for serial port to connect.
  }
  Serial.printf("%cFull path parsing example.\n\n",12);
 
  dn = dirName(path); // Parse out directory chain.
  bn = baseName(path); // Parse out filename.ext.
  fn = stripExt(bn); // Parse out filename only.
  ext = fileExt(bn); // Parse out extention only.
 
  // Print out all for parts.
  Serial.printf("Directory chain = %s\n", dn);
  Serial.printf("filename.ext = %s\n", bn);
  Serial.printf("filename = %s\n", fn);
  Serial.printf("extention = %s\n", ext);
  Serial.println("\ndone!");
}

void loop()
{
  // nothing happens after setup finishes.
}

Probably could be optimized better, but it works. Mostly taken from my DiskIO library:)
EDIT: output should resemble:
Code:
Full path parsing example.

Directory chain = /AUDIO/SFX_1
filename.ext = rain.wav
filename = rain
extention = wav

done!
 
Last edited:
If you use @wwatson's code, or something like it, you should add
C++:
free(dn);
free(fn);
when you've finished using them, because strndup() allocates space from the heap. If you don't, and make repeated calls to either dirName() or stripExt(), then you will have a memory leak and your sketch will eventually fail.

If you insist on using the String class, then please just take the trouble to use a web search to find its documentation. Everything you need to know is out there. Asking people to do your homework for you on essentially easy tasks is a sure-fire way to get ignored when you actually do have a difficult problem.
 
thank you wwatson,
your given example code exact same what i want to use , actually i am trying it to see maximum files name fit on display
I also tried folder name change length /AUDIO/SFX_001 instead of /AUDIO/SFX_1. yes it is working
I also tried file name change length Hardcrash instead of crash. yes it is also working
Code:
Full path parsing example2.

Directory chain = /AUDIO/SFX_001
filename.ext = Hardcrash.wav
filename = Hardcrash
extention = wav

done!
but post #3 alert me attension .by h4n0nnym0u5e... about free memory. it is true . i will use assign char fn bn dn ext so many times. should i need call
Code:
free(dn);
free(fn);
free(bn);
free (ext);
every time after use them on display like
C++:
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_GREEN);
  tft.setTextSize(1);
  tft.setCursor(0, 50);
  tft.print(fn);
  free(fn);
??????
 
@charnjit - the only time you have to free memory is after calling dirName() or stripExt() functions because those two functions use the strdup() function. Freeing memory should be done after calling either one of those functions.
Code:
free(dn);
free(fn);
free(bn);<------ Invalid does not call strdup() and will cause hard crash!
free (ext);<------ Invalid does not call strdup() and will cause hard crash!

Here is the revised version of setup() showing where the free() function is being called:
Code:
void setup() {
 
  // Wait for port to open:
  while (!Serial && millis() < 5000) {
    yield(); // wait for serial port to connect.
  }
  Serial.printf("%cFull path parsing example.\n\n",12);
 
  dn = dirName(path); // Parse out directory chain.
  free(dn); // <-------------------------- Added
  bn = baseName(path); // Parse out filename.ext.
  fn = stripExt(bn); // Parse out filename only.
  free(fn);// <-------------------------- Added
  ext = fileExt(bn); // Parse out extention only.
 
  // Print out all for parts.
  Serial.printf("Directory chain = %s\n", dn);
  Serial.printf("filename.ext = %s\n", bn);
  Serial.printf("filename = %s\n", fn);
  Serial.printf("extention = %s\n", ext);
  Serial.println("\ndone!");
}
This is what @h4yn0nnym0u5e was taking about in post #3...
 
You are right, more like this:
Code:
void setup() {
 
  // Wait for port to open:
  while (!Serial && millis() < 5000) {
    yield(); // wait for serial port to connect.
  }
  Serial.printf("%cFull path parsing example.\n\n",12);
 
  dn = dirName(path); // Parse out directory chain.
  bn = baseName(path); // Parse out filename.ext.
  fn = stripExt(bn); // Parse out filename only.
  ext = fileExt(bn); // Parse out extention only.
 
  // Print out all for parts.
  Serial.printf("Directory chain = %s\n", dn);
  Serial.printf("filename.ext = %s\n", bn);
  Serial.printf("filename = %s\n", fn);
  Serial.printf("extention = %s\n", ext);
  Serial.println("\ndone!");
  free(dn);// <-------------------------- Added
  free(fn);// <-------------------------- Added
}
To big of a hurry 😀
 
the following do not copy any strings at all just store the ptr and as you want the last part of the string it's allready null-terminated which mean that it can just be used as is:
C++:
#include <cstdio>
#include <cstring>

int main() {
    const char *wav_file[4] = {
        "/AUDIO/SFX_1/water.wav",
        "/AUDIO/SFX_1/rain.wav",
        "/AUDIO/SFX_1/air.wav",
        "/AUDIO/SFX_1/crash.wav"
    };

    for (int i = 0; i < 4; ++i) {
        const char *filename = strrchr(wav_file[i], '/');
        if (filename) filename++; // move past the last '/'
        else filename = wav_file[i]; // no '/' found, use full string

        printf("Base name: %s\n", filename);
    }

    return 0;
}
 
Ok, I read the post too quickly earlier — sorry about that.

If you only want to print the filename (without the path or extension) and avoid creating temporary String/heap objects, you can do it like this using raw pointers — no extra allocations needed:

C++:
#include <Adafruit_ILI9341.h> // or your specific TFT lib
Adafruit_ILI9341 tft;

void PrintFilenameDirectToSerial(const char* fullPath)
{
    // Skip path
    const char* start = strrchr(fullPath,'/'); // points to the filename + ext
    if (start == nullptr) start = fullPath;    // fallback to full path
    else start++; // skip past '/'

    // Find dot before extension
    const char* end = strrchr(start, '.');           // points to '.'
    if (end == nullptr) end = start + strlen(start); // fallback if no dot

    // Print directly to Serial (zero-copy)
    Serial.write((const uint8_t*)start, end - start); // prints the filename only
    Serial.println();
}

void PrintFilenameDirectToDisplay(const char* fullPath, int x, int y)
{
    // Skip path
    const char* start = strrchr(fullPath,'/'); // points to filename + ext
    if (start == nullptr) start = fullPath;    // fallback to full path
    else start++; // skip past '/'

    // Find dot before extension
    const char* end = strrchr(start, '.');           // points to '.'
    if (end == nullptr) end = start + strlen(start); // fallback if no dot

    // Print directly to Display (zero-copy)
    tft.setCursor(x, y);
    tft.write((const uint8_t*)start, end - start); // prints the filename only
}

void setup()
{
    // safer to have const char* as the strings are not mutable anyway
    const char *wav_file[4] = {"/AUDIO/SFX_1/water.wav","/AUDIO/SFX_1/rain.wav","/AUDIO/SFX_1/air.wav","/AUDIO/SFX_1/crash.wav"};

    int textSize = 1;
    int lineHeight = 8 * textSize; // 8 pixels per character line (default font)
    
    tft.fillScreen(ILI9341_BLACK);
    tft.setTextColor(ILI9341_GREEN);
    tft.setTextSize(textSize);
    
    for (int i=0;i<4;i++)
    {
        const char* fullPath = wav_file[i]; // deref for faster reuse
        PrintFilenameDirectToSerial(fullPath);
        PrintFilenameDirectToDisplay(fullPath, 0, i*lineHeight);
    }
}

void loop()
{
 
}

if you however want to pass the 'substring' around you can create a simple StringView (ZeroCopyString):
C#:
struct StringView
{
    const char* start;
    int length;
    
    inline int Length() const
    {
        if (start == nullptr) return 0;
        return length;
    }
    /** create a empty StringView */
    StringView():start(nullptr),length(0) {}
    /** creates a StringView from a null terminated string */
    explicit StringView(const char* ptr)
    {
        // Ensure internal state consistency
        if (ptr == nullptr)
        {
            start = nullptr;
            length = 0;
            return;
        }
        start = ptr;
        length = strlen(start);
    }
    /** Creates a StringView from a buffer that may or may not be null-terminated. */
    StringView(const char* ptr, size_t maxLen)
    {
        // Ensure internal state consistency
        if (ptr == nullptr)
        {
            start = nullptr;
            length = 0;
            return;
        }
        start = ptr;
        length = strnlen(ptr, maxLen);
        
    }
    StringView(const char* start, const char* end)
    {
        // Ensure internal state consistency
        if (start == nullptr || end == nullptr || end < start)
        {
            this->start = nullptr;
            length = 0;
            return;
        }
        this->start = start;
        length = end-start;
    }
};

StringView GetFileNameOnly(const char* fullPath) {
    // failsafe
    if (fullPath == nullptr) return StringView();
    
    // Skip path
    const char* start = strrchr(fullPath,'/'); // filename plus extension
    if (start == nullptr) start = fullPath;    // fallback to full path
    else start++; // skip past '/'
    // Find dot before extension
    const char* end = strrchr(start, '.');     // points to '.'
    if (end == nullptr) end = start + strlen(start); // fallback if no dot
    
    return StringView(start, end);
}

void setup()
{
    const char *wav_file[4] = {"/AUDIO/SFX_1/water.wav","/AUDIO/SFX_1/rain.wav","/AUDIO/SFX_1/air.wav","/AUDIO/SFX_1/crash.wav"};
    
    for (int i=0;i<4;i++)
    {
        StringView svFileName = GetFileNameOnly(wav_file[i]);
        
        // 'print' StringView to Serial
        Serial.write((const uint8_t*)svFileName.start, svFileName.Length());
        Serial.println();
    }
}
void loop()
{
 
}
 
the following do not copy any strings at all just store the ptr and as you want the last part of the string it's allready null-terminated which mean that it can just be used as is:
C++:
#include <cstdio>
#include <cstring>

int main() {
    const char *wav_file[4] = {
        "/AUDIO/SFX_1/water.wav",
        "/AUDIO/SFX_1/rain.wav",
        "/AUDIO/SFX_1/air.wav",
        "/AUDIO/SFX_1/crash.wav"
    };

    for (int i = 0; i < 4; ++i) {
        const char *filename = strrchr(wav_file[i], '/');
        if (filename) filename++; // move past the last '/'
        else filename = wav_file[i]; // no '/' found, use full string

        printf("Base name: %s\n", filename);
    }

    return 0;
}
Or you could quickly hack it and do something like instead of printf
Serial.print("Base name: ");
Serial.write(filename, strlen(filename) - 4);
Assuming they all have .wav on it...
else you could use strrchar(filename, '.');
to find the last . and generate the count from the two pointers...
 
Back
Top