/* File: videocat.c Author: Graham Toal <gtoal@gtoal.com> Copyright: None. You're welcome to use this any way you want. Created: 200808231514 Revision history: 200808231514 Initial version 200809061157 Added some comments prior to release. Removed unused procedure. Made sure windows and unix sources were aligned. */ static char *version = "200809061157"; /* Description: This is a utility I hacked up to scan external drives for DVD images (and some other video files) in order to create a searchable database to help me find my videos. (All my original DVDs are boxed up and stored. Moving my DVD collection to HD has given us back about half a room of shelf space!) If you only have one or two external drives, this won't help you much, but if you have a couple of dozen it'll help a lot. You also need some sort of database manager to actually use the data (unless you just want to grep it), but this outputs in CSV format so should be usable by most database that support an external load. Invoke with a drive letter as a parameter followed by a verbal description of the drive and its location, so you can find it later. The code looks for VIDEO_TS.IFO (originally I checked using video_ts.vob but some valid dvd images didn't have one. Later added some video files such as .avi and .divx), extracts the parent directory name, saves in database with the location of movie which you add as a command-line parameter. Example: videocat d:\ "LaCie 500Gb marked 'box sets'" > lacie.csv (on windows) videocat /mnt/videos "Avi conversions from HD videocam" (on unix) videocat j: >> catalog.csv TODO: extract drive title to store along with drive letter allow for updates to replace old data in csv file, if you carelessly merged all your runs of this program into one file. Otherwise to recreate the main database you either have to work out how to remove selected entries or rerun the program on all external drives. find a windows programmer to put a gui over it and make it robust enough for use by non-programmers BUGS: Not 100% confident in the Windows tree-walking code. Not sure about portability across windows compilers. I'm compiling it with TCC ( http://bellard.org/tcc/ ) and the code I based this on was previously compiled with LCC ( http://www.cs.virginia.edu/~lcc-win32/ ) - anything else, you may need to tweak a little. There may be problems caused by specifying a trailing '\' in a directory name. Need to check on dos/windows that specifying "D:" does what is expected even if someone earlier did a "CD subdir" on that drive. (Mind you, not entirely sure what *is* expected...) ---------------------------------------------------------- */ /* Standard C libs */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> #include <errno.h> #ifndef FALSE #define FALSE (0!=0) #define TRUE (0==0) #endif static char *progname; static char *filename; static int debug = FALSE, verbose = FALSE; void DoOneFile(char *desc) { long long int block = 0LL; //FILE *videofile = fopen(filename, "rb"); //if (videofile == NULL) { // if (errno != EACCES) { // //fprintf(stderr, "%s: %s - %s\n", progname, filename, strerror(errno)); // } // return; // may be locked etc. Not fatal if cannot open every file... // // (also, directories give 'permission denied') //} if (strcasecmp(filename+strlen(filename)-strlen("video_ts.ifo"), "video_ts.ifo") == 0) { char *s, Movie[1024]; int l; l = strlen(filename); strcpy(Movie, filename); l = l - strlen("video_ts.ifo") - 1; Movie[l] = '\0'; // There are some tweaks here to try to guess which part of the path is // the actual movie name. It doesn't always get it right but hey, you have // the source code, make it do what you want... if (strcasecmp(Movie+l-strlen("video_ts"), "video_ts") == 0) { l = l - strlen("VIDEO_TS") - 1; Movie[l] = '\0'; } s = strrchr(Movie, '\\'); if (s == NULL) s = strrchr(Movie, '/'); if (s == NULL) s = strrchr(Movie, ':'); if (s == NULL) s = Movie; else { s += 1; if ((*s == 's' || *s == 'S') && (isdigit(s[1]))) { // ...\disk name\s1 d3\video_ts.ifo s = s - 1; *s = ' '; for (;;) { if (s == Movie) break; s = s - 1; if (*s == '/' || *s == '\\' || *s == ':') { s = s + 1; break; } } } } fprintf(stdout, "DVD,\"%s\",", s); fprintf(stdout, "\"%s\",", filename); fprintf(stdout, "\"%s\"", desc); fprintf(stdout, "\n"); } else if ( (strcasecmp(filename+strlen(filename)-strlen(".avi"), ".avi") == 0) || (strcasecmp(filename+strlen(filename)-strlen(".divx"), ".divx") == 0) || (strcasecmp(filename+strlen(filename)-strlen(".mpg"), ".mpg") == 0) || (strcasecmp(filename+strlen(filename)-strlen(".mkv"), ".mkv") == 0) || (strcasecmp(filename+strlen(filename)-strlen(".mp4"), ".mp4") == 0) ) { char *s, Movie[1024]; int l; l = strlen(filename); strcpy(Movie, filename); s = strrchr(Movie, '.'); fprintf(stdout, "%s,", s+1); *s = '\0'; s = strrchr(Movie, '\\'); if (s == NULL) s = strrchr(Movie, '/'); if (s == NULL) s = strrchr(Movie, ':'); if (s == NULL) s = Movie; else s += 1; fprintf(stdout, "\"%s\",", s); fprintf(stdout, "\"%s\",", filename); fprintf(stdout, "\"%s\"", desc); fprintf(stdout, "\n"); } //fclose(videofile); } #ifdef __unix__ #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> int ForAllFiles(int level, const char *dirname, char *desc) { DIR *dd; struct dirent *dent; struct stat statbuf; char *fname; int scanret = 0; if ((dd = opendir(dirname)) != NULL) { while ((dent = readdir(dd))) { #ifndef C_INTERIX if (dent->d_ino) #endif { if (strcmp(dent->d_name, ".") && strcmp(dent->d_name, "..")) { /* build the full name */ fname = calloc(strlen(dirname) + strlen(dent->d_name) + 2, sizeof(char)); sprintf(fname, "%s/%s", dirname, dent->d_name); /* stat the file */ if (lstat(fname, &statbuf) != -1) { if (S_ISDIR(statbuf.st_mode) && !S_ISLNK(statbuf.st_mode)) { ForAllFiles(level+1, fname, desc); } else { // PERFORM ACTION: if(S_ISREG(statbuf.st_mode)) { filename = fname; DoOneFile(desc); } } } free(fname); } } } } else { if (verbose) fprintf(stderr, "%s: Can't open directory %s\n", progname, dirname); return 53; } closedir(dd); return (scanret ? 1 : 0); } #else #ifdef OLDSTYLE /* DOS API. Fails on some machines - infinite loop */ /* Hacked from "dir.c" at http://www-informatik.hs-harz.de/NVMCK/Labor_sapi.doc ... */ /* non-standard libs for findfirst etc under LCC compiler on Windows. Possibly others? */ #include <io.h> #include <direct.h> int ForAllFiles(int level, char *param, char *desc) { static char current[256]; struct _finddata_t ft; long int lf; int bMore = FALSE; if (level == 0) { _getcwd(current, sizeof(current)-1); _chdir(param); // if dir as a param, do this } lf = _findfirst( "*.*", &ft); bMore = (lf != -1L); while (bMore) { if ( ft.name[0] != '.') { if (ft.attrib & _A_SUBDIR) { _chdir(ft.name); ForAllFiles(level+1, param, desc); _chdir(".."); } else { _getcwd(current, sizeof(current)-1); strcat(current, "\\"); strcat(current, ft.name); filename = current; // another nasty global DoOneFile(desc); // BUT WOULD PREFER NOT TO IF IT IS A DIRECTORY!!! // (relying on dir giving EACCES in WinXX) } } bMore = !_findnext(lf, &ft) ; } if (level == 0)_chdir(current); return 1; } #else /* WIN32 API */ #include <io.h> #ifdef TRUE #undef TRUE #undef FALSE #endif #ifdef N #undef N #endif #include <windows.h> /* Program to walk the directory tree (from a nominated starting point) and print the names of all files found (excepting directories) Author: Nick Gammon <nick@gammon.com.au> Hacked by: Graham Toal Web: http://www.gammon.com.au Date: 8th August 1999 Hacked on: 6th July 2006 Usage: treeinfo <starting_point> eg. treeinfo c:\games */ void PrintWin32Error (DWORD ErrorCode) { LPVOID lpMsgBuf; FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, ErrorCode, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL ); fprintf(stderr, "%s", lpMsgBuf ); LocalFree( lpMsgBuf ); } void process_dir (char * dirname, char * subdirname, int level, __int64 * totalsize, long * totalfiles, char *desc) { char * thisdir = NULL; WIN32_FIND_DATA fileinfo; HANDLE hdl; double mb; // we add 4 below for: // null at end: 1 // \ between dir and subdir name : 1 // \* at end: 2 thisdir = malloc (strlen (dirname) + strlen (subdirname) + 4); if (!thisdir) { fprintf(stderr, "*** Unable to allocate memory for directory name: %s\\%s\n", dirname, subdirname); return; } strcpy (thisdir, dirname); if (strlen (subdirname) > 0) { strcat (thisdir, "\\"); strcat (thisdir, subdirname); } strcat (thisdir, "\\*"); if (strlen (thisdir) > _MAX_PATH) { fprintf(stderr, "*** pathname: \"%s\" is too long to process\n", thisdir); free (thisdir); return; } hdl = FindFirstFile (thisdir, &fileinfo); thisdir [strlen (thisdir) - 2] = 0; // remove wildcard from end if (hdl == INVALID_HANDLE_VALUE) { DWORD nErr = GetLastError (); if (level != 0 && nErr == ERROR_NO_MORE_FILES) { free (thisdir); return; } //fprintf(stderr, "*** %s : ", thisdir); //PrintWin32Error (nErr); free (thisdir); return; } do { if (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { if (strcmp (".", fileinfo.cFileName) != 0 && strcmp ("..", fileinfo.cFileName) != 0) { __int64 total = 0; long files = 0; process_dir (thisdir, fileinfo.cFileName, level + 1, &total, &files, desc); *totalsize += total; *totalfiles += files; } } else { LARGE_INTEGER nSize; char pathname[1024*10]; // beware of buffer overflow. Need to fix this. nSize.HighPart = fileinfo.nFileSizeHigh; nSize.LowPart = fileinfo.nFileSizeLow; *totalsize += nSize.QuadPart; (*totalfiles)++; // if (level == 0) ... mb = (double) nSize.QuadPart / 1024.0 / 1024.0; // fprintf(stdout, "%s\\%s \n", /*mb,*/ thisdir, fileinfo.cFileName); sprintf(pathname, "%s\\%s", thisdir, fileinfo.cFileName); filename = pathname; DoOneFile(desc); } if (FindNextFile (hdl, &fileinfo) == 0) { DWORD nErr = GetLastError (); if (nErr != ERROR_NO_MORE_FILES) { fprintf(stderr, "*** %s : ", thisdir); PrintWin32Error (nErr); } FindClose (hdl); // if level == 1 ... mb = (double) *totalsize / 1024.0 / 1024.0; free (thisdir); return; } } while (1); }; int ForAllFiles(int level, char *param, char *desc) { __int64 total = 0; long files = 0; char starting_point [_MAX_PATH] = "c:"; if ((param != NULL) && (*param != '\0')) strcpy (starting_point, param); // remove trailing backslash from starting point if (starting_point [strlen (starting_point) - 1] == '\\') starting_point [strlen (starting_point) - 1] = 0; process_dir (starting_point, "", 0, &total, &files, desc); return 0; } #endif /* OLDSTYLE */ #endif /* not __unix__ , i.e. Windows */ int main (int argc, char **argv) { int c; char *s, *desc; progname = (((s=strrchr(argv[0], '/')) != NULL ? s+1: (s=strrchr(argv[0], '\\')) != NULL ? s+1: argv[0])); if ((argc == 3) && (strcmp(argv[1], "-v") == 0)) { verbose = TRUE; argc -= 1; argv += 1; } if (argc == 2) { desc = ""; } else if (argc == 3) { desc = argv[2]; } else { fprintf(stderr, "syntax: %s [-v] rootdir [\"diskname or description\"]\n", progname); exit(EXIT_FAILURE); } /****************** LOOP OVER ALL FILES ****************/ (void)ForAllFiles(0, argv[1], desc); /************ END OF LOOP OVER ALL FILES ************/ exit(EXIT_SUCCESS); if (version != NULL) return(EXIT_FAILURE); }