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_sqlite (Read 11752 times) previous topic - next topic
0 Members and 1 Guest are viewing this topic.

Re: foo_sqlite

Reply #25
It seems to work.
Well, at least the statement, which you posted here, won't be executed as the syntax is wrong. Anyway, try this:

Code: [Select]
WITH Albums AS (
  SELECT album
  FROM MediaLibrary
  WHERE path LIKE 'D:\Metal\%' AND substr(path, 10, 1) > 'M'
  GROUP BY album
  HAVING avg(rating)>3 OR min(rating)=''
)
INSERT INTO Playlist_Updatable(metadb_handle,playlist_name)
SELECT a.metadb_handle, 'SQL_>M'
FROM MediaLibrary a JOIN Albums ON a.album = Albums.album

Re: foo_sqlite

Reply #26
Oh sorry I forgot to remove the extra columns I used for debugging without "INSTER INTO..."-line. I used the columns to directly verify whether the criteria where applied.
 
Your code looks much more clean, but it does also pull in tracks from other folders. There are more tracks in the list than there are in the Metal-folder. With your code I can also not see the results in the console if I remove the "INSTER INTO..."-line. I do not understand how the tracks from other folders make it into the list...

Re: foo_sqlite

Reply #27
but it does also pull in tracks from other folders.
This is only possible, if you have have albums from the metal folder with the same name in other folders. In this case you could try this (assuming that all selected album names are distinct):

Code: [Select]
WITH Albums AS (
  SELECT album, max(substr(path,1,10)) path
  FROM MediaLibrary
  WHERE path LIKE 'D:\Metal\%' AND substr(path, 10, 1) > 'M'
  GROUP BY album
  HAVING avg(rating)>3 OR min(rating)=''
)
INSERT INTO Playlist_Updatable(metadb_handle,playlist_name)
SELECT a.metadb_handle, 'SQL_>M'
FROM MediaLibrary a JOIN Albums ON a.album = Albums.album AND a.path LIKE Albums.path||'%'

Re: foo_sqlite

Reply #28
but it does also pull in tracks from other folders.
This is only possible, if you have have albums from the metal folder with the same name in other folders. In this case you could try this (assuming that all selected album names are distinct):

Code: [Select]
WITH Albums AS (
  SELECT album, max(substr(path,1,10)) path
  FROM MediaLibrary
  WHERE path LIKE 'D:\Metal\%' AND substr(path, 10, 1) > 'M'
  GROUP BY album
  HAVING avg(rating)>3 OR min(rating)=''
)
INSERT INTO Playlist_Updatable(metadb_handle,playlist_name)
SELECT a.metadb_handle, 'SQL_>M'
FROM MediaLibrary a JOIN Albums ON a.album = Albums.album AND a.path LIKE Albums.path||'%'

Ah that's it! Thank you for your help. I am happy with your latest query suggestion :)

Re: foo_sqlite

Reply #29
I have what I think is a very simple use case but can't quite figure out how to get it to work. I want to simply extract all fields from my database and save off to a CSV to use elsewhere.

What's the syntax for using the writefile command? Is it possible to save the output of a query into a file?

I can just copy the results right out of the console as well but then the data comes without the headers which is less than ideal. If it's possible to easily add/get the headers that'd be a solution for me too.

Re: foo_sqlite

Reply #30
Although not impossible writefile is probably not suitable for your purpose, as you can only write one single value for each record to a different file with it.

But you can just do a "SELECT * FROM MediaLibrary" in the SQLite console, write click on the result and copy all. You will get the content of the result window in a CSV format (separator is a comma and field delimiter a single quote) copied to the clipboard.

You might need to increase the SQLite console row limit in "Preferences -> Advanced -> Tools ->SQLite viewer" to get all your data listed. In case you have a large library you also might consider to split the result sets to a reasonable size to not run into memory problems.

Re: foo_sqlite

Reply #31
As I said I did try what you suggest - but the headers aren't extracted with the results. Any easy way to grab the headers? Adding them manually will be a pain.

Re: foo_sqlite

Reply #32
Directly it's not possible, but although it is a bit of a hack and might not work anymore in the future, you can get the headers separately with this SQL:

