Skip to main content

Notice

Please note that most of the software linked on this forum is likely to be safe to use. If you are unsure, feel free to ask in the relevant topics, or send a private message to an administrator or moderator. To help curb the problems of false positives, or in the event that you do find actual malware, you can contribute through the article linked here.
Topic: foo_custominfo (Read 196654 times) previous topic - next topic
0 Members and 2 Guests are viewing this topic.

foo_custominfo

Introduction
It is basically a 0.9 successor for Quicktag SQL, that allows you to set info fields for files without modifying them. Fields can be set using context menu commands that can be customized in the component preferences. Fields can be accessed in title formatting scripts by using simple variables like %RATING% etc (or functions, which are explained below).

I've also been working on a mini-sdk that allows other components to set their own fields using foo_custominfo. And I hope that components like foo_playcount would some day add an option for this. The sdk also has an interface for implementing other methods for storing the information, in case someone wants to export the info to MySQL or something.

Download here

Installation & Configuration
Just copy the dll file to fb2k\components as usual. Open "Preferences\Tools\Custom info". Select a method for storing custominfo data and press "Switch" button.
- Foobar2k config file: saves all info fields in foobar's configuration file. Reasonable loading time, fast reading of info, fast writing of info. This is also insecure, you will lose all changes since the last config file saving if foobar crashes or something.
- Text file (foobar2kdir\custominfo.txt): Utf-8 encoded text file (which shouldn't be modified though). Slow loading, fast reading of info, fast writing of info. This is also insecure, changes are not written to disk until you exit foobar.
- SQLite database (foobar2kdir\custominfo_sqlite.db): Slow loading, fast reading of info, slow writing of info. Changes are immediately written to disk which is why I personally recommend this option.

Available functions in titleformatting scripts
- $cinfo(X,Y). Returns value with field name X and index Y. Similar to $meta(X,Y).
- $cinfo_num(X). Number of values in field X. Similar to $meta_num(X).
- $cinfo_sep(X,Y,Z). Returns all values in field X separated with strings Y & Z as in $meta_sep(X,Y,Z).
- $cinfo_list(X,Y). Returns all custominfo values for current track using Y as a separator between field name and value, and X as separator between field/value-pairs.

Some advanced features in context menu commands
- You can set several fields at a time by separating field names and values with "|". For example "MYFIELD1|MYFIELD2|MYFIELD3" with corresponding value string "value1|value2|value3".
- Use # in front of field name to add new value, instead of replacing existing values. This can also be combined with the separators, for example: "MYFIELD|#MYFIELD|#MYFIELD".
- You can access system date and time in context menu commands using following variables: %_system_year%, %_system_month%, %_system_day%, %_system_dayofweek%, %_system_hour%, %_system_minute%, %_system_second%, %_system_millisecond%

Limitations
- Custom info cannot be accessed in track info panel "now playing" mode when playback is stopped. Also, masstagger doesn't seem to recognize custom info fields.
- Branching of multivalued custom info fields in Playlist Tree/Browser using %<tag>%-syntax is not possible.

foo_custominfo

Reply #1
A thousand thanks!

After the quickest and simple try I can only say that:

1) where can I see the fields and their contents? It seems that the properties window doesn't show them.

2) if a physical field (written into the file) and a custominfo one has the same name, I can't see the value change in the playlist: does the written one has the precedence over the other?

Thanks again.

foo_custominfo

Reply #2
A thousand thanks!

After the quickest and simple try I can only say that:

1) where can I see the fields and their contents? It seems that the properties window doesn't show them.

2) if a physical field (written into the file) and a custominfo one has the same name, I can't see the value change in the playlist: does the written one has the precedence over the other?

Thanks again.

1) They won't show up in the properties window, because fields are not stored in meta/tech info. I might have to add a special dialog for listing the fields/values. Before that, you'll just have to know the names of the fields you've set.

2) Yes, if a physical field with the same name, it will be prioritized over the custominfo field. Can't help it, that's just the way the metadb display hooks work. But i will add support for some functions like $getcustominfofield(blahblah,index), that will access the custominfo fields directly (also making reading of multivalued fields possible, if I or someone implements some interface for setting them). And you can always use different fieldnames like MYRATING instead of RATING etc..

foo_custominfo

Reply #3

A thousand thanks!

After the quickest and simple try I can only say that:

1) where can I see the fields and their contents? It seems that the properties window doesn't show them.

2) if a physical field (written into the file) and a custominfo one has the same name, I can't see the value change in the playlist: does the written one has the precedence over the other?

Thanks again.

