Some keywording tools that some of you might find useful

Some versions ago, DxO added the ability to tag images with keywords. Despite some changes since the initial introduction, there are some missing tools. DxO is a small company with limited resources and I suspect that they aren’t going to prioritize further enhancements of the keywording system.

I’ve developed tools to fill the gaps. Unfortunately, I wrote my code in PHP, and it’s not easily shareable.

As a spin-off of my work, I’ve developed a few SQL scripts that might be useful to some of you. These are a bit easier to share. I’ll offer these here. If you don’t need them, ignore them.

UPDATE: Clarifications (based on feedback received so far):

  • There’s some potential that the tools won’t work with a Mac DB—I’m still trying to find out. If you are on a Mac and you try them and they don’t work, let me know.
  • The tools won’t affect your database in any way. They only retrieve information.
  • The tools only report keyword information from Photolab’s database; they don’t get any information directly from any of your images or DOP files.
  • If you use some other software to manage your image’s keywords, these tools will be of little interest to you.

The process for running these tools is:

  1. Download and install DB Browser for SQLite (DB4S), This is available for both Windows and Macs from Downloads - DB Browser for SQLite or—use any other equivalent software.
  2. Start DB4S and open the Photolab database in read-only mode.
  3. Go to the Execute SQL tab.
  4. Copy the code I give you into this window and execute it (right-pointing triangle in the tool bar).
  5. Copy the resulting table, into a spreadsheet or a text file.

Keep in mind that the SQL I am giving you cannot maliciously affect your database since you opened the database in read-only mode (right?).

Get the Keyword Structure

Photolab doesn’t have any method of importing/exporting the keyword structure. The tools below only export your existing structure. I’ll give you three variants.

Version 1: Get the basic structure only

/*
 * Create a table of all keywords. Every keyword is written using a
 * full path. The results are in alphabetical order.
 */

WITH RECURSIVE

  /*
   * Create a table that matches keyword IDs to the full path of the
   * keyword it is associated with.
   */

  fullKeywordPath AS (
    SELECT Id, Value, ParentId, Value AS path
    FROM Keywords
    WHERE ParentId IS NULL

    UNION ALL

    SELECT Keywords.Id, Keywords.Value, Keywords.ParentId, fullKeywordPath.path || '|' || Keywords.Value
    FROM Keywords, fullKeywordPath
    WHERE Keywords.ParentId = fullKeywordPath.Id
  )

SELECT path
FROM fullKeywordPath
ORDER BY path

The output will look something like this:

Contents|Wildlife|Birds|Australasian Robins
Contents|Wildlife|Birds|Australasian Robins|South Island Robin
Contents|Wildlife|Birds|Australasian Robins|Tomtit
Contents|Wildlife|Birds|Australasian Warblers
Contents|Wildlife|Birds|Australasian Warblers|Grey Warbler

Each keyword will be identified by its full path name, with “|” separating the paths. You could change this character to something like " > " if you wanted.

Version 2: Get the basic structure and counts

This version just adds an image count to each keyword. The count should match what you see when you view keywords in Photolab. It runs a lot slower than the version above.

/*
 * Create a table of all keywords. Every keyword is written using a
 * full path. The results are in alphabetical order. Each path is
 * followed by a count of the number of items associated with that
 * keyword. Getting the count slows the query a bit.
 */

WITH RECURSIVE

  /*
   * Create a table that matches keyword IDs to the full path of the
   * keyword it is associated with.
   */

  fullKeywordPath AS (
    SELECT Id, Value, ParentId, Value AS path
    FROM Keywords
    WHERE ParentId IS NULL

    UNION ALL

    SELECT Keywords.Id, Keywords.Value, Keywords.ParentId, fullKeywordPath.path || '|' || Keywords.Value
    FROM Keywords, fullKeywordPath
    WHERE Keywords.ParentId = fullKeywordPath.Id
  )

SELECT
  path,
  ( SELECT COUNT(*) FROM ItemsKeywords WHERE KeywordId = fullKeywordPath.Id) AS count
FROM fullKeywordPath
ORDER BY path

The output is now:

Contents|Wildlife|Birds|Australasian Robins	52
Contents|Wildlife|Birds|Australasian Robins|South Island Robin	33
Contents|Wildlife|Birds|Australasian Robins|Tomtit	19
Contents|Wildlife|Birds|Australasian Warblers	11
Contents|Wildlife|Birds|Australasian Warblers|Grey Warbler	11