Code: [Select]
select group_concat('''metadb_handle'',''||vtc_column_name||'''')
from mdb_VTableColumn
where vtc_table_type='0';

Re: foo_sqlite

Reply #33
Reply removed, was meant for SQL Playlist.

Re: foo_sqlite

Reply #34
Hi, I'm trying to write a component (my first attempt at one), that would involve running a few queries against your playlist virtual table.  I'm thinking that for my component to query the virtual table, I would need the pointer to your module (functions) MetaDB_Module, to use in the sqlite function sqlite3_create_module(), (I'm also learning SQLite).  Is my assumption correct?  Can you provide any guidance for how to enable my component to utilize what you've so wonderfully done?

My hope is to be able to get a few fields from/about the prior song and the next song, and then place them into Title Formatting variables.  Then I can display using the foo_osd component on a secondary monitor.  30 years ago I was a fair coder, so this project will give me something to do while I wait for covid to be over. 

Any advice is very much appreciated.

Re: foo_sqlite

Reply #35
Hi, I'm trying to write a component (my first attempt at one), that would involve running a few queries against your playlist virtual table.
I'm sorry, but this is not possible.
I'm thinking that for my component to query the virtual table, I would need the pointer to your module (functions) MetaDB_Module, to use in the sqlite function sqlite3_create_module(), (I'm also learning SQLite).
No, it would work a bit different, if it would be possible.
Is my assumption correct?  Can you provide any guidance for how to enable my component to utilize what you've so wonderfully done?
I've created a SDK to make such things possible, for example for foo_uie_sqlite_tree, but this SDK is not ready to be published and probably will never be.

Re: foo_sqlite

Reply #36
No, it would work a bit different, if it would be possible.
I've created a SDK to make such things possible, for example for foo_uie_sqlite_tree, but this SDK is not ready to be published and probably will never be.
Thank you for replying.  It was easy to write the queries to get the data I wanted for my project.  I guess instead, I'll have to figure out where/how foobar stores the running playlist internally, and how to get the data that way.  I'm guessing it will take a while for me to figure that all out.  FWIW, if you ever decide that you want someone to test your SDK, let me know.  ;)
Take care.

Re: foo_sqlite

Reply #37
I'm having trouble getting my head around conditionals in regex (standard info). For example, here's a query for parsing opuses out of the titles of classical pieces. Ultimately I want to make an UPDATE out of it, but the SELECT suffices to see what's going on. The parts I'm interested in are in the first lines of the hairy, concatenated regex patterns. In reality, I don't think I want all of these groups, but it helps to see what's happening if they're there now.

The query's three columns are:
  • ok: A working demonstration of a parsed title using a lookahead in the first group to capture everything before "Op", "BMW", or the like. Heaven knows how hairy this will grow when trying to work with many albums, and the options in the first line of reg.lookahead need to be written twice the way I have it now.
  • notok: A conditional approach that fails in all cases. I'm trying to catch everything before the opus by seeing if the opus group participates in the overall match. I haven't been able to do this.
  • worksortorder: A sorting field formed from the numerical parts of the opus and the number, if present. By that I mean that "Op. 1" will yield "0001-00" and "Op. 2 No. 1" is "0002-01".

Here's the query. It looks at everything in the active playlist.
Code: [Select]
WITH   re AS (SELECT '^(.*(?=[Oo]p|BMW|bmw|KV?))?'
                  || '(([Oo]p|BMW|bmw|KV?)\.? ?(?:[Pp]osth?\.? ?)?(\d+))?'
                  || ' ?([Nn]o\.? (\d+))?(.*)$'
                  AS lookahead
                   , '^((?(2).*))'    -- This is the broken part
                  || '(([Oo]p|BMW|bmw|KV?)\.? ?(?:[Pp]osth?\.? ?)?(\d+))?'
                  || ' ?([Nn]o\.? (\d+))?(.*)$'
                  AS conditional
             )
SELECT regexp_replace(re.lookahead, '$1—$2—$3—$4—$5—$6—$7', title)
    AS ok
     , regexp_replace(re.conditional, '$1—$2—$3—$4—$5—$6—$7', title)
    AS notok
     , nullif(padl(regexp_replace(re.lookahead, '$4', title), 4, '0')
    || '-'
    || padl(regexp_replace(re.lookahead, '$6', title), 2, '0'), '0000-00')
    AS worksortorder
  FROM Playlist, re
 WHERE playlist_index = active_playlist()