1) They won't show up in the properties window, because fields are not stored in meta/tech info. I might have to add a special dialog for listing the fields/values. Before that, you'll just have to know the names of the fields you've set.

2) Yes, if a physical field with the same name, it will be prioritized over the custominfo field. Can't help it, that's just the way the metadb display hooks work. But i will add support for some functions like $getcustominfofield(blahblah,index), that will access the custominfo fields directly (also making reading of multivalued fields possible, if I or someone implements some interface for setting them). And you can always use different fieldnames like MYRATING instead of RATING etc..


Yes, I think it doesn't make sense to have two tags with the same field names so let's keep them different.

If possible, IMHO, the first thing you should consider is to set the info free from Foobar config file and let them have their own to be stored into.
(I confess I have personal interest into that as by now, with 0.8.3, I'm tagging my files on a PC with a remote control while I'm listening to them and only every now and then I masstag the SQL info physically into the files).

Thanks


foo_custominfo

Reply #5
Sounds really nice.
I'll try it as soon as you've added the field window

cheers perpleXa

foo_custominfo

Reply #6
Very-very-very nice.

I, too, would like to see this written to another source, possibly a file, but something perhaps ODBC compliant, so that I could write to my MySQL database where my master information resides, which I'd be willing to collaborate with you on.

I do have a question and I have not had enough time to diagnose it, but does this plug-in intercept tag requests, such that if I configure this program to use tag %last_played% and then have the value set to %last_played% (as FB2K originally sees it), will this update the .ini file as well, or am I barking up the wrong tree?

Thanks again very much--This was pretty much my last hurdle to totally migrating over to 0.9.x.

First of all, if someone has already done something like this (couldn't find any), let me know and i'll remove this shit.

All comments are welcome...

foo_custominfo

Reply #7
big hurray for this!

i can only hope that with the upcoming sdk someone would make option to store this info in xml files (one xml file for all music files in specific directory) or something like that. too bad that titleformatting hooks can't override values of existing tag fields ...

as for name, i don't feel much creative atm - something like foo_extratags, foo_extendedinfo, though foo_custominfo is just fine.

thank you

edit: i've just realized that with these hooks, one can't use [] construct like [$repeat(•,%rating%) ], but $if(%rating%,$repeat(•,%rating%) ,) instead. could this be taken care of somehow? maybe that would require work on foobar developers' side ...

foo_custominfo

Reply #8
0.1 beta 2
- file moving/deleting within foobar should now work correctly
- library only -option
- all custominfo changes should now be correctly refreshed on the screen
- some fancy functions: $ciget(MYFIELD,<index>) and $cinum(MYFIELD) for working with multi-valued fields. $cilist(<line separator>,<name/value separator>) for listing all info fields and their values.
- options for disabling functions/fields in scripts
- use # in front of fieldname (e.g. #MYFIELD) in context commands to add a field instead of replacing
- lots of internal fixes
- sdk available


as for name, i don't feel much creative atm - something like foo_extratags, foo_extendedinfo, though foo_custominfo is just fine.

Perhaps I should stick with this, cause I can't even change the topic name anymore...

Quote
edit: i've just realized that with these hooks, one can't use [] construct like [$repeat(•,%rating%) ], but $if(%rating%,$repeat(•,%rating%) ,) instead. could this be taken care of somehow? maybe that would require work on foobar developers' side ...

Yes, I think $repeat() doesn't work with normal tags either.  An interesting "feature" of tagz scripts.. the brackets don't work with some functions in the way one might think they do.

I do have a question and I have not had enough time to diagnose it, but does this plug-in intercept tag requests, such that if I configure this program to use tag %last_played% and then have the value set to %last_played% (as FB2K originally sees it), will this update the .ini file as well, or am I barking up the wrong tree?

Hmm.. sorry, I don't quite understand this question..

foo_custominfo

Reply #9
Quote
- some fancy functions: $ciget(MYFIELD,<index>) and $cinum(MYFIELD) for working with multi-valued fields. $cilist(<line separator>,<name/value separator>) for listing all info fields and their values.

... probably are the 38°C, here in Italy, but I can't clearly understand this: do you mind giving us an example, please?
I mean, are they functions to be used let's say, in trackinfo or similar?

Quote
- use # in front of fieldname (e.g. #MYFIELD) in context commands to add a field instead of replacing

Did you mean 'add a value' when you wrote 'add a field' ?
I mean, does this let us add a new value to a multi values field?

Thanks.

foo_custominfo

Reply #10
Quote
- some fancy functions: $ciget(MYFIELD,<index>) and $cinum(MYFIELD) for working with multi-valued fields. $cilist(<line separator>,<name/value separator>) for listing all info fields and their values.

... probably are the 38°C, here in Italy, but I can't clearly understand this: do you mind giving us an example, please?
I mean, are they functions to be used let's say, in trackinfo or similar?
Quote
- use # in front of fieldname (e.g. #MYFIELD) in context commands to add a field instead of replacing

Did you mean 'add a value' when you wrote 'add a field' ?
I mean, does this let us add a new value to a multi values field?

umm... something like that. Fields, values, almost the same thing.  Let's say you would already have a field named MYFIELD, then after this you would have two fields with same name but different values. And you can access them using $ciget(MYFIELD,0) and $ciget(MYFIELD,1). Function $cinum(MYFIELD) would return the number of fields, which in this case would be 2. $cilist() without parameters would return:
MYFIELD=myvalue1
MYFIELD=myvalue2
MYFIELD2=myvalue3
MYFIELD3=myvalue4
etc..

edit: ok, now i see what you meant, and yes, it will add a new value.. It's just that from dev's point of view it's a new field. Well, depending on the implementation of course.

foo_custominfo

Reply #11
I did a quick test. This is what I saw, please tell me if I'm wrong on something.

1)  In trackinfo the functions $cixxx() don't work, in playlist columns they do.

