Hallo all,

i working currently on a small project which interpret DCIM name system
produced by cameras. I started it to fix my photo workflow.
The programs like F-Spot are big monsters if you use it on netbook.
Especially if you use it only to import, tag and search photos. Most of
this can be done by tracker alone.

Normally all you see if you import photos from the camera, is a batch of
files. For me these are groups, like panorama, interval and normal
photos, audio comments and filtered or edited photos related to some
original photos. All of this is possible to interpret if they are on the
cam. After you import it this information is mostly lost.
My idea is to tag and group this photos on import, and safe this
information to tracker.

In attachment is my initial work written in c. I'll be happy to hear you
constructive comments to this work, and ideas how to integrate it with
tracker.

My c need some love. i currently learning c by my self and to make some
practice choiced this target.
/*
 *  walker.c
 *
 *  This is parser for DCF name system found on all modern digital cameras,
 *  and provide interpretation, you can use to tag photos.
 *
 *  Author: Alexey Fisher <[email protected]>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 3 as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; see the file COPYING. If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
/*
 * [wiki] "Design rule for Camera File system (DCF) is a JEITA specification 
 * (number CP-3461) which defines a file format and file system for digital
 * cameras, including the directory structure, file naming method, character
 * set, file format, and metadata format. It is currently the de facto
 * industry standard for digital still cameras. The file format of DCF is
 * based on the Exif 2.2 specification."
 *
 * Technical detiles:
 * It use static file name lenth, so we do not need to care about how to parse
 * dynamic names.
 *
 * independet of camera you use names have same lenth:
 * DCIM/100NIKON/DSCN1234.JPG
 * ^^^^ ^^^^^^^^ ^^^^^^^^^^^^
 *   4     8          12     -    chars
 */
#define _XOPEN_SOURCE 500
#include <ftw.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define D_UNCN 0x0f
#define D_DCIM 0x01
#define D_MISC 0x02
#define Q_LAST_4 0x4
#define Q_THIRST_3 0x3

#define JPG_IMG 0x01
#define RAW_IMG 0x02
#define AUDIO   0x03
#define VIDEO   0x04
#define PRINT_JOB 0x10

#define NICON 0x01
#define CANON 0x02
#define FUIJI 0x03
#define PANAS 0x04 /* panasonic */

#define GR_NONE 0x00 /* do not set any group */
#define GR_PANR 0x01 /* panorama group */
#define GR_INTV 0x02 /* interval group */
#define VEND_ID 0x03 /* we can match vendor here */
#define REL_MOD 0x04 /* related to some photo */
#define REL_COM 0x05

#define MATCH 1
#define NOT_MATCH 0

/**
 * struct filebuffer - storage for filenames we can process. We put unsorted,
 * sort it, check for dependencies and push out.
 * @file - path to the file (DCIM/101NIKON/DSCN0001.JPG = 26 chars + \0)
 * @number - image number (DSCN2561.JPG = 2561)
 * @tag - file tags, (panorama, interval photo, audio comment)
 * @file_type - type of produced file (jpg image, raw image, audio, video) 
 */
struct filebuffer {
	char file[27];
	int number;
	int tag;
	int print;
	int file_type;
} filebuffer[255];

/**
 * compare_int -  function needed for sorting filebuffer array.
 * @c1,c2 - pointers to filebuffer struct.
 */
int compare_int(const void *c1, const void *c2)
{
	struct filebuffer *info1 = (struct filebuffer *) c1;
	struct filebuffer *info2 = (struct filebuffer *) c2;

	if (info1->number > info2->number)
		return 1;
	else if (info1->number < info2->number)
		return -1;
	else {
		/* file with same number is (mostly?) audio comment,
		 * just make sure audio file ordered after image file */
		if (((info2->file_type == JPG_IMG) ||
			    (info2->file_type == RAW_IMG)) &&
			   ((info1->file_type == AUDIO) ||
			    (info1->file_type == VIDEO)))
			return 1;
		else if (((info1->file_type == JPG_IMG) ||
			    (info1->file_type == RAW_IMG)) &&
			   ((info2->file_type == AUDIO) ||
			    (info2->file_type == VIDEO)))
			return -1;
		else
		return 0;
	}
}

/**
 * struct extinfo - structure for extension comparision. We convert here
 * extension string to file_type integer. Every thing out of this list will
 * not be processed!
 * @ext - file extension we can handle, just include all image extensions.
 * @file_type - we reduce different extensions to generic file type
 * for example image, audio, video.
 */