Version 3: Get the basic structure in Adobe Bridge format

Adobe Bridge can import keyword structures. They use tabs to indicate the level of each keyword.

/*
 * Create a table of all keywords. If each row of the table is
 * converted into a line in a file, the file can be imported into
 * Adobe Bridge to create the same structure.
 */

WITH RECURSIVE

  /*
   * Create a table that matches keyword IDs to the full path of the
   * keyword it is associated with. Create a prefix that consists of a
   * tab for each level of depth of a keyword, with the top level
   * being level 0.
   */

  fullKeywordPath AS (
    SELECT Id, Value, ParentId, Value AS path, "" AS prefix
    FROM Keywords
    WHERE ParentId IS NULL

    UNION ALL

    SELECT
      Keywords.Id,
      Keywords.Value,
      Keywords.ParentId,
      fullKeywordPath.path || '|' || Keywords.Value,
      prefix || char(9) AS prefix
    FROM Keywords, fullKeywordPath
    WHERE Keywords.ParentId = fullKeywordPath.Id
  )

SELECT prefix || Value AS name
FROM fullKeywordPath
ORDER BY path

The output is something like:

Contents
	Wildlife
		Birds
			Australasian Robins
				South Island Robin
				Tomtit
			Australasian Warblers
				Grey Warbler

Find Hierarchy Problems

When you tag an image with a keyword, you can set up a preference that ensures that all parent keywords are also tagged. Drag-and-drop changes and other things (an accidental cursor click) can screw up this scheme.

The code below will output any image name/keyword path pairs that should be enabled to maintain a complete hierarchy path, but aren’t. It also lists a UUID.

The UUID can distinguish between an image and a virtual copy. You might have a problem in some and not others. It’s tough to match a UUID to a specific image/virtual copy; if the tool reports problems with an image name/keyword pair and the image has one or more virtual copies, then check them all.

This script can be very slow if you have a lot of tagged images. I just ran it and it took 6 seconds to display any results. If it says “0 rows returned”, then there are no problems.

/*
 * Get a table of each item/keyword pair that should be in
 * ItemsKeywords in order to have complete hierarchies but is not. The
 * information is returned as filename and keyword path.
 *
 * The result rows also include a UUID. Each source file has a main
 * image and one or more virtual copies. Each is uniquely identified
 * by its UUID. If the same filename/keyword path appears more than
 * once, then the UUIDs will be different. This indicates there is a
 * problem in more than one version of the image. If a file/keyword
 * path appears only once, but there are virtual copies of the image,
 * then only one of the versions has a problem, but you will have to
 * check them all to see which has the problem.
 *
 * This may take a few seconds to run. If everything is OK, it should
 * return 0 rows.
 */

WITH RECURSIVE

  /*
   * Create a table that matches folder IDs to the full path of the
   * folder it is associated with.
   */

  fullFolderPath AS (
    SELECT Id, Name, ParentFolderId, Name AS path FROM Folders
    WHERE ParentFolderId IS NULL

    UNION ALL

    SELECT Folders.Id, Folders.Name, Folders.ParentFolderId, fullFolderPath.path || '/' || Folders.Name
    FROM Folders, fullFolderPath
    WHERE Folders.ParentFolderId = fullFolderPath.Id
  ),

  /*
   * Create a table that matches keyword IDs to the full path of the
   * keyword it is associated with.
   */

  fullKeywordPath AS (
    SELECT Id, Value, ParentId, Value AS path
    FROM Keywords
    WHERE ParentId IS NULL

    UNION ALL

    SELECT Keywords.Id, Keywords.Value, Keywords.ParentId, fullKeywordPath.path || '|' || Keywords.Value
    FROM Keywords, fullKeywordPath
    WHERE Keywords.ParentId = fullKeywordPath.Id
  ),

  potentials AS (
	SELECT ItemId, KeywordId, ParentId FROM ItemsKeywords
	JOIN Keywords ON Keywords.Id = ItemsKeywords.KeywordId

	UNION

	SELECT potentials.ItemId, Keywords.Id AS KeywordId, Keywords.ParentId FROM potentials
	JOIN Keywords ON Keywords.Id = potentials.ParentId
  )

