"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "f.meta.cc" between
fotoxx-22.41.tar.gz and fotoxx-22.50.tar.gz

About: fotoxx is a program for photo editing and collection management.

f.meta.cc  (fotoxx-22.41):f.meta.cc  (fotoxx-22.50)
skipping to change at line 113 skipping to change at line 113
netmap_mousefunc respond to clicks on net map netmap_mousefunc respond to clicks on net map
find_netmap_images find images find_netmap_images find images
m_netmap_locs save and recall net map locs (center, zoom level) m_netmap_locs save and recall net map locs (center, zoom level)
EXIF store and retrieve EXIF store and retrieve
----------------------- -----------------------
exif_get get image metadata from list of keys exif_get get image metadata from list of keys
exif_put update image metadata from list of keys and data exif_put update image metadata from list of keys and data
exif_copy copy metadata from file to file, with revisions exif_copy copy metadata from file to file, with revisions
exif_server start exiftool server process, send data requests exif_server start exiftool server process, send data requests
exif_tagdate yyyy:mm:dd hh:mm:ss to yyyymmddhhmmss exif_tagdate yyyy-mm-dd hh:mm:ss to yyyymmddhhmmss
tag_exifdate yyyymmddhhmmss to yyyy:mm:dd hh:mm:ss tag_exifdate yyyymmddhhmmss to yyyy-mm-dd hh:mm:ss
Image index functions Image index functions
--------------------- ---------------------
get_xxrec get image index record for image file get_xxrec get image index record for image file
put_xxrec add or update index record for an image file put_xxrec add or update index record for an image file
read_xxrec_seq read all index records sequentially, one per call read_xxrec_seq read all index records sequentially, one per call
write_xxrec_seq write all index records sequentially write_xxrec_seq write all index records sequentially
******************************************************************************** */ ******************************************************************************** */
skipping to change at line 461 skipping to change at line 461
if (FGWM != 'F' && FGWM != 'G') return; if (FGWM != 'F' && FGWM != 'G') return;
if (clicked_file) { // use clicked file if present if (clicked_file) { // use clicked file if present
file = clicked_file; file = clicked_file;
clicked_file = 0; clicked_file = 0;
} }
else if (curr_file) file = zstrdup(curr_file,"meta-view"); else if (curr_file) file = zstrdup(curr_file,"meta-view");
else return; else return;
if (FGWM == 'G') gallery(0,"paint",-1); // if gallery view, repaint // if (FGWM == 'G') gallery(0,"paint",-1); // if gallery view, repaint remove 22.50
if (metadata_report_type != 1) { if (metadata_report_type != 1) {
if (zd_metaview) zdialog_free(zd_metaview); if (zd_metaview) zdialog_free(zd_metaview);
zd_metaview = 0; zd_metaview = 0;
metadata_report_type = 1; metadata_report_type = 1;
} }
if (! zd_metaview) // popup dialog if not already if (! zd_metaview) // popup dialog if not already
{ {
zd_metaview = zdialog_new("View Metadata",Mwin,"Extras","Cancel",null); zd_metaview = zdialog_new("View Metadata",Mwin,"Extras","Cancel",null);
skipping to change at line 498 skipping to change at line 498
if (filen) filen++; if (filen) filen++;
else filen = file; else filen = file;
fsize = 0; fsize = 0;
if (keyval[1]) fsize = atoi(keyval[1]); // get file size in B/KB/MB units if (keyval[1]) fsize = atoi(keyval[1]); // get file size in B/KB/MB units
chsize = formatKBMB(fsize,3); chsize = formatKBMB(fsize,3);
if (keyval[2] && strlen(keyval[2]) > 19) keyval[2][19] = 0; // truncate dates to yyyy-mm-dd hh:mm:ss if (keyval[2] && strlen(keyval[2]) > 19) keyval[2][19] = 0; // truncate dates to yyyy-mm-dd hh:mm:ss
if (keyval[3] && strlen(keyval[3]) > 19) keyval[3][19] = 0; if (keyval[3] && strlen(keyval[3]) > 19) keyval[3][19] = 0;
pp = keyval[2];
if (pp && strlen(pp) > 4 && pp[4] == ':') pp[4] = '-';
// change yyyy:mm:dd to yyyy-mm-dd
if (pp && strlen(pp) > 7 && pp[7] == ':') pp[7] = '-';
pp = keyval[3];
if (pp && strlen(pp) > 4 && pp[4] == ':') pp[4] = '-';
if (pp && strlen(pp) > 7 && pp[7] == ':') pp[7] = '-';
textwidget_append(widget,0,"File %s \n",filen); textwidget_append(widget,0,"File %s \n",filen);
textwidget_append(widget,0,"Size %s %s \n",keyval[0],chsize); textwidget_append(widget,0,"Size %s %s \n",keyval[0],chsize);
textwidget_append(widget,0,"Dates photo: %s file: %s \n",keyval[2],key val[3]); textwidget_append(widget,0,"Dates photo: %s file: %s \n",keyval[2],key val[3]);
if (keyval[4] || keyval[5]) if (keyval[4] || keyval[5])
textwidget_append(widget,0,"Camera make: %s model: %s \n",keyval[4], keyval[5]); textwidget_append(widget,0,"Camera make: %s model: %s \n",keyval[4], keyval[5]);
if (keyval[6] || keyval[7] || keyval[8] || keyval[9] || keyval[10]) // photo exposure data if (keyval[6] || keyval[7] || keyval[8] || keyval[9] || keyval[10]) // photo exposure data
{ {
if (keyval[6]) focallength = keyval[6]; // focal length, 35mm equivalent if (keyval[6]) focallength = keyval[6]; // focal length, 35mm equivalent
skipping to change at line 625 skipping to change at line 618
// menu function - metadata long report // menu function - metadata long report
void m_meta_view_long(GtkWidget *, cchar *menu) void m_meta_view_long(GtkWidget *, cchar *menu)
{ {
int meta_view_dialog_event(zdialog *zd, cchar *event); int meta_view_dialog_event(zdialog *zd, cchar *event);
FILE *fid; FILE *fid;
char *file, *pp, buff[1000]; char *file, *pp, buff[1000];
GtkWidget *widget; GtkWidget *widget;
cchar *tooloptions = "-s -e "; cchar *tooloptions = "-s -e -d \"%Y-%m-%d %H:%M:%S\""; // 22.50
F1_help_topic = "view meta"; F1_help_topic = "view meta";
Plog(1,"m_meta_view_long \n"); Plog(1,"m_meta_view_long \n");
if (FGWM != 'F' && FGWM != 'G') return; if (FGWM != 'F' && FGWM != 'G') return;
if (clicked_file) { // use clicked file if present if (clicked_file) { // use clicked file if present
file = clicked_file; file = clicked_file;
clicked_file = 0; clicked_file = 0;
} }
else if (curr_file) file = zstrdup(curr_file,"meta-view"); else if (curr_file) file = zstrdup(curr_file,"meta-view");
else return; else return;
if (FGWM == 'G') gallery(0,"paint",-1); // if gallery view, repaint // if (FGWM == 'G') gallery(0,"paint",-1); // if gallery view, repaint remove 22.50
if (metadata_report_type != 2) { if (metadata_report_type != 2) {
if (zd_metaview) zdialog_free(zd_metaview); if (zd_metaview) zdialog_free(zd_metaview);
zd_metaview = 0; zd_metaview = 0;
metadata_report_type = 2; metadata_report_type = 2;
} }
if (! zd_metaview) // popup dialog if not already if (! zd_metaview) // popup dialog if not already
{ {
zd_metaview = zdialog_new("View All Metadata",Mwin,"OK",null); zd_metaview = zdialog_new("View All Metadata",Mwin,"OK",null);
skipping to change at line 750 skipping to change at line 743
} }
err = access(curr_file,W_OK); // test file can be written by me err = access(curr_file,W_OK); // test file can be written by me
if (err) { if (err) {
zmessageACK(Mwin,"%s: %s","no write permission",curr_file); zmessageACK(Mwin,"%s: %s","no write permission",curr_file);
return; return;
} }
init_geolocs(); // initialize geotags init_geolocs(); // initialize geotags
if (FGWM == 'G') gallery(0,"paint",-1); // if gallery view, repaint // if (FGWM == 'G') gallery(0,"paint",-1); // if gallery view, repaint remove 22.50
/*** /***
___________________________________________________________ ___________________________________________________________
| Edit Metadata | | Edit Metadata |
| | | |
| File: filename.jpg | | File: filename.jpg |
| Title [________________________________________________] | | Title [________________________________________________] |
| Description [__________________________________________] | | Description [__________________________________________] |
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - | | - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| Image Date: [__________] Time: [________] [prev] | | Image Date: [__________] Time: [________] [prev] |
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - | | - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| Rating (stars): ⦿ 0 ⦿ 1 ⦿ 2 ⦿ 3 ⦿ 4 ⦿ 5 | | Rating (stars): ⦿ 0 ⦿ 1 ⦿ 2 ⦿ 3 ⦿ 4 ⦿ 5 |
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - | | - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| location [_______________] country [______________] | | location [_______________] country [______________] |
| latitude [__________] longitude [__________] | | latitude [__________] longitude [__________] |
| [Find] [Web] [Prev] [Clear] Geocoding by MapQuest | | [Find] [Web] [Prev] [Clear] Geocoding by MapQuest |
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - | | - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| Image Tags [___________________________________________] | | Image Tags [___________________________________________] |
| Recent Tags [__________________________________________] | | Recent Tags [__________________________________________] |
| Enter Tag [______________] [Add] | | Enter Tag [______________] [Add] |
| Matching Tags [________________________________________] | | Matching Tags [________________________________________] |
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - | | - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
skipping to change at line 1399 skipping to change at line 1392
zd_editanymeta = 0; zd_editanymeta = 0;
return; return;
} }
err = access(curr_file,W_OK); // test file can be written by me err = access(curr_file,W_OK); // test file can be written by me
if (err) { if (err) {
zmessageACK(Mwin,"%s: %s","no write permission",curr_file); zmessageACK(Mwin,"%s: %s","no write permission",curr_file);
return; return;
} }
if (FGWM == 'G') gallery(0,"paint",-1); // if gallery view, repaint // if (FGWM == 'G') gallery(0,"paint",-1); // if gallery view, repaint remove 22.50
/*** /***
____________________________________________________________________ ____________________________________________________________________
| Click to Select | File: filename.jpg | | Click to Select | File: filename.jpg |
|------------------------------| | |------------------------------| |
| (metadata list) | key name [________________________] | | (metadata list) | key name [________________________] |
| | key value [_______________________] | | | key value [_______________________] |
| | | | | |
| | [fetch] [update] [delete] | | | [fetch] [update] [delete] |
| | | | | |
skipping to change at line 1659 skipping to change at line 1652
} }
if (! curr_file) return; if (! curr_file) return;
err = access(curr_file,W_OK); // test file can be written by me err = access(curr_file,W_OK); // test file can be written by me
if (err) { if (err) {
zmessageACK(Mwin,"%s: %s","no write permission",curr_file); zmessageACK(Mwin,"%s: %s","no write permission",curr_file);
return; return;
} }
if (FGWM == 'G') gallery(0,"paint",-1); // if gallery view, repaint // if (FGWM == 'G') gallery(0,"paint",-1); // if gallery view, repaint remove 22.50
/*** /***
_________________________________________ _________________________________________
| Delete Metadata | | Delete Metadata |
| | | |
| File: [______________________________] | | File: [______________________________] |
| | | |
| (o) ALL (o) One Key: [______________] | | (o) ALL (o) One Key: [______________] |
| | | |
| [apply] [cancel] | | [apply] [cancel] |
skipping to change at line 2301 skipping to change at line 2294
zmessageACK(Mwin,"%s \n too many tags",file); zmessageACK(Mwin,"%s \n too many tags",file);
break; break;
} }
} }
save_filemeta(file); // save tag changes save_filemeta(file); // save tag changes
} }
popup_report_write2(zd2,0," *** %s \n","COMPLETED"); popup_report_write2(zd2,0," *** %s \n","COMPLETED");
if (FGWM == 'G') gallery(0,"paint",-1); // refresh gallery // if (FGWM == 'G') gallery(0,"paint",-1); // refresh gallery remove 22.50
load_deftags(1); // update defined tags list load_deftags(1); // update defined tags list
Fblock("batch_tags",0); Fblock("batch_tags",0);
return; return;
} }
// mouse click functions for widgets holding tags // mouse click functions for widgets holding tags
void batch_addtags_clickfunc(GtkWidget *widget, int line, int pos, int kbkey) // a tag in the add list was clicked void batch_addtags_clickfunc(GtkWidget *widget, int line, int pos, int kbkey) // a tag in the add list was clicked
skipping to change at line 2790 skipping to change at line 2783
for (ii = 0; ii < Ntags; ii++) { for (ii = 0; ii < Ntags; ii++) {
zfree(oldtags[ii]); zfree(oldtags[ii]);
zfree(newtags[ii]); zfree(newtags[ii]);
} }
for (ii = 0; ii < Nfiles; ii++) for (ii = 0; ii < Nfiles; ii++)
zfree(filelist[ii]); zfree(filelist[ii]);
if (filelist) zfree(filelist); if (filelist) zfree(filelist);
if (FGWM == 'G') gallery(0,"paint",-1); // if gallery view, repaint // if (FGWM == 'G') gallery(0,"paint",-1); // if gallery view, repaint remove 22.50
return; return;
} }
// a defined tag was clicked // a defined tag was clicked
void batchrenametags_deftags_clickfunc(GtkWidget *widget, int line, int pos, int kbkey) void batchrenametags_deftags_clickfunc(GtkWidget *widget, int line, int pos, int kbkey)
{ {
using namespace batchrenametags; using namespace batchrenametags;
skipping to change at line 2903 skipping to change at line 2896
// batch change or shift photo date/time // batch change or shift photo date/time
void m_batch_photo_date_time(GtkWidget *, cchar *menu) void m_batch_photo_date_time(GtkWidget *, cchar *menu)
{ {
int batch_photo_time_dialog_event(zdialog *zd, cchar *event); int batch_photo_time_dialog_event(zdialog *zd, cchar *event);
cchar *keyname[1] = { "DateTimeOriginal" }; cchar *keyname[1] = { "DateTimeOriginal" };
char *keyvalue[1]; char *keyvalue[1];
char text[100]; char text[100];
char *file, olddatetime[24], newdatetime[24]; // exif format "yyyy:mm:dd hh:mm:ss" char *file, olddatetime[24], newdatetime[24]; // exif format "yyyy-mm-dd hh:mm:ss"
int ii, nn, cc, err, zstat; int ii, nn, cc, err, zstat;
int Fyearonly, Fdateonly; int Fyearonly, Fdateonly;
int Fsetnew, Fshift, Ftest; // check boxes int Fsetnew, Fshift, Ftest; // check boxes
time_t timep; time_t timep;
struct tm DTold, DTnew; // old and new date/time struct tm DTold, DTnew; // old and new date/time
int s_years, s_mons, s_mdays, s_hours, s_mins, s_secs; // shift amounts int s_years, s_mons, s_mdays, s_hours, s_mins, s_secs; // shift amounts
zdialog *zd, *zd2; zdialog *zd, *zd2;
F1_help_topic = "batch photo date"; F1_help_topic = "batch photo date";
skipping to change at line 3023 skipping to change at line 3016
Fyearonly = Fdateonly = 0; Fyearonly = Fdateonly = 0;
if (Fsetnew) // input is new date/time if (Fsetnew) // input is new date/time
{ {
zdialog_fetch(zd,"newdatetime",newdatetime,24); zdialog_fetch(zd,"newdatetime",newdatetime,24);
strTrim2(newdatetime); // strip leading and trailing blanks strTrim2(newdatetime); // strip leading and trailing blanks
cc = strlen(newdatetime); cc = strlen(newdatetime);
if (cc == 4) { // have only "yyyy" if (cc == 4) { // have only "yyyy"
strcat(newdatetime,"-01-01 00:00:00"); // append "-01-01 00:00:00" strcat(newdatetime,"-01-01 00:00:00"); // append "-01-01 00:00:00" 22.50
Fyearonly = 1; Fyearonly = 1;
cc = 19; cc = 19;
} }
if (cc == 10) { // have only "yyyy-mm-dd" if (cc == 10) { // have only "yyyy-mm-dd"
strcat(newdatetime," 00:00:00"); // append " 00:00:00" strcat(newdatetime," 00:00:00"); // append " 00:00:00"
Fdateonly = 1; // flag, change date only Fdateonly = 1; // flag, change date only
cc = 19; cc = 19;
} }
if (cc == 16) { // have only "yyyy-mm-dd hh:mm" if (cc == 16) { // have only "yyyy-mm-dd hh:mm"
strcat(newdatetime,":00"); // append ":00" strcat(newdatetime,":00"); // append ":00"
cc = 19; cc = 19;
} }
if (cc != 19) { // must have yyyy-mm-dd hh:mm:ss if (cc != 19) { // must have yyyy-mm-dd hh:mm:ss
zmessageACK(Mwin,"invalid date/time format"); zmessageACK(Mwin,"invalid date/time format");
goto retry; goto retry;
} }
nn = sscanf(newdatetime,"%d-%d-%d %d:%d:%d", // yyyy-mm-dd hh:mm:ss >> DTnew nn = sscanf(newdatetime,"%d-%d-%d %d:%d:%d", // yyyy-mm-dd hh:mm:ss >> DTnew 22.50
&DTnew.tm_year, &DTnew.tm_mon, &DTnew.tm_mday, &DTnew.tm_year, &DTnew.tm_mon, &DTnew.tm_mday,
&DTnew.tm_hour, &DTnew.tm_min, &DTnew.tm_sec); &DTnew.tm_hour, &DTnew.tm_min, &DTnew.tm_sec);
DTnew.tm_mon -= 1; // mktime month is 0-11 DTnew.tm_mon -= 1; // mktime month is 0-11
if (nn != 6) { // check input format if (nn != 6) { // check input format
zmessageACK(Mwin,"invalid date/time format"); zmessageACK(Mwin,"invalid date/time format");
goto retry; goto retry;
} }
timep = mktime(&DTnew); // DTnew >> timep timep = mktime(&DTnew); // DTnew >> timep
skipping to change at line 3098 skipping to change at line 3091
popup_report_write2(zd2,0,"\n"); // report progress popup_report_write2(zd2,0,"\n"); // report progress
popup_report_write2(zd2,0,"%s \n",file); popup_report_write2(zd2,0,"%s \n",file);
err = access(file,W_OK); // test file can be written by me err = access(file,W_OK); // test file can be written by me
if (err) { if (err) {
popup_report_write2(zd2,0,"%s \n","no write permission"); popup_report_write2(zd2,0,"%s \n","no write permission");
continue; continue;
} }
exif_get(curr_file,keyname,(char **) keyvalue,1); // metadata >> yyyy:mm:dd hh:mm:ss exif_get(curr_file,keyname,(char **) keyvalue,1); // metadata >> yyyy-mm-dd hh:mm:ss
if (! keyvalue[0] && Fshift) { // ignore if Fsetnew if (! keyvalue[0] && Fshift) { // ignore if Fsetnew
popup_report_write2(zd2,0," *** no date/time available \n"); popup_report_write2(zd2,0," *** no date/time available \n");
continue; continue;
} }
if (keyvalue[0]) { if (keyvalue[0]) {
strncpy0(olddatetime,keyvalue[0],20); // yyyy:mm:dd hh:mm:ss strncpy0(olddatetime,keyvalue[0],20); // yyyy-mm-dd hh:mm:ss
zfree(keyvalue[0]); zfree(keyvalue[0]);
} }
else strcpy(olddatetime,"0000:01:01 00:00:00"); // missing old date/time else strcpy(olddatetime,"0000-01-01 00:00:00"); // missing old date/time
nn = sscanf(olddatetime,"%d:%d:%d %d:%d:%d", // yyyy-mm-dd hh:mm:ss >> DTnew nn = sscanf(olddatetime,"%d-%d-%d %d:%d:%d", // yyyy-mm-dd hh:mm:ss >> DTnew
&DTold.tm_year, &DTold.tm_mon, &DTold.tm_mday, &DTold.tm_year, &DTold.tm_mon, &DTold.tm_mday,
&DTold.tm_hour, &DTold.tm_min, &DTold.tm_sec); &DTold.tm_hour, &DTold.tm_min, &DTold.tm_sec);
DTold.tm_mon -= 1; // mktime month is 0-11 DTold.tm_mon -= 1; // mktime month is 0-11
if (nn != 6 && Fshift) { if (nn != 6 && Fshift) {
popup_report_write2(zd2,0," *** EXIF date/time invalid \n"); popup_report_write2(zd2,0," *** EXIF date/time invalid \n");
continue; continue;
} }
if (nn != 6) strcpy(olddatetime,"0000:01:01 00:00:00"); // missing old date/time if (nn != 6) strcpy(olddatetime,"0000-01-01 00:00:00"); // missing old date/time
if (Fsetnew) // set new date/time if (Fsetnew) // set new date/time
{ {
if (Fyearonly) // change year only, leave rest if (Fyearonly) // change year only, leave rest
{ {
DTnew.tm_mon = DTold.tm_mon; // >> revised DTnew DTnew.tm_mon = DTold.tm_mon; // >> revised DTnew
DTnew.tm_mday = DTold.tm_mday; // set month/day/hour/min/sec only DTnew.tm_mday = DTold.tm_mday; // set month/day/hour/min/sec only
DTnew.tm_hour = DTold.tm_hour; // year remains fixed DTnew.tm_hour = DTold.tm_hour; // year remains fixed
DTnew.tm_min = DTold.tm_min; DTnew.tm_min = DTold.tm_min;
DTnew.tm_sec = DTold.tm_sec; DTnew.tm_sec = DTold.tm_sec;
skipping to change at line 3159 skipping to change at line 3152
} }
timep = mktime(&DTnew); timep = mktime(&DTnew);
if (timep < 0) { if (timep < 0) {
popup_report_write2(zd2,0," %s *** date/time conversion failed \n",old datetime); popup_report_write2(zd2,0," %s *** date/time conversion failed \n",old datetime);
continue; continue;
} }
DTnew = *localtime(&timep); DTnew = *localtime(&timep);
snprintf(newdatetime,20,"%04d:%02d:%02d %02d:%02d:%02d", // DTnew >> yyyy:mm:dd hh:mm:ss snprintf(newdatetime,20,"%04d-%02d-%02d %02d:%02d:%02d", // DTnew >> yyyy-mm-dd hh:mm:ss
DTnew.tm_year, DTnew.tm_mon+1, DTnew.tm_mday, // (tm_mon 0-11 >> 1-12) DTnew.tm_year, DTnew.tm_mon+1, DTnew.tm_mday, // (tm_mon 0-11 >> 1-12)
DTnew.tm_hour, DTnew.tm_min, DTnew.tm_sec); DTnew.tm_hour, DTnew.tm_min, DTnew.tm_sec);
olddatetime[4] = olddatetime[7] = newdatetime[4] = newdatetime[7] = '-'; // format: yyyy-mm-dd olddatetime[4] = olddatetime[7] = newdatetime[4] = newdatetime[7] = '-'; // format: yyyy-mm-dd 22.50
popup_report_write2(zd2,0," %s %s \n",olddatetime,newdatetime); popup_report_write2(zd2,0," %s %s \n",olddatetime,newdatetime);
if (Ftest) continue; // test only, no file updates if (Ftest) continue; // test only, no file updates
newdatetime[4] = newdatetime[7] = ':'; // format: yyyy:mm:dd for EXIF newdatetime[4] = newdatetime[7] = '-'; // format: yyyy-mm-dd for EXIF
keyvalue[0] = (char *) &newdatetime; keyvalue[0] = (char *) &newdatetime;
err = exif_put(curr_file,keyname,(cchar **) keyvalue,1); // yyyy:mm:dd hh:mm:ss >> metadata err = exif_put(curr_file,keyname,(cchar **) keyvalue,1); // yyyy-mm-dd hh:mm:ss >> metadata
load_filemeta(curr_file); // get all indexed data for file load_filemeta(curr_file); // get all indexed data for file
snprintf(meta_pdate,16,"%04d%02d%02d%02d%02d%02d", // update photo date, yyyymmddhhmmss snprintf(meta_pdate,16,"%04d%02d%02d%02d%02d%02d", // update photo date, yyyymmddhhmmss
DTnew.tm_year, DTnew.tm_mon+1, DTnew.tm_mday, // (tm_mon 0-11 >> 1-12) DTnew.tm_year, DTnew.tm_mon+1, DTnew.tm_mday, // (tm_mon 0-11 >> 1-12)
DTnew.tm_hour, DTnew.tm_min, DTnew.tm_sec); DTnew.tm_hour, DTnew.tm_min, DTnew.tm_sec);
update_image_index(curr_file); // update image index rec. update_image_index(curr_file); // update image index rec.
} }
popup_report_write2(zd2,0," *** %s \n","COMPLETED"); popup_report_write2(zd2,0," *** %s \n","COMPLETED");
Fblock("batch_photo_DT",0); Fblock("batch_photo_DT",0);
if (FGWM == 'G') gallery(0,"paint",-1); // if (FGWM == 'G') gallery(0,"paint",-1); // remove 22.50
m_batch_photo_date_time(0,0); // repeat m_batch_photo_date_time(0,0); // repeat
return; return;
} }
// dialog event and completion callback function // dialog event and completion callback function
int batch_photo_time_dialog_event(zdialog *zd, cchar *event) int batch_photo_time_dialog_event(zdialog *zd, cchar *event)
{ {
char countmess[80]; char countmess[80];
skipping to change at line 3420 skipping to change at line 3413
zd = 0; zd = 0;
for (ii = 0; ii < nkeys; ii++) { // free memory for (ii = 0; ii < nkeys; ii++) { // free memory
zfree((char *) pp1[ii]); zfree((char *) pp1[ii]);
zfree((char *) pp2[ii]); zfree((char *) pp2[ii]);
} }
nkeys = 0; nkeys = 0;
Fblock("batch_change_meta",0); Fblock("batch_change_meta",0);
if (FGWM == 'G') gallery(0,"paint",-1); // refresh gallery // if (FGWM == 'G') gallery(0,"paint",-1); // refresh gallery remove 22.50
return; return;
} }
// dialog event and completion callback function // dialog event and completion callback function
int batch_change_meta_dialog_event(zdialog *zd, cchar *event) int batch_change_meta_dialog_event(zdialog *zd, cchar *event)
{ {
using namespace batchchangemeta; using namespace batchchangemeta;
char countmess[80]; char countmess[80];
skipping to change at line 4015 skipping to change at line 4008
} }
popup_report_write2(zd2,0," *** %s \n","COMPLETED"); popup_report_write2(zd2,0," *** %s \n","COMPLETED");
cleanup: cleanup:
Fblock("batch_geotags",0); Fblock("batch_geotags",0);
if (zd) zdialog_free(zd); if (zd) zdialog_free(zd);
zd_mapgeotags = 0; zd_mapgeotags = 0;
if (FGWM == 'G') gallery(0,"paint",-1); // refresh gallery // if (FGWM == 'G') gallery(0,"paint",-1); // refresh gallery remove 22.50
return; return;
} }
// batch_geotags dialog event function // batch_geotags dialog event function
int batch_geotags_dialog_event(zdialog *zd, cchar *event) int batch_geotags_dialog_event(zdialog *zd, cchar *event)
{ {
int yn, zstat, err; int yn, zstat, err;
char countmess[80]; char countmess[80];
char location[100], country[100]; char location[100], country[100];
skipping to change at line 4387 skipping to change at line 4380
for (iig = 0; iig < Ngrec; iig++) for (iig = 0; iig < Ngrec; iig++)
{ {
utf8substring(country,grec[iig].country,0,26); // get graphic cc for UTF-8 names utf8substring(country,grec[iig].country,0,26); // get graphic cc for UTF-8 names
cc1 = 26 + strlen(country) - utf8len(country); cc1 = 26 + strlen(country) - utf8len(country);
utf8substring(location,grec[iig].location,0,26); utf8substring(location,grec[iig].location,0,26);
cc2 = 26 + strlen(location) - utf8len(location); cc2 = 26 + strlen(location) - utf8len(location);
strncpy0(pdate,grec[iig].pdate,9); // date, yyyymmdd strncpy0(pdate,grec[iig].pdate,9); // date, yyyymmdd
if (! strmatch(pdate,"null")) { if (! strmatch(pdate,"null")) {
memmove(pdate+8,pdate+6,2); // convert to yyyy-mm-dd memmove(pdate+8,pdate+6,2); // convert to yyyy-mm-dd 22.50
memmove(pdate+5,pdate+4,2); memmove(pdate+5,pdate+4,2);
pdate[4] = pdate[7] = '-'; pdate[4] = pdate[7] = '-';
pdate[10] = 0; pdate[10] = 0;
} }
popup_report_write2(zd2,0,"%-*s %-*s %-10s %6d \n", popup_report_write2(zd2,0,"%-*s %-*s %-10s %6d \n",
cc1,country,cc2,location,pdate,grec[iig].count); cc1,country,cc2,location,pdate,grec[iig].count);
} }
} }
skipping to change at line 4414 skipping to change at line 4407
for (iig = 0; iig < Ngrec; iig++) for (iig = 0; iig < Ngrec; iig++)
{ {
utf8substring(country,grec[iig].country,0,26); // get graphic cc for UTF-8 names utf8substring(country,grec[iig].country,0,26); // get graphic cc for UTF-8 names
cc1 = 26 + strlen(country) - utf8len(country); cc1 = 26 + strlen(country) - utf8len(country);
utf8substring(location,grec[iig].location,0,26); utf8substring(location,grec[iig].location,0,26);
cc2 = 26 + strlen(location) - utf8len(location); cc2 = 26 + strlen(location) - utf8len(location);
strncpy0(pdate,grec[iig].pdate,9); // date, yyyymmdd strncpy0(pdate,grec[iig].pdate,9); // date, yyyymmdd
if (! strmatch(pdate,"null")) { if (! strmatch(pdate,"null")) {
memmove(pdate+8,pdate+6,2); // convert to yyyy-mm-dd memmove(pdate+8,pdate+6,2); // convert to yyyy-mm-dd 22.50
memmove(pdate+5,pdate+4,2); memmove(pdate+5,pdate+4,2);
pdate[4] = pdate[7] = '-'; pdate[4] = pdate[7] = '-';
pdate[10] = 0; pdate[10] = 0;
} }
popup_report_write2(zd2,0,"%-10s %-*s %-*s %6d \n", popup_report_write2(zd2,0,"%-10s %-*s %-*s %6d \n",
pdate,cc1,country,cc2,location,grec[iig].count); pdate,cc1,country,cc2,location,grec[iig].count);
} }
} }
skipping to change at line 4810 skipping to change at line 4803
using namespace timeline_names; using namespace timeline_names;
int ii, jj, cc; int ii, jj, cc;
int Fnull = 0, Finvalid = 0; int Fnull = 0, Finvalid = 0;
int yy, mm; int yy, mm;
static int pline, ppos; static int pline, ppos;
char *txline, pdate[8], *pp, end; char *txline, pdate[8], *pp, end;
char albumfile[200]; char albumfile[200];
FILE *fid; FILE *fid;
xxrec_t *xxrec; xxrec_t *xxrec;
static int busy = 0;
if (busy) return;
// stop re-entry 22.50
busy++;
if (kbkey == GDK_KEY_F1) { // key F1 pressed, show help if (kbkey == GDK_KEY_F1) { // key F1 pressed, show help
showz_docfile(Mwin,"userguide",F1_help_topic); showz_docfile(Mwin,"userguide",F1_help_topic);
return; goto retx;
} }
if (line == -1) // arrow key navigation if (line == -1) // arrow key navigation
{ {
for (ii = 0; ii < 14; ii++) // current report column for (ii = 0; ii < 14; ii++) // current report column
if (ppos == colpos[ii]) break; if (ppos == colpos[ii]) break;
if (kbkey == GDK_KEY_Left) { // prior month if (kbkey == GDK_KEY_Left) { // prior month
if (ii > 2) ppos = colpos[ii-1]; if (ii > 2) ppos = colpos[ii-1];
else { else {
skipping to change at line 4861 skipping to change at line 4858
textwidget_scroll(widget,line); // keep line on screen textwidget_scroll(widget,line); // keep line on screen
pline = line; // remember chosen line, position pline = line; // remember chosen line, position
ppos = pos; ppos = pos;
pp = textwidget_word(widget,line,pos," ",end); // hilite clicked word pp = textwidget_word(widget,line,pos," ",end); // hilite clicked word
if (pp) textwidget_highlight_word(widget,line,pos,strlen(pp)); if (pp) textwidget_highlight_word(widget,line,pos,strlen(pp));
txline = textwidget_line(widget,line,1); // get clicked line txline = textwidget_line(widget,line,1); // get clicked line
if (! txline || ! *txline) return; if (! txline || ! *txline) goto retx;
cc = 0; cc = 0;
if (strmatchN(txline,"null",4)) Fnull = 1; // find images with null date if (strmatchN(txline,"null",4)) Fnull = 1; // find images with null date
else if (strmatchN(txline,"invalid",7)) Finvalid = 1; // find images with invalid date else if (strmatchN(txline,"invalid",7)) Finvalid = 1; // find images with invalid date
else if (pos < 13) { // clicked on year or year count else if (pos < 13) { // clicked on year or year count
strncpy0(pdate,txline,5); // have "yyyy" strncpy0(pdate,txline,5); // have "yyyy"
cc = 4; cc = 4;
} }
else // month was clicked else // month was clicked
{ {
mm = (pos - 13) / 5 + 1; // month, 1-12 mm = (pos - 13) / 5 + 1; // month, 1-12
if (mm < 1 || mm > 12) return; if (mm < 1 || mm > 12) goto retx;
strncpy(pdate,txline,4); // "yyyy" strncpy(pdate,txline,4); // "yyyy"
pdate[4] = '0' + mm/10; pdate[4] = '0' + mm/10;
pdate[5] = '0' + mm % 10; // have "yyyymm" pdate[5] = '0' + mm % 10; // have "yyyymm"
pdate[6] = 0; pdate[6] = 0;
cc = 6; cc = 6;
} }
snprintf(albumfile,200,"%s/timeline",albums_folder); snprintf(albumfile,200,"%s/timeline",albums_folder);
fid = fopen(albumfile,"w"); // open output file fid = fopen(albumfile,"w"); // open output file
if (! fid) { if (! fid) {
zmessageACK(Mwin,"file error: %s",strerror(errno)); zmessageACK(Mwin,"file error: %s",strerror(errno));
return; goto retx;
} }
if (Fblock("timeline","block edits")) return;
// check pending, block
if (Fusesearch) // include prior search results if (Fusesearch) // include prior search results
{ {
for (ii = 0; ii < Nsearch; ii++) for (ii = 0; ii < Nsearch; ii++)
{ {
zmainloop(100); // keep GTK alive zmainloop(100); // keep GTK alive
xxrec = get_xxrec(zlist_get(filelist,ii)); xxrec = get_xxrec(zlist_get(filelist,ii));
if (! xxrec) continue; if (! xxrec) continue;
if (Fnull) { // search for missing dates if (Fnull) { // search for missing dates
skipping to change at line 4975 skipping to change at line 4970
yy = jj / 100; // 0 to 2099 yy = jj / 100; // 0 to 2099
mm = jj - yy * 100; // 1 to 12 mm = jj - yy * 100; // 1 to 12
if (yy < 0 || yy >= Nyears || mm < 1 || mm > 12) continue; // invalid, reject if (yy < 0 || yy >= Nyears || mm < 1 || mm > 12) continue; // invalid, reject
fprintf(fid,"%s\n",xxrec->file); // output matching file fprintf(fid,"%s\n",xxrec->file); // output matching file
} }
} }
} }
fclose(fid); fclose(fid);
Fblock("timeline",0);
navi::gallerytype = SEARCH; // search results navi::gallerytype = SEARCH; // search results
gallery(albumfile,"initF",0); // generate gallery of matching files gallery(albumfile,"initF",0); // generate gallery of matching files
gallery(0,"paint",0); gallery(0,"paint",0);
m_viewmode(0,"G"); m_viewmode(0,"G");
retx:
busy = 0;
return; return;
} }
/******************************************************************************* */ /******************************************************************************* */
// Search image tags, geotags, dates, ratings, titles, descriptions // Search image tags, geotags, dates, ratings, titles, descriptions
// to find matching images. This is fast using the image index. // to find matching images. This is fast using the image index.
// Search also any other metadata, but relatively slow. // Search also any other metadata, but relatively slow.
namespace search_images namespace search_images
skipping to change at line 5371 skipping to change at line 5368
// search images dialog event and completion callback function // search images dialog event and completion callback function
int searchimages_dialog_event(zdialog *zd, cchar *event) int searchimages_dialog_event(zdialog *zd, cchar *event)
{ {
int datetimeOK(char *datetime); int datetimeOK(char *datetime);
int searchimages_metadata_dialog(zdialog *zd); int searchimages_metadata_dialog(zdialog *zd);
int searchimages_metadata_report(void); int searchimages_metadata_report(void);
void *searchimages_thread(void *); void *searchimages_thread(void *);
cchar dateLoDefault[20] = "0000-01-01 00:00:00"; // start of time cchar dateLoDefault[20] = "0000-01-01 00:00:00"; // start of time 22.50
cchar dateHiDefault[20] = "2099-12-31 23:59:59"; // end of time cchar dateHiDefault[20] = "2099-12-31 23:59:59"; // end of time
char *file, *file2, **vlist; char *file, *file2, **vlist;
char **flist, *pp, buffer[XFCC]; char **flist, *pp, buffer[XFCC];
char mm[4] = "mm"; char mm[4] = "mm";
int ii, jj, kk, err, cc; int ii, jj, kk, err, cc;
int nt, cc1, cc2, ff, nf; int nt, cc1, cc2, ff, nf;
char *pp1, *pp2; char *pp1, *pp2;
char entertag[tagcc], matchtags[20][tagcc]; char entertag[tagcc], matchtags[20][tagcc];
char matchtagstext[(tagcc+2)*20]; char matchtagstext[(tagcc+2)*20];
skipping to change at line 5598 skipping to change at line 5595
Fnulldate = 1; // (user input "null") Fnulldate = 1; // (user input "null")
Fdates = Ffiledate = Fphotodate = 0; Fdates = Ffiledate = Fphotodate = 0;
zdialog_stuff(zd,"photodate",1); zdialog_stuff(zd,"photodate",1);
zdialog_stuff(zd,"filedate",0); zdialog_stuff(zd,"filedate",0);
} }
if (Fdates) // complete partial date/time data if (Fdates) // complete partial date/time data
{ {
cc = strlen(searchDateFrom); cc = strlen(searchDateFrom);
for (ii = cc; ii < 20; ii++) // default date from: for (ii = cc; ii < 20; ii++) // default date from:
searchDateFrom[ii] = dateLoDefault[ii]; // 0000-01-01 00:00:00 searchDateFrom[ii] = dateLoDefault[ii]; // 0000-01-01 00:00:00 22.50
cc = strlen(searchDateTo); cc = strlen(searchDateTo);
for (ii = cc; ii < 20; ii++) // default date to: for (ii = cc; ii < 20; ii++) // default date to:
searchDateTo[ii] = dateHiDefault[ii]; // 2099-12-31 23:59:59 searchDateTo[ii] = dateHiDefault[ii]; // 2099-12-31 23:59:59 22.50
if (cc == 7) { // input was yyyy-mm if (cc == 7) { // input was yyyy-mm
strncpy(mm,searchDateTo+5,2); // get mm = "01" .. "12" strncpy(mm,searchDateTo+5,2); // get mm = "01" .. "12"
if (strstr("04 06 09 11",mm)) memmove(searchDateTo+8,"30",2); // set dd = 30 for these months if (strstr("04 06 09 11",mm)) memmove(searchDateTo+8,"30",2); // set dd = 30 for these months
if (strmatch(mm,"02")) { if (strmatch(mm,"02")) {
memmove(searchDateTo+8,"28",2); // set dd = 28 for month 02 memmove(searchDateTo+8,"28",2); // set dd = 28 for month 02
ii = atoi(searchDateTo); ii = atoi(searchDateTo);
if (ii == (ii/4)*4) memmove(searchDateTo+8,"29",2); // set dd = 29 if leap year if (ii == (ii/4)*4) memmove(searchDateTo+8,"29",2); // set dd = 29 if leap year
} }
} }
skipping to change at line 6867 skipping to change at line 6864
// convert yyyy-mm-dd hh:mm[:ss] to yyyymmddhhmmss // convert yyyy-mm-dd hh:mm[:ss] to yyyymmddhhmmss
char * pdatetime_metadatetime(cchar *pdatetime) char * pdatetime_metadatetime(cchar *pdatetime)
{ {
char pdate[12], ptime[12]; char pdate[12], ptime[12];
static char metadatetime[20]; static char metadatetime[20];
char *pp; char *pp;
int cc; int cc;
strncpy0(pdate,pdatetime,11); // yyyy-mm-dd strncpy0(pdate,pdatetime,11); // yyyy:mm:dd
strncpy0(ptime,pdatetime+11,9); // hh:mm[:ss] strncpy0(ptime,pdatetime+11,9); // hh:mm[:ss]
cc = strlen(ptime); cc = strlen(ptime);
if (cc == 5) strcat(ptime,":00"); // hh:mm >> hh:mm:00 if (cc == 5) strcat(ptime,":00"); // hh:mm >> hh:mm:00
else if (cc != 8) return 0; else if (cc != 8) return 0;
pp = pdate_metadate(pdate); pp = pdate_metadate(pdate);
if (! pp) return 0; if (! pp) return 0;
strncpy0(metadatetime,pp,9); strncpy0(metadatetime,pp,9);
skipping to change at line 9856 skipping to change at line 9853
// get EXIF/IPTC metadata for given image file and EXIF/IPTC key(s) // get EXIF/IPTC metadata for given image file and EXIF/IPTC key(s)
// returns array of pointers to corresponding key values // returns array of pointers to corresponding key values
// if a key is missing, corresponding pointer is null // if a key is missing, corresponding pointer is null
// returned strings belong to caller, are subject for zfree() // returned strings belong to caller, are subject for zfree()
// up to 100 keynames may be requested per call // up to 100 keynames may be requested per call
// returns: 0 = OK, +N = error // returns: 0 = OK, +N = error
int exif_get(cchar *file, cchar **keys, char **kdata, int nkeys) int exif_get(cchar *file, cchar **keys, char **kdata, int nkeys)
{ {
char *pp, *pp2, geostring[20]; char *pp, *pp2, *pp3, geostring[20];
char *inputs[120], *outputs[100]; // higher limits char *inputs[120], *outputs[100]; // higher limits
int ii, jj, err, NP; int ii, jj, err, NP;
float geofloat; float geofloat;
uint cc, ucc; uint cc, ucc;
if (nkeys < 1 || nkeys > 99) zappcrash("exif_get nkeys: %d",nkeys); if (nkeys < 1 || nkeys > 99) zappcrash("exif_get nkeys: %d",nkeys);
cc = nkeys * sizeof(char *); // initz. outputs = null cc = nkeys * sizeof(char *); // initz. outputs = null
memset(kdata,0,cc); memset(kdata,0,cc);
inputs[0] = (char *) "-m"; inputs[0] = (char *) "-m";
// options for exiftool // ignore minor errors
inputs[1] = (char *) "-s2"; inputs[1] = (char *) "-s2";
inputs[2] = (char *) "-n"; // get tag names not descriptions
inputs[3] = (char *) "-fast"; inputs[2] = (char *) "-n";
// -fast2 loses maker notes // get native values not human-readable
inputs[3] = (char *) "-fast";
// speedup (-fast2 loses maker notes)
///inputs[6] = (char *) "-q";
// -quiet causes exiftool hangup 22.50
///inputs[4] = (char *) "-d";
// date format DOES NOT WORK 22.50
///inputs[5] = (char *) "%Y-%m-%d %H:%M:%S";
// yyyy-mm-dd hh:mm:ss 22.50
jj = NP = 4; jj = NP = 4;
for (ii = 0; ii < nkeys; ii++) // build exiftool inputs for (ii = 0; ii < nkeys; ii++) // build exiftool inputs
{ {
cc = strlen(keys[ii]); // -keyname cc = strlen(keys[ii]); // -keyname
if (! cc) { if (! cc) {
Plog(0,"exif_get() null key \n"); Plog(0,"exif_get() null key \n");
return 1; return 1;
} }
skipping to change at line 9921 skipping to change at line 9921
} }
pp2 = strchr(pp,':'); // check ':' delimiter pp2 = strchr(pp,':'); // check ':' delimiter
if (! pp2 || strlen(pp2) < 2 || strmatchN(pp2,": null",6)) { // check not keyname: null if (! pp2 || strlen(pp2) < 2 || strmatchN(pp2,": null",6)) { // check not keyname: null
zfree(pp); zfree(pp);
continue; continue;
} }
pp2 += 2; // key value pp2 += 2; // key value
if (strstr(pp,"Date")) {
// if Date or Date-Time value 22.50
pp3 = strchr(pp2,':');
// fix yyyy:mm:dd --> yyyy-mm-dd
if (pp3 && *pp3 == ':' && strlen(pp3) > 2 && *(pp3+3) == ':')
// ...Date... yyyy:mm:dd
*pp3 = *(pp3+3) = '-';
// | |
}
// pp3 +3
for (jj = 0; jj < nkeys; jj++) // loop exif_server inputs for (jj = 0; jj < nkeys; jj++) // loop exif_server inputs
{ {
if (strmatchN(keys[jj],"GPSLatitude",11)) { // round to 5 decimal places if (strmatchN(keys[jj],"GPSLatitude",11)) { // round to 5 decimal places
if (! strmatchN(pp,"GPSLatitude",11)) continue; if (! strmatchN(pp,"GPSLatitude",11)) continue;
err = convSF(pp2,geofloat); err = convSF(pp2,geofloat);
if (err > 1) continue; if (err > 1) continue;
snprintf(geostring,20,"%.5f",geofloat); snprintf(geostring,20,"%.5f",geofloat);
kdata[jj] = zstrdup(geostring,"exif-get"); // return key data kdata[jj] = zstrdup(geostring,"exif-get"); // return key data
} }
else if (strmatchN(keys[jj],"GPSLongitude",12)) { // round to 5 decimal places else if (strmatchN(keys[jj],"GPSLongitude",12)) { // round to 5 decimal places
skipping to change at line 10247 skipping to change at line 10253
if (outputs && ii < Nrecs) if (outputs && ii < Nrecs)
outputs[ii] = zstrdup(outrec,"exif-server"); // add to returned records outputs[ii] = zstrdup(outrec,"exif-server"); // add to returned records
} }
return 0; return 0;
} }
/******************************************************************************* */ /******************************************************************************* */
// convert between EXIF and fotoxx tag date formats // convert between EXIF and fotoxx tag date formats
// EXIF date: yyyy:mm:dd hh:mm:ss 20 chars. // EXIF date: yyyy-mm-dd hh:mm:ss 20 chars.
// tag date: yyyymmddhhmmss 16 chars. // tag date: yyyymmddhhmmss 16 chars.
// //
void exif_tagdate(cchar *exifdate, char *tagdate) void exif_tagdate(cchar *exifdate, char *tagdate)
{ {
int cc; int cc;
memset(tagdate,0,15); memset(tagdate,0,15);
cc = strlen(exifdate); cc = strlen(exifdate);
skipping to change at line 10275 skipping to change at line 10281
return; return;
} }
void tag_exifdate(cchar *tagdate, char *exifdate) void tag_exifdate(cchar *tagdate, char *exifdate)
{ {
int cc; int cc;
memset(exifdate,0,20); memset(exifdate,0,20);
cc = strlen(tagdate); cc = strlen(tagdate);
strcpy(exifdate,"1900:01:01 00:00:00"); strcpy(exifdate,"1900-01-01 00:00:00");
if (cc > 3) strncpy(exifdate+0,tagdate+0,4); if (cc > 3) strncpy(exifdate+0,tagdate+0,4);
if (cc > 5) strncpy(exifdate+5,tagdate+4,2); if (cc > 5) strncpy(exifdate+5,tagdate+4,2);
if (cc > 7) strncpy(exifdate+8,tagdate+6,2); if (cc > 7) strncpy(exifdate+8,tagdate+6,2);
if (cc > 9) strncpy(exifdate+11,tagdate+8,2); if (cc > 9) strncpy(exifdate+11,tagdate+8,2);
if (cc > 11) strncpy(exifdate+14,tagdate+10,2); if (cc > 11) strncpy(exifdate+14,tagdate+10,2);
if (cc > 13) strncpy(exifdate+17,tagdate+12,2); if (cc > 13) strncpy(exifdate+17,tagdate+12,2);
exifdate[19] = 0; exifdate[19] = 0;
return; return;
} }
 End of changes. 45 change blocks. 
57 lines changed or deleted 72 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)