And here's some sample output. I've omitted the notok column for space considerations, but it fails in every row as the second and third rows of the ok column do here—lacking opuses altogether. Basically, nothing is found and the whole title goes into the final (.*) group. That is how I want such lines handled, but not every line! I've also edited the "No. 1" out of the first part of "Op. 9"'s title just to see what happens.

Code: [Select]
                           ok                                        worksortorder
Nocturne in E Minor, —Op. Post. 72—Op—72—No. 1—1—: Andante              0072-01
——————Nocturne in C-sharp minor (1830): Lento con gran espressione
——————Nocturne in C minor (1837)
3 Nocturnes, —Op. 9—Op—9———in B-flat minor: Larghetto                   0009-00
3 Nocturnes, —Op. 9—Op—9—No. 2—2— in E-flat major: Andante              0009-02
3 Nocturnes, —Op. 9—Op—9—No. 3—3— in B major: Allegretto                0009-03
3 Nocturnes, —Op. 15—Op—15—No. 1—1— in F major: Andante cantabile       0015-01
3 Nocturnes, —Op. 15—Op—15—No. 2—2— in F-sharp major: Larghetto         0015-02
3 Nocturnes, —Op. 15—Op—15—No. 3—3— in G minor: Lento                   0015-03
2 Nocturnes, —Op. 27—Op—27—No. 1—1— in C-sharp minor: Larghetto         0027-01
2 Nocturnes, —Op. 27—Op—27—No. 2—2— in D-flat major: Lento sostenuto    0027-02
2 Nocturnes, —Op. 32—Op—32—No. 1—1— in B major: Andante sostenuto       0032-01
2 Nocturnes, —Op. 32—Op—32—No. 2—2— in A-flat major: Lento              0032-02
2 Nocturnes, —Op. 37—Op—37—No. 1—1— in G minor: Andante sostenuto       0037-01
2 Nocturnes, —Op. 37—Op—37—No. 2—2— in G major: Andantino               0037-02
2 Nocturnes, —Op. 48—Op—48—No. 1—1— in C minor: Lento                   0048-01
2 Nocturnes, —Op. 48—Op—48—No. 2—2— in F-sharp minor: Andantino         0048-02
2 Nocturnes, —Op. 55—Op—55—No. 1—1— in F minor: Andante                 0055-01
2 Nocturnes, —Op. 55—Op—55—No. 2—2— in E-flat major: Lento sostenuto    0055-02
2 Nocturnes, —Op. 62—Op—62—No. 1—1— in B major: Andante                 0062-01
2 Nocturnes, —Op. 62—Op—62—No. 2—2— in E major: Lento                   0062-02

So, any tips on how I can get that conditional approach to work? Barring that, are there any other good ways to accomplish this sort of thing? I imagine that better minds than mine have done something like this.

Re: foo_sqlite

Reply #38
I have what I think is a very simple use case but can't quite figure out how to get it to work. I want to simply extract all fields from my database and save off to a CSV to use elsewhere.

I've had some success saving what I need to another .db file, which can be manipulated with sqlite3 or something like SQLite Studio to get CSVs. This grabs the whole MediaLibrary as well as the internal table that holds all of the SQL Tree queries.

Code: [Select]
VACUUM;
ATTACH DATABASE 'export\foobarlibrary.db' AS 'LibraryDB';
DROP TABLE IF EXISTS LibraryDB.MediaLibrary;
CREATE TABLE         LibraryDB.MediaLibrary AS
              SELECT *
                FROM MediaLibrary;

DROP TABLE IF EXISTS LibraryDB.ust_TreeItem;
CREATE TABLE         LibraryDB.ust_TreeItem AS
              SELECT *
                FROM ust_TreeItem;

DETACH DATABASE 'LibraryDB';

At the moment, it just puts the db in a folder called export off of the foobar2000 root directory. I forget if this fails if the folder has not yet been created, so that may happen. I'd love some way to get another folder in there—perhaps even a user-selected one—but that's probably not SQLite's job. Sure, one could just type in another location, but I'm trying to keep it somewhat generic here.

Re: foo_sqlite

