/* * $Id$ * * dv2sub.c -- utility for extracting information from raw DV file * Copyright (C) 2006 Vaclav Ovsik * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * 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; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #define MMAP_BLOCK_LEN (16 * 1024 * 1024) static int verbose_flag = 0; static int mm_flag = 1; #ifndef MIN #define MIN(a, b) ( (a) < (b) ? (a) : (b) ) #endif #ifndef MAX #define MAX(a, b) ( (a) > (b) ? (a) : (b) ) #endif #define FRAME_SIZE_PAL 144000 #define FRAME_SIZE_NTSC 120000 #define FRAME_SIZE_MIN MIN(FRAME_SIZE_PAL, FRAME_SIZE_NTSC) #define FRAME_SIZE_MAX MAX(FRAME_SIZE_PAL, FRAME_SIZE_NTSC) typedef int (* callback_t)(int, dv_decoder_t *, unsigned char *, void *); typedef struct cbent_st { callback_t cb; void *data; } cbent_t; static int dv_stream(int fd, int pass_through_flag, cbent_t cbent[]) { dv_decoder_t *dec; if ( (dec = dv_decoder_new(0, 0, 0)) == NULL ) { fprintf(stderr, "dv_decoder_new() returns NULL\n"); goto err_decoder_new; } unsigned char *frame; if ( (frame = (unsigned char *)malloc(FRAME_SIZE_MAX)) == NULL ) { fprintf(stderr, "malloc() failed: %s\n", strerror(errno)); goto err_malloc_frame; } int frameno = 0; size_t rest = 0; while ( 1 ) { size_t len = rest; ssize_t n; do { n = read(fd, frame + len, FRAME_SIZE_MAX - len); if ( n == (ssize_t)-1 ) { fprintf(stderr, "read() failed: %s\n", strerror(errno)); goto err_loop; } len += n; } while ( n ); if ( len == 0 ) break; if ( len < FRAME_SIZE_MIN ) { fprintf(stderr, "remains only %u bytes, premature EOF?\n", len); goto err_loop; } if ( dv_parse_header(dec, frame) < 0 ) { fprintf(stderr, "dv_parse_header() failed\n" "Input stream is corrupted or isn't raw DV stream!\n"); goto err_loop; } dv_parse_packs(dec, frame); for(cbent_t *ce = cbent; ce->cb != NULL; ce++) { if ( (*(ce->cb))(frameno, dec, frame, ce->data) < 0 ) goto err_loop; } size_t frame_size = dv_is_PAL(dec) ? FRAME_SIZE_PAL : FRAME_SIZE_NTSC; if ( pass_through_flag ) { size_t wlen = 0; do { ssize_t n = write(1, frame + wlen, frame_size - wlen); if ( n == (ssize_t)-1 ) { fprintf(stderr, "write() failed: %s\n", strerror(errno)); goto err_loop; } wlen += n; } while ( frame_size != wlen ); } rest = FRAME_SIZE_MAX - frame_size; memmove(frame, frame + frame_size, rest); frameno++; } for(cbent_t *ce = cbent; ce->cb != NULL; ce++) { if ( (*(ce->cb))(-1, NULL, NULL, ce->data) < 0 ) goto err_loop; } free(frame); dv_decoder_free(dec); return 0; err_loop: free(frame); err_malloc_frame: dv_decoder_free(dec); err_decoder_new: return -1; } #if HAVE_MMAP static int dv_stream_mm(int fd, cbent_t cbent[]) { dv_decoder_t *dec; if ( (dec = dv_decoder_new(0, 0, 0)) == NULL ) { fprintf(stderr, "dv_decoder_new() returns NULL\n"); goto err_decoder_new; } size_t page_size = (size_t)sysconf(_SC_PAGESIZE); if ( page_size == (size_t)-1 ) { fprintf(stderr, "sysconf(_SC_PAGESIZE) returns -1\n"); goto err_sysconf; } off_t flen = lseek(fd, 0, SEEK_END); if ( flen == (off_t)-1 ) { fprintf(stderr, "lseek() failed: %s\n", strerror(errno)); goto err_lseek; } unsigned char *mmdata = NULL; off_t offset = 0; int frameno = 0; off_t mmoff = 0; int mmlen = 0; while ( offset < flen ) { if ( flen - offset < FRAME_SIZE_MIN ) { fprintf(stderr, "remains only %d bytes, premature EOF?\n", (int)(flen - offset)); goto err_loop; } if ( offset + FRAME_SIZE_MAX > mmoff + mmlen ) { if ( mmdata ) { if ( munmap(mmdata, mmlen) ) { fprintf(stderr, "munmap() failed: %s\n", strerror(errno)); goto err_unmap; } } mmoff = offset - offset % page_size; mmlen = MIN(MMAP_BLOCK_LEN, flen - offset + page_size); if ( (mmdata = (unsigned char *)mmap(NULL, mmlen, PROT_READ, MAP_SHARED, fd, mmoff)) == NULL ) { fprintf(stderr, "mmap() failed: %s\n", strerror(errno)); goto err_mmap; } } unsigned char *frame = mmdata + (int)(offset - mmoff); if ( dv_parse_header(dec, frame) < 0 ) { fprintf(stderr, "dv_parse_header() failed\n" "Input stream is corrupted or isn't raw DV stream!\n"); goto err_loop; } dv_parse_packs(dec, frame); for(cbent_t *ce = cbent; ce->cb != NULL; ce++) { if ( (*(ce->cb))(frameno, dec, frame, ce->data) < 0 ) goto err_loop; } offset += dv_is_PAL(dec) ? FRAME_SIZE_PAL : FRAME_SIZE_NTSC; frameno++; } for(cbent_t *ce = cbent; ce->cb != NULL; ce++) { if ( (*(ce->cb))(-1, NULL, NULL, ce->data) < 0 ) goto err_loop; } if ( mmdata ) if ( munmap(mmdata, mmlen) ) { fprintf(stderr, "munmap() failed: %s\n", strerror(errno)); goto err_unmap; } dv_decoder_free(dec); return 0; err_loop: if ( mmdata ) munmap(mmdata, mmlen); err_mmap: err_unmap: err_lseek: err_sysconf: dv_decoder_free(dec); err_decoder_new: return -1; } #endif static int dv_gensub(int frameno, dv_decoder_t *dec, unsigned char *frame, void *data) { static char last_dtmstr[32]; static int last_dtmstr_frameno; static int last_frameno; if ( frameno < 0 ) { char *s = strchr(last_dtmstr, ' '); if ( s ) *s = '|'; fprintf((FILE *)data, "{%d}{%d}%s\r\n", last_dtmstr_frameno, last_frameno, last_dtmstr); return 0; } char dtmstr[32]; if ( dv_get_recording_datetime(dec, dtmstr) == 0 ) dtmstr[0] = 0; if ( frameno ) { if ( strcmp(last_dtmstr, dtmstr) ) { char *s = strchr(last_dtmstr, ' '); if ( s ) *s = '|'; fprintf((FILE *)data, "{%d}{%d}%s\r\n", last_dtmstr_frameno, last_frameno, last_dtmstr); strcpy(last_dtmstr, dtmstr); last_dtmstr_frameno = frameno; } } else { strcpy(last_dtmstr, dtmstr); last_dtmstr_frameno = frameno; } last_frameno = frameno; return 0; } static int dv_info(int frameno, dv_decoder_t *dec, unsigned char *frame, void *data) { if ( frameno < 0 ) return 0; char tstpstr[32], dtmstr[32]; char const *v_sysstr = dv_is_PAL(dec) ? "pal" : "ntsc"; char const *v_fmtstr = dv_format_normal(dec) > 0 ? "normal" : dv_format_wide(dec) > 0 ? "wide" : dv_format_letterbox(dec) > 0 ? "letterbox" : "unknown"; int is_prog = dv_is_progressive(dec); char const *v_progr = is_prog > 0 ? " progressive" : is_prog == 0 ? " interlaced" : ""; int a_sampl = dv_get_num_samples(dec); int a_ch = dv_get_num_channels(dec); int a_freq = dv_get_frequency(dec); dv_get_timestamp(dec, tstpstr); dv_get_recording_datetime(dec, dtmstr); fprintf((FILE *)data, "Frame # %d:\n" "\tvideo: %s %s%s\n" "\taudio: %d channels, %d Hz, %d samples\n" "\ttimestamp: %s recording date & time: %s\n", frameno, v_sysstr, v_fmtstr, v_progr, a_ch, a_freq, a_sampl, tstpstr, dtmstr); return 0; } static int dv_stream_wrap(int fd, const char *fn, int pass_through_flag, cbent_t cbent[]) { if ( verbose_flag ) { if ( fd ) fprintf(stderr, "processing file `%s'...\n", fn); else fprintf(stderr, "processing stdin...\n"); } struct stat stat_buf; if ( fstat(fd, &stat_buf) ) { if ( fd ) fprintf(stderr, "fstat(%d) failed (file `%s'): %s\n", fd, fn, strerror(errno)); else fprintf(stderr, "fstat(0) failed: %s\n", strerror(errno)); return -1; } #if HAVE_MMAP if ( S_ISREG(stat_buf.st_mode) && mm_flag && !pass_through_flag ) return dv_stream_mm(fd, cbent); else #endif return dv_stream(fd, pass_through_flag, cbent); } static char const *progname; static void usage( void ) { puts("Usage: dv2sub [options] []\n" "\n" "Options:\n" " -i, --info generate textual information about frames\n" " --info-out like -i, but write to file , instead stdout\n" " -s, --subtitles generate MicroDVD subtitles with rec. date & time\n" " --subtitles-out like -s, but write to file , instead stdout\n" " --no-mmap don't use memory mapped input\n" " -p, --pass-through pass input DV stream to stdout (implies --no-mmap)\n" " -v, --verbose be verbose\n" " -h, --help this short usage listing\n" " -V, --version utility version info\n" "\n" "Report bugs to " PACKAGE_BUGREPORT ); exit(1); } int main(int argc, char *argv[], char *env[]) { progname = argv[0]; int info_flag = 0; int subtitles_flag = 0; int pass_through_flag = 0; char const *subtitles_filename = NULL; char const *info_filename = NULL; FILE *info_file = NULL; FILE *subtitles_file = NULL; if ( argc == 1 ) usage(); while ( 1 ) { static struct option long_options[] = { {"info", no_argument, NULL, 'i'}, {"subtitles", required_argument, NULL, 's'}, {"no-mmap", no_argument, &mm_flag, 0}, {"info-out", required_argument, NULL, 0}, {"subtitles-out", required_argument, NULL, 0}, {"pass-through", no_argument, NULL, 'p'}, {"verbose", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, {"help", no_argument, NULL, 'h'}, {0, 0, 0, 0} }; /* `getopt_long' stores the option index here. */ int option_index = 0; int c = getopt_long(argc, argv, "ispvVh", long_options, &option_index); /* Detect the end of the options. */ if ( c == -1 ) break; switch (c) { case 0: /* If this option set a flag, do nothing else now. */ if ( long_options[option_index].flag != 0 ) break; char const *optnam = long_options[option_index].name; if ( strcmp(optnam, "info-out") == 0 ) { info_filename = optarg; break; } if ( strcmp(optnam, "subtitles-out") == 0 ) { subtitles_filename = optarg; break; } break; case 'i': info_flag = 1; break; case 's': subtitles_flag = 1; break; case 'p': pass_through_flag = 1; break; case 'v': verbose_flag = 1; break; case 'V': printf("%s %s\n", progname, PACKAGE_VERSION); exit(0); case 'h': usage(); case '?': /* `getopt_long' already printed an error message. */ usage(); default: abort(); } } if ( pass_through_flag + (info_flag && !info_filename) + (subtitles_flag && !subtitles_filename) > 1 ) { fprintf(stderr, "Conflict options for stdout." " You make a mistake probably.\n"); exit(1); } int fail = 0; cbent_t ce[8]; memset(ce, 0, sizeof(ce)); int i_ce = 0; if ( info_filename ) { if ( strcmp(info_filename, "-") == 0 ) info_file = stdout; else if ( (info_file = fopen(info_filename, "w")) == NULL ) { fprintf(stderr, "fopen(\"%s\"...) failed: %s\n", info_filename, strerror(errno)); exit(1); } info_flag = 1; } else info_file = stdout; if ( info_flag ) { ce[i_ce].cb = &dv_info; ce[i_ce].data = info_file; i_ce++; } if ( subtitles_filename ) { if ( strcmp(subtitles_filename, "-") == 0 ) subtitles_file = stdout; else if ( (subtitles_file = fopen(subtitles_filename, "wb")) == NULL ) { fprintf(stderr, "fopen(\"%s\"...) failed: %s\n", subtitles_filename, strerror(errno)); exit(1); } subtitles_flag = 1; } else subtitles_file = stdout; if ( subtitles_flag ) { ce[i_ce].cb = &dv_gensub; ce[i_ce].data = subtitles_file; i_ce++; } if ( i_ce ) { if ( optind == argc ) { fail |= dv_stream_wrap(0, NULL, pass_through_flag, ce); } else { for(int i = optind; i < argc; i++) { int fd; if ( (fd = open(argv[i], O_RDONLY)) < 0 ) { fprintf(stderr, "open(\"%s\"...) failed: %s\n", argv[i], strerror(errno)); continue; } fail |= dv_stream_wrap(fd, argv[i], pass_through_flag, ce); close(fd); } } } if ( info_filename ) { if ( strcmp(info_filename, "-") != 0 ) { if ( fclose(info_file) ) { fprintf(stderr, "fclose() (file `%s') failed: %s\n", info_filename, strerror(errno)); } else info_file = NULL; } } if ( subtitles_filename ) { if ( strcmp(subtitles_filename, "-") != 0 ) { if ( fclose(subtitles_file) ) { fprintf(stderr, "fclose() (file `%s') failed: %s\n", subtitles_filename, strerror(errno)); } else subtitles_file = NULL; } } if ( verbose_flag ) fprintf(stderr, "all done\n"); return fail ? 1 : 0; }