struct extinfo {
	const char *ext;
	const int file_type;
} extinfo[] = {
	{"JPG",		JPG_IMG},
	{"JPEG",	JPG_IMG}, /* do not belong to DCIM */

	{"NEF",		RAW_IMG}, /* nikon */
	{"CRW",		RAW_IMG}, /* canon */
	{"RAF",		RAW_IMG}, /* fuji */

	{"WAV",		AUDIO},

	{"AVI",		VIDEO},
	{"MOV",		VIDEO}, /* multi type - video, or audio comment */

	{"MRK",		PRINT_JOB}
};

#define sizeof_extinfo (sizeof(extinfo))/sizeof(struct extinfo)
/**
 * compare_ext - compare function needed to sort and search in extinfo array.
 */
int compare_ext(const void *c1, const void *c2)
{
	struct extinfo *info1 = (struct extinfo *) c1;
	struct extinfo *info2 = (struct extinfo *) c2;
	return strcasecmp(info1->ext, info2->ext);
}

/**
 * struct pattern_db - structure needed to match of file system,
 * all digits converted to xxx.
 * @pattern - pattern of DCIM fs.
 * @vendor - vendor name of DCIM fs.
 * @quirk - maske to read numerik part.
 * @tag - tag provided directory name.
 */
struct pattern_db {
	const char *pattern;
	const int vendor;
	const int quirk;
	const int tag;
} pattern_db[] = {
	/* nicon */
	{"xxxNIKON",	NICON,	Q_THIRST_3,	VEND_ID},
	{"xxxP_xxx",	NICON,	Q_THIRST_3,	GR_PANR},
	{"xxxINTVL",	NICON,	Q_THIRST_3,	GR_INTV},

	/*  WAV in sound folder - voice record */
	{"xxxSOUND",	NICON,	Q_THIRST_3,	GR_NONE},

	/*  JPG, AVI, WAV(comment)to in base */
	{"DSCNxxxx",	NICON,	Q_LAST_4,	GR_NONE},

	/* same exif time to base. */
	{"SSCNxxxx",	NICON,	Q_LAST_4,	REL_MOD},
	{"FSCNxxxx",	NICON,	Q_LAST_4,	REL_MOD},

	/* canon */
	{"xxxCANON",	CANON,	Q_THIRST_3,	VEND_ID},
	{"CANONMSC",	CANON,	0,		GR_NONE},

	{"IMG_xxxx",	CANON,	Q_LAST_4,	GR_NONE}, /* JPG */
	{"CRW_xxxx",	CANON,	Q_LAST_4,	GR_NONE}, /* CRW */

	/* JPG, b = A ertse pan grup, B, C */
	{"STx_xxxx",	CANON,	Q_LAST_4,	GR_PANR},

	/* AVI or THM */
	{"MVI_xxxx",	CANON,	Q_LAST_4,	GR_NONE},

	/* WAV , xxxx same as jpg to com */
	{"SND_xxxx",	CANON,	Q_LAST_4,	GR_NONE},

	{"xxx_FUJI",	FUIJI,	Q_THIRST_3,	VEND_ID},

	/*  JPG, RAF (raw), AVI, WAV (WAV with same xxxx as JPG is comment) */
	{"DSCFxxxx",	FUIJI,	Q_LAST_4,	GR_NONE},

	{"xxx_PANA",	PANAS,	Q_THIRST_3,	VEND_ID},

	/* JPG, MOV (vid), MOV aud comment xxx same for JPG */
	{"Pxxxxxxx",	PANAS,	Q_LAST_4,	GR_NONE}
};

#define sizeof_pattern_db (sizeof(pattern_db))/sizeof(struct pattern_db)

/**
 * compare_pattern - compare function needed to sort and search in pattern_db
 */
int compare_pattern (const void *c1, const void *c2)
{
	struct pattern_db *info1 = (struct pattern_db *) c1;
	struct pattern_db *info2 = (struct pattern_db *) c2;
	return strcasecmp(info1->pattern, info2->pattern);
}

/**
 * struct current - structure for temporary storage.
 * @dcim_1 - flag we set if we found DCIM folder.
 * @l2_tag - information provided on second level of DCIM system.
 * @l3_tag - information provided on third level of DCIM system.
 * @number - number we get from file name.
 * @vendor - vendor name of fs.
 * @level - current directory level we in.
 * @bufnum - number of buffer array.
 * @file_type - file type we currently process.
 */