2)  With $ciget() and $cinum() we have to use the field name without the '%%' symbols

3)  With $ciget() the values count starts with 0 (zero) and not with 1 (Zero will return the first item)

4)  With $cilist() we don't have to use any field name as it returns the whole list of all the fields and their values that are associated (by custominfo) with the track (remember we are in columns UI so the function works for each line/track).
Here, I don't understand the line separator: do you mean 'field+its value(s)' separator? (Perhaps they are stored in different lines in the config file...)

This evening I will test some old Quicktag SQL script and let you know the results.

Thanks again.


If we were in the old Fas West... I would already have been shot down: tooooo sloooooooooooow...

foo_custominfo

Reply #12
I did a quick test. This is what I saw, please tell me if I'm wrong on something.

1)  In trackinfo the functions $cixxx() don't work, in playlist columns they do.

confirmed.. and the variables don't seem to work either.. not sure if I can do anything about that.
Quote
2)  With $ciget() and $cinum() we have to use the field name without the '%%' symbols

3)  With $ciget() the values count starts with 0 (zero) and not with 1 (Zero will return the first item)

yep, they should work in a similar way to $meta() and $meta_num().

foo_custominfo

Reply #13
confirmed.. and the variables don't seem to work either.. not sure if I can do anything about that.
If the track info panel directly calls titleformat_object::run(), there is nothing you can do; titleformat display hooks are not used by that method.

yep, they should work in a similar way to $meta() and $meta_num().
That raises the question why they do not use the same naming scheme. I think the name $custom would be too non-specific, but since you are essentially providing per-user track data, $user and $user_num seem appropriate.

foo_custominfo

Reply #14
Can you confirm that I can't masstag from, say, %__situation% to %situation% (with format from field) and then split the fields into multiple values with comma as separator? (Or it's just me...)

Thanks

foo_custominfo

Reply #15
Is there any chance of tagging rar's and zipped archives in the future ?
You're talking to my guy all wrong... It's the wrong tone. Say it again, and i'll stab you in the face with a soldering iron!

foo_custominfo

Reply #16
Some comments about the custominfo SDK:

Code: [Select]
- For all functions that take a metadb_handle as parameter, you can specify the handle to be null.
  This tells custominfo to use the previously accessed storage entry instead of searching for an entry
  associated with certain location. This may greatly improve the speed of operations consisting of
  many calls. And naturally, lock()/unlock() must be used with this feature.
This is just asking for trouble. You may forget to acquire the lock, or may forget to update the "cursor" correctly in more complex operations. A more robust solution would be to use your own brand of handles that wrap access to the information of a single item much like a metadb_handle wraps access to the cached file metadata in foobar2000 itself.

Code: [Select]
- Return values for field processing methods are true on success (even if requested location/field not found),
  and false on critical failures (after which it will automatically be disabled).
That does not make sense. If something really goes horribly wrong, you can throw an exception. With an exception, you also have the chance to pass information about the failure up to the caller (and lastly to the user).

Raw pointers to service instances (like custominfo or metadb_handle): The smart pointer classes in the SDK weren't just created on a whim, they are meant to reduce programming errors. Using "optimizations" like avoiding their use is unnecessary and only encourages an inconsistent programming style.

Code: [Select]
- Custominfo core will take care of removing dead/empty entries, updating locations of moved files, 
  and restricting information storing to media library entries only (according to user settings).
Would it also update inactive backends, or would the information contained in these become outdated over time?

