Author Topic: How to get metadata from various format files?  (Read 842 times)

trojannemo

  • Posts: 143
Hi there. I created a little program that accepts various format audio files and lets you convert to 5 target formats by drag dropping your file on the correct button.
All done using BASS and many addons. It works great.

I know how to write metadata to most of the 5 output formats, including adding album art, since I've done that before with BASS.

But now i'm stuck at a step I haven't had to deal with before...reading from the input file (which can be .ogg, .mp3, .alac, .wma, .flac, .opus or .wav) to then apply the metadata to the output file.

I've looked at the documentation for ChannelGetTags and it makes no sense to me given that I need to get the Artist, Song, Album Name, Track Name, and Album Art (where present).
No matter what I've tried I'm drawing a blank on how to get useful data that I can get then apply when exporting the file.

Can you point me in a more specific direction than just looking at ChannelGetTags? Maybe a sample?


EDIT: Found the Tags addon and now I can read tags from all these source files. Great! Only thing i'm still struggling with is reading the album art. I'm testing with a file that I know has a large album art. Using the following, I still get a null image placeholder:

EDIT2: I can read and save the album art for .mp3 files. However .flac and .m4a come back as being null even though I know for sure it has album art.

Code: [Select]
BassTags.ReadPictureTAGs = true;

var album_art = metadata.PictureGetImage(0);
if (album_art != null)
{
    album_art.Save(input_file.Replace(input_ext, ".jpg"));
}
TagPicture art = metadata.PictureGet(0);
if (art != null)
{
    File.WriteAllBytes(input_file.Replace(input_ext, ".jpg"), art.Data);
}


Thanks!
« Last Edit: 20 Jul '24 - 17:31 by trojannemo »

Ian @ un4seen

  • Administrator
  • Posts: 26033
Re: How to get metadata from various format files?
« Reply #1 on: 23 Jul '24 - 12:43 »
Images in FLAC files are available from BASS_ChannelGetTags with BASS_TAG_FLAC_PICTURE+n (0=first). That gives a TAG_FLAC_PICTURE structure containing the image data and info on its format. Please see the TAG_FLAC_PICTURE documentation for details.

BASS doesn't currently provide access to images in MP4 files. I'll check if something similar to BASS_TAG_FLAC_PICTURE can be added for that.

trojannemo

  • Posts: 143
Re: How to get metadata from various format files?
« Reply #2 on: 26 Jul '24 - 05:48 »
Thanks Ian.

I can now read, save to file, and apply to my output file the source FLAC album art. But the TAGS addon doesnt seem to read FLAC data so I'm still stuck on that. How do I get the metadata from a FLAC stream similarly to how I do with the TAGS addon?

Code: [Select]
var metadata = BassTags.BASS_TAG_GetFromFile(input_file);
artist = metadata.artist;
title = metadata.title;
album = metadata.album;
genre = metadata.genre;
track = metadata.track;
year = metadata.year;

This works wonderfully for most input audio types except FLAC it seems.

Ian @ un4seen

  • Administrator
  • Posts: 26033
Re: How to get metadata from various format files?
« Reply #3 on: 26 Jul '24 - 13:28 »
That BassTags class isn't actually related to the TAGS add-on but rather something that's provided by BASS.Net. I'm not sure it currently supports BASS_TAG_FLAC_PICTURE tags. It should support text FLAC tags though, which are provided by BASS via the BASS_TAG_OGG option. If you aren't seeing them then perhaps you're using Media Foundation instead of BASSFLAC to handle FLAC files? FLAC tags are only available when using BASSFLAC. You can check what's being used via the "ctype" value from BASS_ChannelGetInfo.

Regarding MP4 cover art, here's a BASS update that adds a new BASS_ChannelGetTags option for that ("covr" atoms):

   www.un4seen.com/stuff/bass.zip

The new option and its return value is defined like this:

Code: [Select]
#define BASS_TAG_MP4_PICTURE 0x1400 // + index #, picture : TAG_BINARY structure

typedef struct {
const void *data;
DWORD length;
} TAG_BINARY;

Usage example:

Code: [Select]
const TAG_BINARY *pic = (const TAG_BINARY*)BASS_ChannelGetTags(stream, BASS_TAG_MP4_PICTURE);
// read image from pic->data here

Of course, this stuff isn't in BASS.Net yet, but perhaps it's possible to define a TAG_BINARY class yourself in the meantime? I'm not a .Net user myself, so unfortunately I can't help much with the specifics of that.