struct current {
	int dcim_1;
	int l2_tag;
	int l3_tag;
	int number;
	int vendor;
	int level;
	int bufnum;
	int file_type;
} current;


/**
 * match_pattern - function to match pattern of DCIM fs.
 */
static int match_pattern(const char *name, int tflag, struct FTW *ftwbuf)
{
	char patt_tmp[8];
	int i = 0;
	char val[5];
	struct pattern_db key, *res;

	/* convert all digits to x, so we can match this pattern */
	while (i < 9) {
		if ((name[i] >= '0') && (name[i] <= '9')) {
			patt_tmp[i] = 'x';
		} else
			patt_tmp[i] = name[i];
		/* exception for canon, it use letter for panorama group */
		if ((i == 2) && (ftwbuf->level == 3) &&
				(current.vendor == CANON) &&
				(name[0] == 'S' && name[1] == 'T'))
			patt_tmp[i] = 'x';
		/* if we parse filenames we have patt_tmp[8] == '.',
		 * so let us make sure it is '\0' */
		if (i == 8)
			patt_tmp[i] = '\0';
		i += 1;
	}

	/* search for pattern */
	key.pattern = patt_tmp;
	res = bsearch(&key, pattern_db, sizeof_pattern_db,
			sizeof(struct pattern_db), compare_pattern);
	/* stop here if didn't found */
	if (res == NULL)
		return NOT_MATCH;

	/* after we know what we have, we can use quirk for numerik part */
	if (res->quirk == Q_THIRST_3)
		sscanf(name, "%3s", val);
	else if (res->quirk == Q_LAST_4)
		sscanf(name, "%*4c%4s", val);

	current.number = atoi(val);

	/* not all pattern provide us vendor id, just test to be sure */
	if (res->tag == VEND_ID)
		current.vendor = res->vendor;

	/* we can set l2_tag now, we get here only on level 2
	 * and we will use this info on level 3 too */
	if (ftwbuf->level == 2)
		current.l2_tag = res->tag;
	else if (ftwbuf->level == 3) {
		current.l3_tag = res->tag;
		/* if panorama or interval photo,
		 * apply level 2 tag to level 3 */
		if (((current.l2_tag == GR_PANR) ||
				(current.l2_tag == GR_INTV)) &&
				(current.l3_tag == GR_NONE))
			current.l3_tag = current.l2_tag;
	}

	return MATCH;
}

/**
 * check_file_ext - function to check file extention, and file type.
 */
static int check_file_ext(const char *file_name)
{
	char *val;
	struct extinfo key, *res;

	/* first of all find point in file name */
	val = strrchr(file_name, '.');

	/* if there is no point, drop it */
	if (val == NULL)
		return NOT_MATCH;

	/* map pinter to the next char after point */
	++val;
	key.ext = val;
	/* search for  file extension in extinfo database */
	res = bsearch(&key, extinfo, sizeof_extinfo,
			sizeof(struct extinfo), compare_ext);

	if (res == NULL)
		return NOT_MATCH;
	/* we may need file type info later  */
	current.file_type = res->file_type;

	return MATCH;
}

/**
 * direct_out - push out directly files we do not care of but are images.
 */
static int direct_out(const char *fpath)
{
	printf("direct: %s\n", fpath);
	return 0;
}

/**
 * buff_trigger - sort, parse and push out content of file buffer.
 * in file buffer are only files we can match.
 */
static int buff_trigger(void)
{
	if (current.bufnum != 0) {
		int i = 0;

		printf("drop old and start new\n");

		/* sort filebuffer by file numbers */
		qsort(filebuffer, current.bufnum,
				sizeof(struct filebuffer), compare_int);
		while (i < current.bufnum) {
			/* push out sortet file */
			printf("buf: %s, %i, %i\n",
				filebuffer[i].file, filebuffer[i].tag,
					filebuffer[i].number);

			/* check this file and file before have same name.
			 * Only audio comments have same file names */
			if ((filebuffer[i].number == filebuffer[i-1].number) &&
			   ((filebuffer[i-1].file_type == JPG_IMG) ||
			    (filebuffer[i-1].file_type == RAW_IMG)) &&
			   ((filebuffer[i].file_type == AUDIO) ||
			    (filebuffer[i].file_type == VIDEO))) {
				printf("tadaaaaa thi is audio comment\n");
			}
			i += 1;
		}
		/* reset buffer counter */
		current.bufnum = 0;
	}
	return 0;
}

/**
 * add_to_buf - add matched files to buffer.
 */
