summaryrefslogtreecommitdiffabout
authorMichael Krelin <hacker@klever.net>2010-03-02 22:13:23 (UTC)
committer Michael Krelin <hacker@klever.net>2010-03-08 00:14:30 (UTC)
commit02ba82674ce4c5983130a2501169e013b70a95fa (patch) (side-by-side diff)
tree381eead49cedf960378a7d418fe7ac957d704afb
parentce549cc7cc156ff1d6e15bcceb6c6178154af459 (diff)
downloadiii-02ba82674ce4c5983130a2501169e013b70a95fa.zip
iii-02ba82674ce4c5983130a2501169e013b70a95fa.tar.gz
iii-02ba82674ce4c5983130a2501169e013b70a95fa.tar.bz2
introducing iii-extract-riff-chunk utility
for getting the timestamp out of Nikon AVIs Signed-off-by: Michael Krelin <hacker@klever.net>
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--src/.gitignore1
-rw-r--r--src/Makefile.am4
-rw-r--r--src/iii-extract-riff-chunk.cc246
3 files changed, 251 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
index 425033a..f1c5466 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -8,3 +8,4 @@ soapeyefiService.h
*.o
/iiid
/COPYING.cc
+/iii-extract-riff-chunk
diff --git a/src/Makefile.am b/src/Makefile.am
index b5b7d5c..b31bed5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,3 +1,4 @@
+bin_PROGRAMS=iii-extract-riff-chunk
sbin_PROGRAMS=iiid
noinst_HEADERS = \
eyefi.h \
@@ -17,6 +18,9 @@ nodist_iiid_SOURCES = \
COPYING.cc
iiid_LDADD = ${MODULES_LIBS} ${UUID_LIBS}
+iii_extract_riff_chunk_SOURCES = iii-extract-riff-chunk.cc
+nodist_iii_extract_riff_chunk_SOURCES = COPYING.cc
+
COPYING.cc: ${top_srcdir}/COPYING
echo "const char * COPYING = " >$@ || (rm $@;exit 1)
sed -e 's/"/\\"/g' -e 's/^/\"/' -e 's/$$/\\n\"/' $< >>$@ || (rm $@;exit 1)
diff --git a/src/iii-extract-riff-chunk.cc b/src/iii-extract-riff-chunk.cc
new file mode 100644
index 0000000..3a06db2
--- a/dev/null
+++ b/src/iii-extract-riff-chunk.cc
@@ -0,0 +1,246 @@
+/* vim:set sw=8 nosi noai cin cino=:0,l1,g0: */
+#include <stdint.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <cstring>
+#include <iostream>
+#include <fstream>
+#include <stdexcept>
+#include <cassert>
+#include <list>
+#include <string>
+#include <iterator>
+
+#include "config.h"
+
+#define PHEADER \
+ PACKAGE " Version " VERSION "\n" \
+ "Copyright (c) 2009-2010 Klever Group"
+
+typedef uint32_t fourcc_type;
+enum fourcc_value {
+ fourcc_RIFF = 0x46464952, fourcc_AVI = 0x20495641, fourcc_LIST = 0x5453494c,
+ fourcc_hdrl = 0x6c726468, fourcc_strl = 0x6c727473,
+ fourcc_ncdt = 0x7464636e, fourcc_ncvr = 0x7276636e, fourcc_nctg = 0x6774636e, fourcc_ncth = 0x6874636e
+};
+
+fourcc_type str2fourcc(const char *str) {
+ fourcc_type rv = 0;
+ return *(fourcc_type*)strncpy((char*)&rv,str,sizeof(rv));
+}
+const std::string fourcc2str(fourcc_type fcc) {
+ char rv[sizeof(fcc)+1];
+ *(fourcc_type*)rv = fcc;
+ rv[sizeof(fcc)]=0;
+ return rv;
+}
+
+#pragma pack(1)
+struct riff_sized_head {
+ fourcc_type fourcc;
+ uint32_t size;
+};
+#pragma pack()
+
+class exceptional_success : public std::exception { };
+
+struct riff {
+ struct chunk_type {
+ riff_sized_head head;
+ uint32_t left;
+
+ chunk_type(riff& r) {
+ r.read(&head);
+ left = head.size;
+ }
+ };
+
+ std::istream *in;
+ typedef std::list<chunk_type> chunks_type;
+ chunks_type chunks;
+
+ riff(std::istream& i) : in(&i) { }
+
+ protected:
+
+ int begin_chunk() {
+ chunks.push_back( chunk_type(*this) );
+ return chunks.size();
+ }
+ void end_chunk(int level) {
+ assert(in); assert(chunks.size()==level);
+ std::streamsize o = chunks.back().left;
+ chunks.pop_back();
+ if(!o) return;
+ in->seekg(o,std::ios::cur);
+ for(chunks_type::iterator i=chunks.begin(),ie=chunks.end() ;i!=ie; (i++)->left -= o) ;
+ }
+
+ void read(void *p,size_t n) {
+ assert(in);
+ if( (!chunks.empty()) && chunks.back().left < n)
+ throw std::runtime_error("attempt to read beyond the end of chunk");
+ if(!in->read((char*)p,n))
+ throw std::runtime_error("failed to read()");
+ std::streamsize gc = in->gcount();
+ for(chunks_type::iterator i=chunks.begin(),ie=chunks.end();i!=ie;++i) {
+ i->left -= gc; assert(i->left >= 0);
+ }
+ if(gc!=n) throw std::runtime_error("incomplete read()");
+ }
+
+ template<typename T> void read(T* v) { read(v,sizeof(*v)); }
+
+ friend class scoped_chunk;
+
+};
+
+struct scoped_chunk {
+ riff& rs;
+ int level;
+ riff::chunks_type::reverse_iterator chunk_iterator;
+
+ scoped_chunk(riff& rs_) : rs(rs_), level(rs.begin_chunk()), chunk_iterator(rs.chunks.rbegin()) { }
+ ~scoped_chunk() { rs.end_chunk(level); }
+
+ riff::chunk_type& chunk() { return *chunk_iterator; }
+
+ fourcc_type get_chunk_id() { return chunk().head.fourcc; }
+ bool has(uint32_t bytes=1) { return chunk().left >= bytes; }
+
+ template<typename T> T read() { T rv; rs.read(&rv); return rv; }
+ template<typename T> void read(T& t) { rs.read(&t); }
+ void read(void *p,size_t n) { rs.read(p,n); }
+
+};
+
+struct chunk_path_type : public std::list<std::string> {
+
+ chunk_path_type() { }
+ chunk_path_type(const char *str) {
+ for(const char *p0=str,*p1=strchr(p0,'/');p0;p1=p1?strchr(p0=++p1,'/'):(p0=0))
+ if(p0!=p1)
+ push_back( p1? std::string(p0,p1-p0) : std::string(p0) );
+ }
+
+};
+
+struct chunk_walker {
+ riff& r;
+ chunk_path_type path;
+
+ chunk_walker(riff& r_) : r(r_) { }
+
+ void shoot(scoped_chunk& chunk) {
+ riff::chunk_type& c = chunk.chunk();
+ while(c.left) {
+ static char tmp[512];
+ int r = (c.left<sizeof(tmp)) ? c.left : sizeof(tmp) ;
+ chunk.read(tmp,r); std::cout.write(tmp,r);
+ }
+ if(first) throw exceptional_success();
+ }
+
+ void walk_chunks() {
+ scoped_chunk chunk(r);
+ std::string pc = fourcc2str(chunk.get_chunk_id());
+ bool list = false;
+ switch(chunk.get_chunk_id()) {
+ case fourcc_RIFF: case fourcc_LIST:
+ list = true;
+ pc += '.'; pc += fourcc2str(chunk.read<fourcc_type>());
+ break;
+ }
+ path.push_back(pc);
+ std::pair<chunk_path_type::const_iterator,chunk_path_type::const_iterator>
+ mm = std::mismatch(path.begin(),path.end(),target.begin());
+ bool dive = list;
+ if(mm.first==path.end()) {
+ if(mm.second==target.end()) {
+ shoot(chunk);
+ dive = false;
+ }
+ }else{
+ assert(mm.second!=target.end());
+ dive = false;
+ }
+
+ if(dive) while(chunk.has()) walk_chunks();
+ path.pop_back();
+ }
+
+
+ chunk_path_type target;
+ chunk_walker& set_target(chunk_path_type t) { target = t; return *this; }
+ bool first;
+ chunk_walker& set_oneshot(bool os) { first = true; return *this; }
+};
+
+void usage(char **argv) {
+ std::cerr << PHEADER << std::endl << std::endl
+ << ' ' << argv[0] << " [options] <avi-file> <chunk-path>" << std::endl
+ << std::endl <<
+ " -h, --help,\n"
+ " --usage display this text\n"
+ " -V, --version display version information\n"
+ " -L, --license show license\n"
+ "\n"
+ "Example: " << argv[0] << " DSC_0001.AVI '/RIFF.AVI /LIST.ncdt/nctg' \\\n"
+ "\t\t| dd bs=1 skip=82 count=19 2>/dev/null\n"
+ "Output: YYYY:MM:DD HH:MM:SS (for Nikon-recorded AVI)"
+ << std::endl << std::endl;
+ exit(0);
+}
+
+int main(int argc,char **argv) try {
+
+ bool first = false;
+
+ for(;;) {
+ static struct option opts[] = {
+ { "first", no_argument, 0, '1' },
+ { "help", no_argument, 0, 'h' },
+ { "usage", no_argument, 0, 'h' },
+ { "version", no_argument, 0, 'V' },
+ { "license", no_argument, 0, 'L' },
+ { NULL, 0, 0, 0 }
+ };
+ int c = getopt_long(argc,argv,"1hVL",opts,NULL);
+ if(c==-1) break;
+ switch(c) {
+ case 'h':
+ usage(argv); break;
+ case 'V':
+ std::cerr << VERSION << std::endl;
+ exit(0); break;
+ case 'L':
+ extern const char *COPYING;
+ std::cerr << COPYING << std::endl;
+ exit(0); break;
+ case '1':
+ first = true; break;
+ default:
+ std::cerr << "Huh?" << std::endl;
+ exit(1); break;
+ }
+ }
+
+ if(optind!=(argc-2)) usage(argv);
+
+ std::ifstream i(argv[optind],std::ios::in);
+ if(!i) {
+ std::cerr << "Failed to open file '" << argv[optind] << "'" << std::endl;
+ exit(1);
+ }
+ riff rs(i);
+ chunk_walker(rs)
+ .set_target(chunk_path_type(argv[++optind]))
+ .set_oneshot(first)
+ .walk_chunks();
+ return 0;
+}catch(exceptional_success&) {
+ return 0;
+}catch(std::exception &e) {
+ std::cerr << "oops: " << e.what() << std::endl;
+ return 1;
+}