trojannemo

  • Posts: 143
Re: How to get metadata from various format files?
« Reply #4 on: 26 Jul '24 - 16:13 »
So here's where I am.

Quote
Text metadata and album art working
MP3 to FLAC
MP3 TO OPUS
FLAC TO MP3
FLAC TO OPUS


Text metadata working (album art not yet)
(I can read the OPUS METADATA_BLOCK_PICTURE data, which looks like a Base64 string but it's failing to convert to an image so I'm assuming i'm missing something)
OPUS TO MP3
OPUS TO FLAC
OPUS TO OGG
MP3 TO OGG
FLAC TO OGG
M4A TO MP3
M4A TO OGG
M4A TO FLAC
M4A TO OPUS


Nothing working yet
Anything to and from WAV


I tried to integrate your update bass.dll into my workflow and even resorted to my good friend ChatGPT. Neither I nor ChatGPT could get it to work as intended. Here's the current C# code:

Code: [Select]
const int BASS_TAG_MP4_PICTURE = 0x1400; // + index #, picture : TAG_BINARY structure

[DllImport("bass.dll", CharSet = CharSet.Auto)]
public static extern IntPtr BASS_ChannelGetTags(int handle, BASSTag tags);

public struct TagBinary
{
    public IntPtr Data;
    public uint Length;

    public TagBinary(IntPtr data, uint length)
    {
        Data = data;
        Length = length;
    }
}

Code: [Select]
IntPtr tagBinaryPtr = BASS_ChannelGetTags(BassStream, (BASSTag)BASS_TAG_MP4_PICTURE);
if (tagBinaryPtr == IntPtr.Zero)
{
    Console.WriteLine("Tags not found or error.");
    return;
}

TagBinary tagBinary = Marshal.PtrToStructure<TagBinary>(tagBinaryPtr);
uint length = tagBinary.Length;
if (length == 0)
{
    Console.WriteLine("No data length.");
    return;
}

byte[] imgData = new byte[length];
Marshal.Copy(tagBinary.Data, imgData, 0, (int)length);

try
{
    using (MemoryStream ms = new MemoryStream(imgData))
    {
        using (System.Drawing.Image image = System.Drawing.Image.FromStream(ms))
        {
            image.Save(art);
            Console.WriteLine("Image saved successfully.");
        }
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Failed to process image data: {ex.Message}");
}

Ian @ un4seen

  • Administrator
  • Posts: 26033
Re: How to get metadata from various format files?
« Reply #5 on: 26 Jul '24 - 16:30 »
To check that you're receiving the image data fine from the BASS_TAG_MP4_PICTURE tag, you could try writing it to a file and then viewing that file in other software. If that fails, also check that other software is able to display the image directly from the MP4 file.

trojannemo

  • Posts: 143
Re: How to get metadata from various format files?
« Reply #6 on: 26 Jul '24 - 16:33 »
Looking at it in HxD, this is a .m4a ALAC file, I don't think that's what you were targeting with this new function, correct? Or when you say MP4 you are referring to all types of .m4a files like AAC and ALAC?

So many formats and codecs I don't know how you keep up with it all.

Chris

  • Posts: 2210
Re: How to get metadata from various format files?
« Reply #7 on: 26 Jul '24 - 16:41 »
Just load that via the Plugin System in bass (e.g bassflac.dll) with BASS_PluginLoad.....
Thats all

trojannemo

  • Posts: 143
Re: How to get metadata from various format files?
« Reply #8 on: 26 Jul '24 - 16:42 »
Not sure what you're referring to. Ian modified the base bass.dll for this new function. How do I load bass.dll as a plugin to bass.dll?
Not to mention your answer isn't really addressing any of the questions I've posed so far.

Ian @ un4seen

  • Administrator
  • Posts: 26033
Re: How to get metadata from various format files?
« Reply #9 on: 26 Jul '24 - 17:32 »
The new option will apply to ALAC MP4 files too. Is BASS_ChannelGetTags returning a non-0 value? If so, BASS is finding something. Please try writing it to a file and then see if that file is valid.

trojannemo

  • Posts: 143
Re: How to get metadata from various format files?
« Reply #10 on: 26 Jul '24 - 17:38 »
I don't know how to print an IntPtr to file. Seems to be returning zero when I print it to console. Which is also why the code isn't executing because it only executes if the value isn't zero.

Ian @ un4seen

  • Administrator
  • Posts: 26033
Re: How to get metadata from various format files?
« Reply #11 on: 26 Jul '24 - 17:49 »
If you upload/link the file in question, I'll have a look here.

   ftp.un4seen.com/incoming/

trojannemo

  • Posts: 143
Re: How to get metadata from various format files?
« Reply #12 on: 26 Jul '24 - 17:56 »
I already had it on my server waiting for you to ask  ;)

https://keepitfishy.com/files/dominus.zip

This is a random classical song I found online since I had no .m4a files of my own. I have no problem copying the metadata using the Tags Add-on except for the album art. With the ChannelGetTags I'm getting nothing, as mentioned above.

Thanks!

Ian @ un4seen

  • Administrator
  • Posts: 26033
Re: How to get metadata from various format files?
« Reply #13 on: 26 Jul '24 - 18:14 »
The BASS_TAG_MP4_PICTURE option is working fine with that file here, so it seems like your code for that may need some tweaking. Try stepping through the code in the debugger to see if/where it's failing.

Btw, when I said to write the tag to file, I mean the data in the TAG_BINARY struct, eg. the "imgData" array content in your code above. You can then try loading the written file in an image viewer to confirm whether it's valid. That's what I did here, and it produced a valid PNG file, so it should there too once the code is working.

trojannemo

  • Posts: 143
Re: How to get metadata from various format files?
« Reply #14 on: 26 Jul '24 - 18:32 »
Hi Ian. I can't write the DATA to file because there's no DATA. My BASS_GetChannelTags is returning a IntPtr.Zero and that IntPtr has no data in it.

I've rewritten the whole thing in a different way. Same behavior.

Code: [Select]
[DllImport("bass.dll", CharSet = CharSet.Auto)]
public static extern IntPtr BASS_ChannelGetTags(int handle, int tag);

[StructLayout(LayoutKind.Sequential)]
public struct TAG_BINARY
{
    public IntPtr data;
    public int length;
}

IntPtr tagPtr = BASS_ChannelGetTags(BassStream, 0x1400); // BASS_TAG_MP4_PICTURE
if (tagPtr != IntPtr.Zero)
{
    TAG_BINARY tag = (TAG_BINARY)Marshal.PtrToStructure(tagPtr, typeof(TAG_BINARY));
    byte[] imageData = new byte[tag.length];
    Marshal.Copy(tag.data, imageData, 0, tag.length);

    // Save the image to a file
    File.WriteAllBytes(art, imageData);
    Console.WriteLine("Album art saved as album_art.jpg");
}
else
{
    Console.WriteLine("No album art found");
}

Could it be an issue with me using the Bass.Net wrapper and invoking the .dll for just this one function?

trojannemo

  • Posts: 143
Re: How to get metadata from various format files?
« Reply #15 on: 26 Jul '24 - 18:55 »
Some more debugging led me to this:

Code: [Select]
IntPtr tagPtr = BASS_ChannelGetTags(BassStream, 0x1400); // BASS_TAG_MP4_PICTURE

Immediately after if I print out the error code, I get BASS_ERROR_NOTAVAIL, which the documentation says it means "The requested tags are not available."

So I think this is definitely an issue of the translation from C to C#.

trojannemo

  • Posts: 143
Re: How to get metadata from various format files?
« Reply #16 on: 26 Jul '24 - 19:40 »
Going through the bass.h file included in the updated file you provided, I found this:

Code: [Select]
#define BASS_TAG_APE_BINARY 0x1000 // + index #, binary APE tag : TAG_APE_BINARY structure
#define BASS_TAG_MP4_PICTURE 0x1400 // + index #, picture : TAG_BINARY structure

What exactly do you mean by + index #? Does that mean if the album art is at index 0 it would still be 0x1400 or would it be 0x14000? or 0x1401 or 0x14001 if it's at index 1? Just making sure it's not something as simple as that that I'm missing.

trojannemo

  • Posts: 143
Re: How to get metadata from various format files?
« Reply #17 on: 26 Jul '24 - 22:40 »
Downloaded another classical song in M4A format and added album art to it. This one works fine with the new changes. But this is AAC. My original test file is ALAC. And you said it worked for you? I wonder what I can possibly be doing wrong.

Ian @ un4seen

  • Administrator
  • Posts: 26033
Re: How to get metadata from various format files?
« Reply #18 on: 29 Jul '24 - 12:59 »
That's strange. Are you trying both MP4 files with the exact same executable? If not, perhaps the ALAC test case is loading an older BASS.DLL? That would result in BASS_ChannelGetTags failing with BASS_ERROR_NOTAVAIL. BASS_GetVersion will return 0x0204111f (2.4.17.31) if the updated DLL is loaded.

trojannemo

  • Posts: 143
Re: How to get metadata from various format files?
« Reply #19 on: 30 Jul '24 - 00:32 »
Check it out. It seems to have loaded the correct DLL.



Note that it says success because the main job is to convert the audio, the album art would be icing on the cake.
The relevant code for this part is here:

Code: [Select]
var tagPtr = BASS_ChannelGetTags(BassStream, BASS_TAG_MP4_PICTURE);
Console.WriteLine("Bass Version: " + Bass.BASS_GetVersion().ToString("X"));
Console.WriteLine("BASS Error: " + Bass.BASS_ErrorGetCode());
if (tagPtr != IntPtr.Zero)
{
    TAG_BINARY tag = (TAG_BINARY)Marshal.PtrToStructure(tagPtr, typeof(TAG_BINARY));
    byte[] imageData = new byte[tag.length];
    Marshal.Copy(tag.data, imageData, 0, tag.length);
    File.WriteAllBytes(art, imageData);                       
}

It never gets to the TAG_BINARY line because the tagPtr variable is always 0.

The same exact code works with the AAC .m4a file. The only thing I'm thinking may be affecting things is that according to a tag inspector I found it says the ALAC .m4a file has a .png album art file whereas the AAC .m4a file has a JPG album art file. Could / should that be making a difference?

Ian @ un4seen

  • Administrator
  • Posts: 26033
Re: How to get metadata from various format files?
« Reply #20 on: 30 Jul '24 - 17:23 »
The image format won't make a difference to BASS_TAG_MP4_PICTURE availability, as BASS just reads and forwards the image data without checking its format. The decoder (eg. BASSALAC or Media Foundation) shouldn't make any difference either, but what is your stream's "ctype" value from BASS_ChannelGetInfo? If it's BASS_CTYPE_STREAM_ALAC (0x10e00), please try removing BASSALAC so that Media Foundation is used instead, and see if that makes any difference.

trojannemo

  • Posts: 143
Re: How to get metadata from various format files?
« Reply #21 on: 31 Jul '24 - 00:39 »
That worked. Removing the alac add-on and just using the newer bass.dll with the new tag worked for both the ALAC and AAC files, both of which are now being read as Media Foundation.

Can I get rid of the ALAC and AAC dlls and bass will continue to work with those input files?

Thanks so much for your patience Ian.

Ian @ un4seen

  • Administrator
  • Posts: 26033
Re: How to get metadata from various format files?
« Reply #22 on: 31 Jul '24 - 17:18 »
Ah, I think I know what the problem with BASSALAC is. Are you using BASS_ALAC_StreamCreateFile rather than the plugin system (BASS_PluginLoad + BASS_StreamCreateFile) to open the file? BASSALAC currently doesn't ask BASS to handle tag reading in that case, but this was due to change in the next BASSALAC release. I have now pushed out a 2.4.1 release for that (and a few other tweaks), so please get it from the BASS webpage and see if you still have the problem with it.

Regarding using Media Foundation instead, ALAC support was introduced in Windows 10. If you don't need to support any older Windows versions then you could indeed leave out BASSALAC. In the case of AAC, support for that was introduced in Windows 7 (or Vista with an update).

trojannemo

  • Posts: 143
Re: How to get metadata from various format files?
« Reply #23 on: 1 Aug '24 - 00:05 »
Using the updated bassalac did the trick. Now my code works with aac m4a and alac m4a, opus, mp3, and flac  :)

Now if I could figure out how to add album art to ogg files...

Ian @ un4seen

  • Administrator
  • Posts: 26033
Re: How to get metadata from various format files?
« Reply #24 on: 1 Aug '24 - 12:13 »
Good to hear that the BASSALAC update is working well.

Regarding images in OGG files, there are 2 methods documented here:

   https://wiki.xiph.org/VorbisComment

The simplest method is to store a base64-encoded version of the image data in a "COVERART" tag. The other (recommended) method is to store a base64-encoded version of FLAC's METADATA_BLOCK_PICTURE structure (same info as in BASSFLAC's TAG_FLAC_PICTURE structure) in a "METADATA_BLOCK_PICTURE" tag.