Reply #39
Long as I'm here, I'd like to make a feature request: The third answer down on this stackoverflow post mentions that UPDATE FROM queries are available as of SQLite version 3.33.0. I've been having trouble implementing any of the solutions given there, but that particular way may be what I really want when I write tags. Is there any chance you can bump up the SQLite version? Thanks.  ;D

Re: foo_sqlite

Reply #40
I'm having trouble getting my head around conditionals in regex ...
I didn't have a closer look at your code, but here is the code which I use to move a featuring artist from title to an own tag. You should be able to adapt it for your needs.

I'm using a permant table for my regular expressions. The expression searches for common occurrances of featuring artist with or without braces in the track title.
Code: [Select]
CREATE TABLE RegularExpressions
(
  name TEXT PRIMARY KEY,
  expression TEXT
);

INSERT INTO RegularExpressions
VALUES('Featuring','(?(DEFINE)'||
                     '(?<open-braces>[({\[])'||
                     '(?<close-braces>[)}\]])'||
                     '(?<no-braces>[^(){}\[\]])'||
                     '(?<content-1>(?i)f(ea)?t((\.\s*)|\s+))'||
                     '(?<content-2>(?i)featuring\s+)'||
                     '(?<content>\b(?&content-1)|(?&content-2))\b'||
                   ')'||
                  '(?:(?:\s*(?&open-braces)(?&no-braces)*)?|\s+)(?&content)(?<featuring-artist>(?&no-braces)+)(?:(?&close-braces)\s*)?');

For updating the tags I'm using this statement:
Code: [Select]
WITH RegularExpression AS (
  SELECT expression
  FROM RegularExpressions
  WHERE name='Featuring'
)
UPDATE PlaylistUpdatable
SET "featuring artist"=regexp_replace((SELECT expression FROM RegularExpression),
                                      '$+{featuring-artist}',
                                      title,
                                      1),
     title=regexp_replace((SELECT expression FROM RegularExpression),
                           '',
                          title)
WHERE "featuring artist" IS NULL AND
       playlist_index=active_playlist() AND
       item_is_selected;

The updatable virtual playlist table is created with this code:
Code: [Select]
CREATE VIRTUAL TABLE `PlaylistUpdatable` USING MetaDB_Module(no_multivalue_split,playlist)

At the moment, it just puts the db in a folder called export off of the foobar2000 root directory. I forget if this fails if the folder has not yet been created, so that may happen. I'd love some way to get another folder in there—perhaps even a user-selected one—but that's probably not SQLite's job. Sure, one could just type in another location, but I'm trying to keep it somewhat generic here.
Well, currently this cannot be done with foo_sqlite and at the moment I don't have any plans to support this. But why do you need to write the tables to another SQLite db? You can perfectly access the db from foo_sqlite with an external tool. Only the virtual tables are not usable with it.

Is there any chance you can bump up the SQLite version?
This will definitely happen, but at the moment I cannot make any promises, when it will be. I'm currently working on some updates, which need to be finished first. It should be somewhen this year, but I cannot guarantee it.

Re: foo_sqlite

Reply #41
Thanks for the code help. I'll have to study it a bit to learn what it's doing, but that does look like a good way to handle this. I'm glad to hear that an update is in the offing when it comes; thanks for all that you've done.

But why do you need to write the tables to another SQLite db? You can perfectly access the db from foo_sqlite with an external tool. Only the virtual tables are not usable with it.

Could you say a little more about this? Part of my reasons for exporting is just for the sake of backing up all of my tags, but I didn't know of any ways to access foo_sqlite externally. Is it simply a matter of attaching foo_sqlite.db and executing a saved view? I haven't been able to read any of the data that way, but maybe I could make buttons that duplicate clicking on foo_uie_tree_view nodes? Maybe just make those node queries all SELECT * FROM view_blahblahblah (or bypass the treeview altogether) and work with view_blahblahblah from a Spider Monkey Panel? That would be sweet.

Re: foo_sqlite

Reply #42
Could you say a little more about this? Part of my reasons for exporting is just for the sake of backing up all of my tags,
For backing up your tags and all your settings you just need to backup the folder with your user data (the location is dependent of the installation type and the foobar2000 version). You can also use foo_jesus to automate this task.