Code: [Select]
    //Returns the name or short description of the storage service in parameter "out". 
    //It is shown in the drop down list of the configuration dialog, and used for identifying the service.
    virtual void get_name(pfc::string_base &out) = 0;
This essentially means that the user will have to reconfigure the component if the name of a storage is ever changed (for whatever reason). If you use a GUID for identification and the name only for the user interface, you can avoid that.

Assuming items in the storage can be accessed efficiently as a linear list: This assumption is wrong for most advanced data structures (including an possible SQL backend). While it is possible to provide iterators for a lot of data structures, the best approach is again to use handles and just ask the storage backend for the handle associated with a given location/metadb_handle.

Updating information in the storage backend on an iten-by-item and even field-by-field basis: That is a performance bottleneck if I ever saw one, especially if you trigger a global update notification for every updated field. Any kind of batch update will be really slow.

Code: [Select]
custominfo* custominfo::get()
{
    static service_ptr_t<custominfo> ptr;
    if (ptr==0)
        service_enum_create_t(ptr,0);
    return ptr.get_ptr();
}
Apart from not being thread-safe, this will crash if the server is unloaded before the client, because at the time the service_ptr_t<> destructor is run, the pointer it contains is no longer valid in that case. It will try to call the release method of that pointer and cause an access violation. You should use static_api_ptr_t<> if you know the service exists (like inside foo_custominfo itself), or use a service_ptr_t<> and manually create the service instance. However, you should manually release that pointer, if the lifetime of the service_ptr_t<> extends beyond initquit::on_quit, which could happen if the service_ptr_t<> is a member of a dialog class.

foo_custominfo

Reply #17
0.1 beta 3
- context submenus working.. whee
- context command importing/exporting using the good old .qtg files.
- storage: text file (foobar2k\custominfo.txt, utf8 encoded)
- storage: Sqlite database, read-only so far, compatible with the database used by Quicktag SQL.  (foobar2k\custominfo_sqlite.db)
- copying storage contents
- sdk removed for the time being


I did a quick test. This is what I saw, please tell me if I'm wrong on something.

1)  In trackinfo the functions $cixxx() don't work, in playlist columns they do.

confirmed.. and the variables don't seem to work either.. not sure if I can do anything about that.

Hmm.. now that i tried it again, it seems that it's working after all?

foosion: Thanks for the comments, i'll remove the sdk for now and think about that stuff.


Can you confirm that I can't masstag from, say, %__situation% to %situation% (with format from field) and then split the fields into multiple values with comma as separator? (Or it's just me...)

Huh? Can you explain that again? So what type of fields are those? And their values? And how would you do the splitting?

foo_custominfo

Reply #18
This is just asking for trouble. You may forget to acquire the lock, or may forget to update the "cursor" correctly in more complex operations. A more robust solution would be to use your own brand of handles that wrap access to the information of a single item much like a metadb_handle wraps access to the cached file metadata in foobar2000 itself.

Yes, this certainly is a problem, which I have to think about. But own handle type would not be very convenient either. I've always hated metadb handles, and two (perhaps three) handle types.. aaargh.
Quote
That does not make sense. If something really goes horribly wrong, you can throw an exception. With an exception, you also have the chance to pass information about the failure up to the caller (and lastly to the user).

But how can I be sure that an exception really is thrown? I mean, throwing an exception is easier to omit than setting up a correct return value. I also planned on adding a method for getting an error message. And of course I have to check for exceptions too.
Quote
Raw pointers to service instances (like custominfo or metadb_handle): The smart pointer classes in the SDK weren't just created on a whim, they are meant to reduce programming errors. Using "optimizations" like avoiding their use is unnecessary and only encourages an inconsistent programming style.

But still, the service pointer templates aren't used everywhere in the foobar2k sdk either. That was the main reason that encouraged me to use pointers..  And as you showed with the custominfo::get(), one can make mistakes with service_ptr_t<> too.
Quote
Would it also update inactive backends, or would the information contained in these become outdated over time?

No, it wouldn't make much sense to update inactive storages. The information in them is outdated anyway, and modifying them without user explicitly selecting the storage doesn't seem appropriate. Though I perhaps should make it more clear that they are not updated.
Quote
This essentially means that the user will have to reconfigure the component if the name of a storage is ever changed (for whatever reason). If you use a GUID for identification and the name only for the user interface, you can avoid that.

Yeah, thought about too. I might change that...
Quote
Assuming items in the storage can be accessed efficiently as a linear list: This assumption is wrong for most advanced data structures (including an possible SQL backend). While it is possible to provide iterators for a lot of data structures, the best approach is again to use handles and just ask the storage backend for the handle associated with a given location/metadb_handle.