SELECT
  ( SELECT path FROM fullFolderPath WHERE fullFolderPath.Id = Sources.FolderId ) || '/' || Sources.Name AS filename,
  ( SELECT path FROM fullKeywordPath WHERE fullKeywordPath.Id = Keywords.Id ) AS keywordpath,
  Items.Uuid

FROM potentials
  JOIN Items ON Items.Id = potentials.ItemId
  JOIN Keywords ON Keywords.Id = potentials.KeywordId
  JOIN Sources ON Sources.Id = Items.SourceId
WHERE NOT EXISTS ( SELECT 1 FROM ItemsKeywords AS ik WHERE ik.ItemId = potentials.ItemId AND ik.KeywordId = potentials.KeywordId )
ORDER BY filename, keywordpath

The output looks like this:

D:/Users/sample/Pictures/Photos/2025/IMG_1294.CR3	Test|A	2E7224C3-0A39-463B-9F54-9451B3FB3F61

In this case, the image is tagged with Test and Test|A|B, but not Test|A.

I suppose that these SQLs work with a DB from PhotoLab for Windows.
The DB of PL (Mac) has a different structure. Maybe I’ll adapt the SQLs…
or DxO could add import and export functionality to PhotoLab :wink:

What are the differences?

It would be crazy to design totally different structures for the two OSs. My SQL doesn’t deal with every table or even field in a table, so there may be nothing to adapt.

The second sentence of my post states that I think this is unlikely. I gave up waiting for DxO to provide much help with keywords.

1 Like

One important difference for the Mac DB is that its schema is auto-generated using the CoreData designer, from an object-oriented model. This means that, if DxO were to change the O-O model, the corresponding RDBMS model would change and could cause problems.

Not forgetting that the keywords are stored, not only in the database but, also, in an XMP sidecar for RAW images and the DOP sidecar for all files. These could need synchronising, depending on your preferred metadata storage. Any hierarchical keywords are also stored in an inconsistent manner between the DOP and XMP files as well as the database.

This doesn’t tell me what the structural differences are. For all I know, the DxO people generate the schema on a Mac and then use that same schema on Windows.

DB4S can display the structure. It’s the first tab. A screenshot of that tab would go a long way toward answering my question.

I should have highlighted that the scripts are currently only tested for PL8 on Windows. They probably work on PL 7 as well. For many software tools, DB changes occur only when the major version number increments. These SQL scripts may or may not work on PL 9. However, the scripts don’t make any changes to the DB, so the worst that happens is that they don’t work.

True, but irrelevant. The tools I provided only operate on the database.

I should perhaps have added that the tools only operate on DxO’s database. If you use another tool for managing keywords (as you do), nothing I provided will be of any use to you.

I think I’ll edit the OP to reflect some of these points.

@freixas Thank you for providing the scripts they may well prove useful. But the schema for the Mac database, regardless of how it was/is generated, has a number of differences to the Windows schema, not least the naming convention!?

So it will be possible to create the Mac equivalent scripts @platypus but the current ones won’t work.

PL8 has a slight difference in schema from PL7 (with PL8 on Windows), the order of fields in ‘Folders’ was changed in PL8 and an additional index or two was added to one structure or another (sorry I have forgotten exactly which ones).

In the meantime, I have been working with the DOPs where SQL certainly isn’t going, to help, except maybe to compare results between outputs from the scripts and outputs from the DOPs, but I have managed this so far in PureBasic