static int add_to_buf(const char *fpath)
{
	const char *ptr;

	/* DCIM structure is static
	 * DCIM/101CANON/STA_1256.JPG = 26 char.
	 * we remove here prefix and leave only interesting part */
	ptr = fpath + (strlen(fpath) - 26);

	/* copy content to the buffer */
	strcpy(filebuffer[current.bufnum].file, ptr);
	filebuffer[current.bufnum].number = current.number;
	filebuffer[current.bufnum].tag = current.l3_tag;
	filebuffer[current.bufnum].file_type = current.file_type;

	/* update buffer counter */
	current.bufnum += 1;

	return 0;
}

/**
 * belong_to_dcim - function to check if processed names belong to
 * DCIM name space.
 */
static int belong_to_dcim(const char *fpath, int tflag, struct FTW *ftwbuf)
{

	/* check if there is a struction we know about */
	if ((ftwbuf->level == 1)) {
		/* we know about DCIM and MISC, 
		 * if there are images outside of this, 
		 * we still can copy them, but we do not need to 
		 * tag it.
		 */
		if ((tflag == FTW_D) && 
			    (!strcmp(fpath + ftwbuf->base,  "DCIM"))) {
			current.dcim_1 = D_DCIM;
			/* reset l2_tag we update dcim_1 */
			current.l2_tag = D_UNCN;
			return MATCH;
		} else if ((tflag == FTW_D) &&
			    (!strcmp(fpath + ftwbuf->base,  "MISC"))) {

			current.dcim_1 = D_MISC;
			current.l2_tag = D_UNCN;
			return MATCH;
		} else {
			current.dcim_1 = D_UNCN;
			current.l2_tag = D_UNCN;
			return NOT_MATCH;
		}
	} else if (ftwbuf->level == 2) {
		/* on this level should be only dirs,
		 * do not bother to match it */
		if ((tflag == FTW_D) &&
				(current.dcim_1 == D_DCIM) &&
				(strlen(fpath + ftwbuf->base) == 8)) {

			if (match_pattern(fpath + ftwbuf->base,
						tflag, ftwbuf) == MATCH)
				return MATCH;
			else
				current.l2_tag = D_UNCN;
				return NOT_MATCH;
		} else {
			current.l2_tag = D_UNCN;
			return NOT_MATCH;
		}
	} else if (ftwbuf->level == 3) {
		if ((tflag == FTW_F) &&
				/* extra check dir we currently in */
				(current.l2_tag != D_UNCN) &&
				(strlen(fpath + ftwbuf->base) == 12)) {

			if (match_pattern(fpath + ftwbuf->base,
						tflag, ftwbuf) == MATCH)
				return MATCH;
		}
	}
	return NOT_MATCH;
}

static int prezess_file(const char *fpath, const struct stat *sb,
				int tflag, struct FTW *ftwbuf)
{
	int err;

	/* drop files which extensions we do not know */
	if ((tflag == FTW_F) && 
			(check_file_ext(fpath + ftwbuf->base) == NOT_MATCH))
		return 0;

	/* check if we are in DCIM structure */
	err = belong_to_dcim(fpath, tflag, ftwbuf);

	/* trigger buffer if we in a new directory */
	if (current.level != ftwbuf->level)
		buff_trigger();

	current.level = ftwbuf->level;

	/* now we need only files */
	if (tflag != FTW_F)
		return 0;

	/* patterns we can match got to the buffer, other image files
	 * go direct out. We need buffer here to sort things before we
	 * can put it together */
	if (err == MATCH)
		add_to_buf(fpath);
	else
		direct_out(fpath);

	return 0;
	
}

int main(int argc, char *argv[])
{
	current.bufnum = 0;

	/* create our tmp storage */
	struct current *curr_pnt;
	curr_pnt = &current;

	/* sort databases */
	qsort(extinfo, sizeof_extinfo,
			sizeof(struct extinfo), compare_ext);
	qsort(pattern_db, sizeof_pattern_db,
			sizeof(struct pattern_db), compare_pattern);

	/* read directory tree */
	if (nftw((argc < 2) ? "." : argv[1], prezess_file, 20,
					FTW_PHYS | FTW_MOUNT) == -1) {
		perror("nftw");
		exit(EXIT_FAILURE);
	}
	/* trigger the buffer if we forgot some thing in there */
	if (current.bufnum != 0)
		buff_trigger();

	exit(EXIT_SUCCESS);
}
_______________________________________________
tracker-list mailing list
[email protected]
http://mail.gnome.org/mailman/listinfo/tracker-list

Reply via email to