True, but no matter how the data is organized, you can always construct a map from one dimensional array to the elements of the internal data structure (not the most effective solution but fast enough). I just didn't want to make things any more complicated by defining own iterator or handle types.
Quote
Updating information in the storage backend on an iten-by-item and even field-by-field basis: That is a performance bottleneck if I ever saw one, especially if you trigger a global update notification for every updated field. Any kind of batch update will be really slow.

Well, I know this, and I never meant this for any large scale operations. The main purpose is really just to set or remove single fields for small number of files, and if someone wants to update million fields at a time, it's really not my problem if it takes a lot of time. I see this as an issue of "ease of coding/extending vs. speed".

LOL, lots of fuzz about sdk that nobody will propably ever use.. well, good practice for me if nothing else.

foo_custominfo

Reply #19
Yes, this certainly is a problem, which I have to think about. But own handle type would not be very convenient either. I've always hated metadb handles, and two (perhaps three) handle types.. aaargh.
Whether you like them or not, metadb handles are the key to fast metadata access in foobar2000.

But how can I be sure that an exception really is thrown? I mean, throwing an exception is easier to omit than setting up a correct return value. I also planned on adding a method for getting an error message. And of course I have to check for exceptions too.
You can be just as sure about an exception being thrown as you can be about the return value being correct. Of course you have to set up a try-catch block, but you can use one for several calls whereas you would have to check to the return value of every call with an approach that does not use exceptions. With exceptions, you can also signal failure and transmit an error message as a single operation, otherwise you have to ensure that the error message is not reset or changed between the time the method in which the error occurred returns and the time the caller tries to retrieve it with your separate method.

True, but no matter how the data is organized, you can always construct a map from one dimensional array to the elements of the internal data structure (not the most effective solution but fast enough). I just didn't want to make things any more complicated by defining own iterator or handle types.
Yes, you can, but that does not mean it is efficient by any means.

Well, I know this, and I never meant this for any large scale operations. The main purpose is really just to set or remove single fields for small number of files, and if someone wants to update million fields at a time, it's really not my problem if it takes a lot of time. I see this as an issue of "ease of coding/extending vs. speed".

LOL, lots of fuzz about sdk that nobody will propably ever use.. well, good practice for me if nothing else.
Perhaps you don't realize it, but your SDK reveals quite a lot about the inner workings of your component, so this isn't just about the SDK. Thanks for your openness anyway, I hope you won't be too offended when we ask people who complain about foobar2000 being slow with several tens of thousand items in the library to try to remove your component.

foo_custominfo

Reply #20
But still, the service pointer templates aren't used everywhere in the foobar2k sdk either. That was the main reason that encouraged me to use pointers..
Service pointer template is not used when:
- Referenced object is not a service
- Storing the pointer anywhere would make no sense or imply buggy code, or relevant code is only called by implementation of service you get a pointer to, like in case of metadb display hooks getting raw pointer to metadb_handle.
Quote
And as you showed with the custominfo::get(), one can make mistakes with service_ptr_t<> too.
You can kill yourself with a spoon too, if you try hard enough.
The service pointer template assumes you have basic knowledge about consequences of your code being in a DLL, and services being implemented by other DLLs, which you apparently fail at.
Microsoft Windows: We can't script here, this is bat country.

foo_custominfo

Reply #21
i have a weird problem. I'm trying to rate songs but it works only one playlist...when i choose another playlist and trying to rate songs...nothing happens. someone help me 

foo_custominfo

Reply #22
i have a weird problem. I'm trying to rate songs but it works only one playlist...when i choose another playlist and trying to rate songs...nothing happens. someone help me 

Probably you have "library only" checked and the files in the second playlist are not in the library.

foo_custominfo

Reply #23
Quote


I did a quick test. This is what I saw, please tell me if I'm wrong on something.

1)  In trackinfo the functions $cixxx() don't work, in playlist columns they do.

confirmed.. and the variables don't seem to work either.. not sure if I can do anything about that.

Hmm.. now that i tried it again, it seems that it's working after all?


Tried a little more, these are the results:


Trackinfo mode = Follow cursor

OK, your three functions work.


Trackinfo mode = Now playing

They work only if there is a playing file. If I stop Foobar I'm still getting the "unknown function" message for all of the three.

If I go to the next track (while playing) I can see the message shown for half a second.

Thanks.

foo_custominfo

Reply #24
How can we get the separate values of a multi values field as the old 'tag' (with no %%) did in albumlist or the #tag# did in PLT?

I can't figure up how to set PLT or browser panels to use the single values...


Thanks.