2025/06/03_09:29:03.518 @ line      1   Sidecar = {
2025/06/03_09:29:03.518 @ line      2   Date = "2025-06-01T15:55:50.6298766Z",
2025/06/03_09:29:03.518 @ line      3   Software = "DxO PhotoLab 8.6",
2025/06/03_09:29:03.518 @ line      4   Source = {
2025/06/03_09:29:03.518 @ line      5   CafId = "C61004c",
2025/06/03_09:29:03.518 @ line      6   Items = {
2025/06/03_09:29:03.518 @ line      7   {
2025/06/03_09:29:03.518 ---------------------------------------------------------------------------
2025/06/03_09:29:03.518 @ line      8   Albums = "",                                           <===[
2025/06/03_09:29:03.529 @ line      9   CreationDate = "2025-05-29T09:41:02.0233595Z",
2025/06/03_09:29:03.529 @ line     13   Keywords = {
2025/06/03_09:29:03.529 Keyword = A
2025/06/03_09:29:03.529 Keyword = A|B
2025/06/03_09:29:03.529 Keyword = A|B|C
2025/06/03_09:29:03.529 Keyword = A|B|C|D
2025/06/03_09:29:03.529 @ line     39   Name = "P1102026_Neutral (nt V2).RW2",
2025/06/03_09:29:03.529 @ line     41   OutputItems = {
2025/06/03_09:29:03.529 @ line     45   Rating = 0,
2025/06/03_09:29:03.530 @ line    317   ColorLookupPath = "",
2025/06/03_09:29:03.531 @ line    504   Overrides = {
2025/06/03_09:29:03.531 @ line    616   Version = "19.5",
2025/06/03_09:29:03.531 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2025/06/03_09:29:03.531 @ line    621   Uuid = "6A9404D6-3FD0-459E-B164-8DC08F223AD3",         <---]
2025/06/03_09:29:03.531 {Album_count = 1 Uuid_count = 1}
2025/06/03_09:29:03.531 ---------------------------------------------------------------------------
2025/06/03_09:29:03.540 ---------------------------------------------------------------------------
2025/06/03_09:29:03.540 @ line    625   Albums = "",                                           <===[
2025/06/03_09:29:03.540 @ line    626   CreationDate = "2025-06-01T15:52:54.4250729Z",
2025/06/03_09:29:03.540 @ line    630   Keywords = {
2025/06/03_09:29:03.540 Keyword = A
2025/06/03_09:29:03.540 Keyword = A|B
2025/06/03_09:29:03.540 Keyword = A|B|C
2025/06/03_09:29:03.540 Keyword = A|B|C|D
2025/06/03_09:29:03.540 @ line    656   Name = "P1102026_Neutral (nt V2).RW2",
2025/06/03_09:29:03.540 @ line    658   OutputItems = {
2025/06/03_09:29:03.540 @ line    662   Rating = 0,
2025/06/03_09:29:03.541 @ line    934   ColorLookupPath = "",
2025/06/03_09:29:03.542 @ line   1121   Overrides = {
2025/06/03_09:29:03.542 @ line   1131   Version = "19.5",
2025/06/03_09:29:03.542 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2025/06/03_09:29:03.551 @ line   1136   Uuid = "62FD850F-8EA1-4AAB-8DA4-817E1E316BE8",         <---]
2025/06/03_09:29:03.551 {Album_count = 2 Uuid_count = 2}
2025/06/03_09:29:03.551 ---------------------------------------------------------------------------
2025/06/03_09:29:03.551 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2025/06/03_09:29:03.551 @ line   1141   Uuid = "A0944FE9-B476-4874-93EA-CDDF6AC1BDA0",         <----
2025/06/03_09:29:03.551 {Album_count = 2 Uuid_count = 3}
2025/06/03_09:29:03.551 ---------------------------------------------------------------------------
2025/06/03_09:29:03.551 @ line   1144   Version = "19.0",
2025/06/03_09:29:03.551 ---------------------------------------------------------------------------
2025/06/03_09:29:03.551  
2025/06/03_09:29:03.551 Creation Date list:- 
2025/06/03_09:29:03.551        Album     #               Creation Date
2025/06/03_09:29:03.551 @ line     9     1  2025-05-29T09:41:02.0233595Z  Rating = 0, Name = "P1102026_Neutral (nt V2).RW2",
2025/06/03_09:29:03.562 @ line   626     2  2025-06-01T15:52:54.4250729Z  Rating = 0, Name = "P1102026_Neutral (nt V2).RW2",
2025/06/03_09:29:03.562  
2025/06/03_09:29:03.562 Sorted Creation Date List:- 
2025/06/03_09:29:03.562        Album            #        Creation Date
2025/06/03_09:29:03.562 @ line     9     1      1  2025-05-29T09:41:02.0233595Z  Rating = 0, Name = "P1102026_Neutral (nt V2).RW2",
2025/06/03_09:29:03.562 @ line   626     2      2  2025-06-01T15:52:54.4250729Z  Rating = 0, Name = "P1102026_Neutral (nt V2).RW2",
2025/06/03_09:29:03.562  
2025/06/03_09:29:03.562 Modification Date list:- 
2025/06/03_09:29:03.562 @ line     0     0    Rating = 0, Name = "P1102026_Neutral (nt V2).RW2",
2025/06/03_09:29:03.562 @ line     0     0    Rating = 0, Name = "P1102026_Neutral (nt V2).RW2",
2025/06/03_09:29:03.562  
2025/06/03_09:29:03.562 Sorted Modification Date List:- 
2025/06/03_09:29:03.562 @ line     0     0    Rating = 0, Name = "P1102026_Neutral (nt V2).RW2",
2025/06/03_09:29:03.562 @ line     0     0    Rating = 0, Name = "P1102026_Neutral (nt V2).RW2",
2025/06/03_09:29:03.573  
2025/06/03_09:29:03.573 ==================================================================================

The 2 read passes are necessary only with DOPs from the Mac where it appears that the actual presentation of ‘Albums’ (copies) in the DOPs is not in order, which it is for Windows DOPs, i.e. with Windows the order of presentation in the DOP is always [M]aster, VC[1] etc. (according to my tests)

To achieve correct ordering with Mac DOPs the ‘Albums’ need to be sorted on ‘CreationDate’, hence the first pass to obtain the ‘CreationDate’ followed by a sort and that provides the data to mark or present the ‘Albums’ in the “correct” order.

The analysis program from which the screen grab was taken is just a vehicle for experimentation but the code from it is being added to programs intended for a more serious purpose, e.g. locating images that have not been assigned a keyword by the user in DxPL, which is impossible with the current ‘Search’ facility in DxPL @RAGING_FURY

The times shown are from the one of the two log files created (a User Log and a Debug Log) and it outputs to the Debug screen as well and it takes 33 milliseconds to process a single pass (all that is necessary for Windows),

So processing a directory of 1,000 images would take 33 seconds (on my 5900X but taking a single core)) and on Windows no second pass is necessary.

If the ‘Pick’/‘Reject’ flag is not included in the selection criteria only the first 50 lines need to be processed so it will take even less time!?

To do the same in SQL I would need code added to select a directory (‘Folders’), locate an image (‘Sources’ and thence to ‘Items’ ) and check for the ‘Items’ in ‘ItemsKeywords’, if absent (not found) then there is a candidate for an un-keyworded image.

My program will also be able to find any combination of keywords (including no ‘Keyword’ at all), and with ‘Rating’ and/or ‘ColorLabels’ and even the ‘Pick’/‘Reject’ flag thrown in for good measure, if I can be bothered to provide an appropriate user interface(?).

The selected (located) images can then be passed back to DxPL as an External Selection (stored as a form of ‘Projects’ entry in the database.) which can easily be converted to a real ‘Project’ by the user, if necessary.

This is a nice initiativve, thankss. But some one at DXO needs to address this, they maybe a small company but it is not as if they are giving away their software for free. (not to mention the sneaky money grab ‘you need to buy filmpack to get luminocity masking scam’)

Not too impressed, hope some effort goes into the next version - we will not know until then as any development efforts are treated as state secrets.

All very well developing the headline stuff, but some focus on basic functionality and keeping users happy, would go a long way.

Of course I know it is unlikely anyone from DXO reads this, nevermind acting upon it.

@They may well read it but these days don’t respond and I am afraid that finishing what they started does not seem to figure in their ‘raison d’être’.

You may “rant and rage”, I have since I first started testing with PL5, but it is like “banging your head against a brick wall” it only get better when you stop!

So I have mostly given up the forum, still currently involved with Beta testing (don’t ask for any details because you won’t get them) but have turned my attention to coding (having been coding since I buttoned in my first program into the console of an ICT 1301 in 1965, at the start of my degree course) to get some intellectual stimulus.

I started with Python (somewhat different from COBOL, which I used for 36 years) and had an online tutorial with my Grandson on Sunday as he prepares for his mock GSCEs but then turned to PureBasic, which allows me to create .exes.

So I can release anything I write but realised I have a potential trust issue, i.e. how do I prove that I am not going to cause damage, deliberately or even accidentally?!

The PureBasis compiler is released with some limitations to any one who wants access with no time limit but a code limit of 800 lines. So if I can stay within those limits I could release the source code and the .exe and any potential users could have both and choose which they want (trust) to use!?

I was hoping @RAGING_FURY that you would test my ‘No keyword’ version of the program when it is finished. That might be reasonably soon given that we are due some wet weather, which will make gardening somewhat difficult/unpleasant!


It is actually raining as I write.

@freixas Sorry I have gone slightly off topic - I apologise.

Regards

Bryan

sure, happy to try it whenever you get round to it. Am available off & on . You can DM me if I don’t respond.
Garden look inviting !

@RAGING_FURY Thank you I will use DM once I have a releasable version and keep the code between us for the time being and we can test not only the program but what it could/should contain in the way of features and how the process of interacting between the developer (me) and the client (you) in this case can/should be conducted.

As for the garden there is a “Jungle” beyond the Pittosporum/Conifer and down the right hand side of the garden which needs cutting back (not down) a bit

whatever works for you.

Re pittosporum, mine is getting leggy inplaces, need to do a serious prune too. Still, i like the natural look!

Thanks to @platypus, I’ve been able to look at the Mac database. My SQL will work if I rename the tables and fields. When I get around to it, I’ll try to post Mac-compatible versions if someone doesn’t beat me to it.

With regards to my SQL, the order of the fields and the presence or absence of an index doesn’t matter.

The SQL shouldn’t be hard to write (if you know SQL). It will be order of magnitudes faster than getting the info from DOP files. You could probably do a database of 100,000 images in 30 seconds or less.

If it is of any interest, I have written a full blown macOS app to manage Keywords, Star Ratings, Finder Tags, Descriptions, etc.

You can build up a library of keywords, including hierarchical ones as well as a library of Finder Tags.

Then you can add keywords to images and search for them. Once you have a search result, you can save it as a Smart Folder, just like you can in Finder. In fact, once you have used my app to add metadata, you can just as well use Spotlight to find files that match. Metadata can even be stored (safely) in RAW files, or in XMP sidecars if you prefer.

It has been designed from the ground up to work hand in hand with PL and you can open files from it, directly in PL or any other app.

The image browser shows a flattened folder hierarchy, based on the selected folder. In fact, you can browse the entire disk.

Drop me a DM if you are interested in beta testing it.

I went ahead and wrote the SQL for this (for Windows only right now, sorry). One problem is that, if you delete or rename files or folders from outside Photolab AND
you didn’t assign keywords to the renamed images before the rename, then the original filenames for these images will show up in the results.

Performance is good. For ~71,000 source files, the query took about 12 seconds.

If you just want to check a specific folder, add this line between the WHERE line and the ORDER BY line:

AND filename LIKE 'D:/Users/.../%'

where the “…” part is filled in with the full path to the file. Note that even though Windows uses “\” is a path separator, you have to use “/” here. Also, to match the exact folder name, you need to end with “/%”.

/*
 * Get a table of image files which have no keywords assigned.
 *
 * The result rows also include a UUID. Virtual copies of an image
 * will have the same filename but different UUIDs.
 *
 * This may take a few seconds to run. If everything is OK, it should
 * return 0 rows.
 */

WITH RECURSIVE

  /*
   * Create a table that matches folder IDs to the full path of the
   * folder it is associated with.
   */

  fullFolderPath AS (
    SELECT Id, Name, ParentFolderId, Name AS path FROM Folders
    WHERE ParentFolderId IS NULL

    UNION ALL

    SELECT Folders.Id, Folders.Name, Folders.ParentFolderId, fullFolderPath.path || '/' || Folders.Name
    FROM Folders, fullFolderPath
    WHERE Folders.ParentFolderId = fullFolderPath.Id
  )

SELECT
  ( SELECT path FROM fullFolderPath WHERE fullFolderPath.Id = Sources.FolderId ) || '/' || Sources.Name AS filename,
  Items.Uuid

FROM Items
  JOIN Sources ON Sources.Id = Items.SourceId
WHERE NOT EXISTS ( SELECT 1 FROM ItemsKeywords AS ik WHERE ik.ItemId = Items.Id )
ORDER BY filename

As I’m going out of town for a few days, if anyone wants to try running these for a Mac, here are the translations:

  • Globally, Id → Z_PK
  • Globally, Name → ZNAME
  • Sources → ZDOPSOURCE
  • Items → ZDOINPUTITEM
  • Items.Source → ZSOURCE
  • Items.Uuid → ZUUID
  • Keywords → ZDOPKEYWORD
  • Keywords.Value → ZTITLE
  • Keywords.ParentId → ZPARENT
  • Folders → ZDOPFOLDER
  • Folders.Name → ZNAME
  • Folders.ParentFolderId → ZPARENT
  • ItemsKeywords → Z_8KEYWORDS
  • ItemsKeywords.ItemId → Z_8ITEMS
  • ItemsKeywordsKeywordId → Z_10KEYWORDS

When I say “globally”, I don’t mean you can replace any occurrence of “Id” with “Z_PK”. I mean you can replace the exact word “Id” with “Z_PK”. The difference is that the former will change “KeywordId” to “KeywordZ_PK” and the latter won’t.

Let me know how it works.

@RAGING_FURY There are three in that last picture, actually there are four (as indicated)

and there are more in the “Jungle”.

The white “avalanche” is a rambling rose growing over a Cotoneaster, a Grevillea and a Syringa

@freixas
I wrote this earlier but didn’t post it

“If you know SQL” is the key part of your statement, I started using databases while SEQUEL was still a “twinkle in someone’s eye” and managed to survive without learning it until know, hence my unsubtle hint to someone who knows SQL, i.e. you!

PureBasic can actually submit SQL commands to an SQLite database (with a maximum of two databases open at any one time) and I can actually submit SQL commands individual requests

@freixas Thank you for your efforts, although it is still cloudy here I have another task in our lean-to to attend to first but I will get to your script either later today or first thing tomorrow.

@Joanna I think that this is the first time I have seen you offering your application in the forum (I apologise if that is not the case).

Sadly it is Mac specific and uses features not available on the PC.

The DOP analyser program I am working on will work on both the PC and the Mac. PureBasic has compilers for Windows, MacOS, Linux and the Pi and all versions are included in the one licence fee as is the rights to sell, giveaway the programs compiled with it…

But, although I have considered buying an old, second-hand Mac, currently I only have personal access to PCs.

However, my comments about being able to use PureBasic with a trial licence on programs of less than 800 lines ,covers all the languages I listed above, with certain limitations on the facilities available.

If only the “threat” of publishing “adjunct” software would spur DxO to finish at least some of what they started it would be a win-win (or mac-mac) situation!

2 Likes

@freixas Thank you for your SQL scripts, I have tried all of them and I also changed the last to use “EXISTS” in place of “NOT EXISTS” when I ran the script on a test database…

The database I used has the vital statistics which I output from a PureBasic program I wrote, that uses SQL commands to interrogate the PhotoLab SQLite database to obtain the counts etc…

As part of the test I Loaded 1,000 test images, a mixture of RAW (RW2) and JPG images and set the first 500 of the 1,000 with the same two keywords

11:27:12.948 UlogfileName = C:\PB\Logs\ULog\20250605_112712.txt
11:27:12.949 DLogfileName = C:\PB\Logs\DLog\20250605_112712.txt
11:27:21.121 Starting PLDBCountV08
11:27:21.121 Source DB is present = N:\_MY PHOTO-Catalogs\DxO\DxO PhotoLab 8\PhotoLab.db DBSize = 106,849,280 bytes
11:27:21.152 

11:27:21.152 Starting Database Analysis - 2025/06/05_11:27:21.152
11:27:21.152  on DB = N:\_MY PHOTO-Catalogs\DxO\DxO PhotoLab 8\PhotoLab.db
11:27:21.155      9,713 Total of all entries in Sources (1 for each Original image + Output/Export Items)
11:27:21.158      9,848 Total of all entries in Items (Original Images + VCs + Output/Export items)
11:27:21.165      9,332 Total in Items of all entries in Items (Original Images + VCs)
11:27:21.166        516 Total in Items of all Output/Export items(Images)
11:27:21.206          0 Total of all Picked Items (Images)
11:27:21.246      8,226 Total of all Rejected Items (Images)
11:27:21.248      9,333 Total of all in Iptc 
11:27:21.248         34 Total of all in Folders (1 for every directory level)
11:27:21.249          3 Total of all Drives (Volumes) in Folders
11:27:21.250      9,713 Total of all in Metadatas (1 for every original image)
11:27:21.251      9,153 Total of isRaw in Metadatas (1 for every RAW image)
11:27:21.251          3 Total of Projects
11:27:21.252          3 Total of Items in Projects
11:27:21.252         25 Total of Keywords
11:27:21.253      5,076 Total of Items in ItemsKeywords

and got this as part of the output from your original “NOT EXISTS” script

i.e starting from “00501.ORF” the images have been flagged as not keyworded.

With respect to my count program I need to review this line in particular

11:27:21.206          0 Total of all Picked Items (Images)
11:27:21.246      8,226 Total of all Rejected Items (Images)

No images have had that field used during my testing so they should be treated as “Unassigned”, oops.