but I didn't know of any ways to access foo_sqlite externally. Is it simply a matter of attaching foo_sqlite.db and executing a saved view?
It's actually just opening the database with an external tool and work with the saved data.

I haven't been able to read any of the data that way,
This is strange. I no problems to access the db with a tool like this.

but maybe I could make buttons that duplicate clicking on foo_uie_tree_view nodes?
This would be a possibility. The upcoming version of foo_sqlite will support the materialization of the virtual tables. This will be done automatically in the background.

Maybe just make those node queries all SELECT * FROM view_blahblahblah (or bypass the treeview altogether) and work with view_blahblahblah from a Spider Monkey Panel? That would be sweet.
This is not possible as a SMP doesn't have access to the virtual tables.

 

Re: foo_sqlite

Reply #43
I'm probably not being clear and jumbling many different ideas together. Indeed, I don't come to foobar knowing any of this; rather, I'm using foobar to learn all this. It's a hell of a lot more fun than all the tutorials droning on about company personnel, inventory, and quarterly sales. :)

I've used SQLiteStudio just to look into the dbs, and only now am I realizing that I can start working in there to create views and whatnot rather than going through the SQLite console or the treeview batch tab in foobar for everything. Cool!

But when I say that I can't read the data, I guess I mean that I can't read the contents of the virtual tables or any views that refer to them. I have, in one instance, created a permanent table and saved data to it in order to export an album list, and I can read the contents of that just fine. (I didn't actually mean to leave it there, and it gets dropped and recreated before each such export. I guess I should drop it when I'm done, or use a temporary table, or…) That's just by album and doesn't have records for each track, but the query I pasted above could certainly be redirected within foo_sqlite.db if desired. This is what you see in your db?

I guess I wanted to put things like that in a separate db because I had in mind the examples of other music players that could export metadata to xml files or what have you, and I hadn't yet found any other ways in foobar to achieve something like that. I also didn't want to make foo_sqlite.db bigger than it had to be. That directory I saved my db to is just a temporary location since I haven't figured out how to supply a directory more specific to my current computer short of explicitly writing it into the query, which I don't want to do. Once I've created it, I move it by hand. Perhaps I could learn some code for moving it from the place SQLite leaves it to where I want to put it, but I haven't gotten to that bit yet. :)

Re: foo_sqlite

Reply #44
This is what you see in your db?
Well, the main idea behind foo_sqlite is to have the possibility to query the media library with SQL. While title format queries are sufficient in many cases, they cannot be used for all kind of queries, which needs to query the tags from more than one track, e.g. get all tracks from albums, which have a track with a rating of 5. With title format queries you can only get the single tracks, which have a rating of 5, while it is possible with SQL to get also all other tracks of these albums, which don't have a ratnig of 5.

The virtual tables are just a SQL wrapper for the media library or the playlists, which are always completely loaded into memory. The foobar2000 SDK provides means to access these in-memory data. That's the reason, why you can only access the virtual tables from the SQLite console and not from external tools.

Re: foo_sqlite

Reply #45
OK, weird errors. I was trying to redo my query along the lines you showed me. I was having trouble getting the results I wanted, but it was doing its thing and giving me one row of something and the rest NULL. I also had foo_sqlite.db open in SQLiteStudio to modify my regex table.

I commented out a line in that regex and replaced it with a bunch of (.*) just to see if I could get something. It looked like this below. There may be errors in it, but I'm not asking about those.  :)
Code: [Select]
DROP TABLE IF EXISTS regexes;
CREATE TABLE         regexes (
                        name TEXT UNIQUE PRIMARY KEY,
                  expression TEXT
                             );

INSERT INTO regexes
     VALUES (
            'work'      -- opus and number
          , '(?(DEFINE)'
         || '(?<worktext>(?i)\b(?:opp?|bwv|kv?)\.?\b)?'
         || '(?<posth>(?i)\bposth?\b)?'
         || '(?<num>(?i)no)?'
         || ')'
         -- || '(?&worktext)\.?\s*(?&posth)\.?\s*(\d+)?\s*'
         -- || '(?&num)\.?\s*(\d+)?'
         || '(.*)(\d+)?(.*)(\d+)?'
            );

I ran that in SQLiteStudio and went back to the SQL console in foobar to rerun my SELECT there, and it seemed to hang. I aborted and tried again. Foobar crashed.

After restarting, the SQL Tree was no longer displayed. I tried to rebuild Playlist_Updatable but it failed due to a duplicated column. I tried the same with the Playlist table but could not. Here's the error output for that.
Execution error:
SQLite Error: (1) Unable to declare virtual table Playlist:
SQLite Error: (1) duplicate column name: asin

Error while preparing the first statement of:
DROP TABLE IF EXISTS Playlist;
CREATE VIRTUAL TABLE Playlist USING MetaDB_Module(add_defaults);
COMMIT;


Looking at the Playlist table in Preferences, I see that most of the fields have been duplicated. I put a screencap below.

I done broke it good.  :o  I guess I'll try manually removing fields via Preferences, but it pretty much doubled all of them after artist.

Re: foo_sqlite

Reply #46
OK, I removed the duplicate columns and restarted. It came up with the default nodes in the treeview. Running SELECT * FROM ust_TreeItem in the console showed no rows.

  • Uninstalled/reinstalled foo_uie_sql_tree. No change.
  • Uninstalled/reinstalled foo_uie_sql_tree and foo_sqlite. No change.
  • Noticed that foobar 1.6.6 is out, made a new portable installation, and copied my profile folders over. Treeview is restored, but ust_TreeItem is still showing no rows, even from a different installation's copy of foo_uie_sql_tree.db.

So it looks like disaster is averted, but I'll probably do a more full rebuild. Oh look, a crash report.

Re: foo_sqlite

Reply #47
Just to be clear, I'm merely reporting my experience. You do not need to resolve anything for me now, though I can try any suggestions you may have if you need to know more.

I do think though that maybe I ought to just work my new stuff out entirely within SQLiteStudio—in this handy export database I happen to have right here—and then just paste my final working version into foobar.  :))

Ooh, one other thing I've been noticing: the Playlist_Updatable table seems to get corrupted if I'm moving things around from playlist to playlist, removing playlists, or something along those lines. My WHERE playlist_index = active_playlist() will exclude everything because that field will no longer contain playlist indexes. I've seen it contain different things at different times: nothing, incremented numbers, or the file paths of the tracks, if I'm remembering everything correctly. Dropping that table and recreating it restores order.

Re: foo_sqlite

Reply #48
I ran that in SQLiteStudio and went back to the SQL console in foobar to rerun my SELECT there, and it seemed to hang. I aborted and tried again. Foobar crashed.
Ok, I never tried it, but changing foo_sqlite.db with an external tool, which is using a much newer version of SQLite, seems to be problematic and led to a database corruption.

Treeview is restored, but ust_TreeItem is still showing no rows, even from a different installation's copy of foo_uie_sql_tree.db
The reason is, that the information, which the root node of the tree is not stored in the db, but in the foobar 2000 config file, as there can be multiple panels with a tree all with the different root node. So, if you want to restore from a backup you, also need to restore the file foo_uie_sql_tree.cfg. The database alone is not enough.
So it looks like disaster is averted,
I hope you have a recent backup.

Oh look, a crash report.
Most likely caused by the corrupted database.

Ooh, one other thing I've been noticing: the Playlist_Updatable table seems to get corrupted if I'm moving things around from playlist to playlist, removing playlists, or something along those lines. My WHERE playlist_index = active_playlist() will exclude everything because that field will no longer contain playlist indexes. I've seen it contain different things at different times: nothing, incremented numbers, or the file paths of the tracks, if I'm remembering everything correctly. Dropping that table and recreating it restores order.
Is this something, that can be reproduced somehow or happens more than once? Or did it only occur after the crash?

Re: foo_sqlite

Reply #49
Is this something, that can be reproduced somehow or happens more than once? Or did it only occur after the crash?

That was happening periodically before the crash (and before my bright idea to involve SQLiteStudio) throughout my work with this query and other, more successful update queries. I'd be pulling files from one playlist which was a work queue to another to work on (I hadn't seen that item_is_selected technique), moving those to a third playlist of finished tracks, and often playing music in a fourth with albums passing in and out. Some of that work involved also updating and adding tags with masstagger, musicbrainz, and discogs, the latter often throwing up "stoi" errors of its own with inexactly-matched tracks. So, lots going on.

 
SimplePortal 1.0.0 RC1 © 2008-2021