+
+/*
+ * 1) if the format context has a timecode
+ * return fmt_ctx->timecode - 0
+ * 2) if the layer/channel has a timecode
+ * return st->timecode - (start_time-nudge)
+ * 3) find the 1st program with stream, find 1st program video stream,
+ * if video stream has a timecode, return st->timecode - (start_time-nudge)
+ * 4) find timecode in any stream, return st->timecode
+ * 5) read 100 packets, save ofs=pkt.pts*st->time_base - st->nudge:
+ * decode frame for video stream of 1st program
+ * if frame->timecode has a timecode, return frame->timecode - ofs
+ * if side_data has gop timecode, return gop->timecode - ofs
+ * if side_data has smpte timecode, return smpte->timecode - ofs
+ * 6) if the filename/url scans *date_time.ext, return date_time
+ * 7) if stat works on the filename/url, return mtime
+ * 8) return -1 failure
+*/
+double FFMPEG::get_initial_timecode(int data_type, int channel, double frame_rate)
+{
+ AVRational rate = check_frame_rate(0, frame_rate);
+ if( !rate.num ) return -1;
+// format context timecode
+ AVDictionaryEntry *tc = av_dict_get(fmt_ctx->metadata, "timecode", 0, 0);
+ if( tc ) return ff_get_timecode(tc->value, rate, 0);
+// stream timecode
+ if( open_decoder() ) return -1;
+ AVStream *st = 0;
+ int64_t nudge = 0;
+ int codec_type = -1, fidx = -1;
+ switch( data_type ) {
+ case TRACK_AUDIO: {
+ codec_type = AVMEDIA_TYPE_AUDIO;
+ int aidx = astrm_index[channel].st_idx;
+ FFAudioStream *aud = ffaudio[aidx];
+ fidx = aud->fidx;
+ nudge = aud->nudge;
+ st = aud->st;
+ AVDictionaryEntry *tref = av_dict_get(fmt_ctx->metadata, "time_reference", 0, 0);
+ if( tref && aud && aud->sample_rate )
+ return strtod(tref->value, 0) / aud->sample_rate;
+ break; }
+ case TRACK_VIDEO: {
+ codec_type = AVMEDIA_TYPE_VIDEO;
+ int vidx = vstrm_index[channel].st_idx;
+ FFVideoStream *vid = ffvideo[vidx];
+ fidx = vid->fidx;
+ nudge = vid->nudge;
+ st = vid->st;
+ break; }
+ }
+ if( codec_type < 0 ) return -1;
+ if( st )
+ tc = av_dict_get(st->metadata, "timecode", 0, 0);
+ if( !tc ) {
+ st = 0;
+// find first program which references this stream
+ int pidx = -1;
+ for( int i=0, m=fmt_ctx->nb_programs; pidx<0 && i<m; ++i ) {
+ AVProgram *pgrm = fmt_ctx->programs[i];
+ for( int j=0, n=pgrm->nb_stream_indexes; j<n; ++j ) {
+ int st_idx = pgrm->stream_index[j];
+ if( st_idx == fidx ) { pidx = i; break; }
+ }
+ }
+ fidx = -1;
+ if( pidx >= 0 ) {
+ AVProgram *pgrm = fmt_ctx->programs[pidx];
+ for( int j=0, n=pgrm->nb_stream_indexes; j<n; ++j ) {
+ int st_idx = pgrm->stream_index[j];
+ AVStream *tst = fmt_ctx->streams[st_idx];
+ if( !tst ) continue;
+ if( tst->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ) {
+ st = tst; fidx = st_idx;
+ break;
+ }
+ }
+ }
+ else {
+ for( int i=0, n=fmt_ctx->nb_streams; i<n; ++i ) {
+ AVStream *tst = fmt_ctx->streams[i];
+ if( !tst ) continue;
+ if( tst->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ) {
+ st = tst; fidx = i;
+ break;
+ }
+ }
+ }
+ if( st )
+ tc = av_dict_get(st->metadata, "timecode", 0, 0);
+ }
+
+ if( !tc ) {
+ // any timecode, includes -data- streams
+ for( int i=0, n=fmt_ctx->nb_streams; i<n; ++i ) {
+ AVStream *tst = fmt_ctx->streams[i];
+ if( !tst ) continue;
+ if( (tc = av_dict_get(tst->metadata, "timecode", 0, 0)) ) {
+ st = tst; fidx = i;
+ break;
+ }
+ }
+ }
+
+ if( st && st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ) {
+ if( st->r_frame_rate.num && st->r_frame_rate.den )
+ rate = st->r_frame_rate;
+ nudge = st->start_time;
+ for( int i=0; i<ffvideo.size(); ++i ) {
+ if( ffvideo[i]->st == st ) {
+ nudge = ffvideo[i]->nudge;
+ break;
+ }
+ }
+ }
+
+ if( tc ) { // return timecode
+ double secs = st->start_time == AV_NOPTS_VALUE ? 0 :
+ to_secs(st->start_time - nudge, st->time_base);
+ return ff_get_timecode(tc->value, rate, secs);
+ }
+
+ if( !st || fidx < 0 ) return -1;
+
+ decode_activate();
+ AVCodecContext *av_ctx = activate_decoder(st);
+ if( !av_ctx ) {
+ fprintf(stderr,"activate_decoder failed\n");
+ return -1;
+ }
+ avCodecContext avctx(av_ctx); // auto deletes
+ if( avctx->codec_type == AVMEDIA_TYPE_VIDEO &&
+ avctx->framerate.num && avctx->framerate.den )
+ rate = avctx->framerate;
+
+ avPacket pkt; // auto deletes
+ avFrame frame; // auto deletes
+ if( !frame ) {
+ fprintf(stderr,"av_frame_alloc failed\n");
+ return -1;
+ }
+ int errs = 0;
+ int64_t max_packets = 100;
+ char tcbuf[AV_TIMECODE_STR_SIZE];
+
+ for( int64_t count=0; count<max_packets; ++count ) {
+ av_packet_unref(pkt);
+ pkt->data = 0; pkt->size = 0;
+
+ int ret = av_read_frame(fmt_ctx, pkt);
+ if( ret < 0 ) {
+ if( ret == AVERROR_EOF ) break;
+ if( ++errs > 100 ) {
+ fprintf(stderr,"over 100 read_frame errs\n");
+ break;
+ }
+ continue;
+ }
+ if( !pkt->data ) continue;
+ int i = pkt->stream_index;
+ if( i != fidx ) continue;
+ int64_t tstmp = pkt->pts;
+ if( tstmp == AV_NOPTS_VALUE ) tstmp = pkt->dts;
+ double secs = to_secs(tstmp - nudge, st->time_base);
+ ret = avcodec_send_packet(avctx, pkt);
+ if( ret < 0 ) return -1;
+
+ while( (ret = avcodec_receive_frame(avctx, frame)) >= 0 ) {
+ if( (tc = av_dict_get(frame->metadata, "timecode", 0, 0)) )
+ return ff_get_timecode(tc->value, rate, secs);
+ int k = frame->nb_side_data;
+ AVFrameSideData *side_data = 0;
+ while( --k >= 0 ) {
+ side_data = frame->side_data[k];
+ switch( side_data->type ) {
+ case AV_FRAME_DATA_GOP_TIMECODE: {
+ int64_t data = *(int64_t *)side_data->data;
+ int sz = sizeof(data);
+ if( side_data->size >= sz ) {
+ av_timecode_make_mpeg_tc_string(tcbuf, data);
+ return ff_get_timecode(tcbuf, rate, secs);
+ }
+ break; }
+ case AV_FRAME_DATA_S12M_TIMECODE: {
+ uint32_t *data = (uint32_t *)side_data->data;
+ int n = data[0], sz = (n+1)*sizeof(*data);
+ if( side_data->size >= sz ) {
+ av_timecode_make_smpte_tc_string(tcbuf, data[n], 0);
+ return ff_get_timecode(tcbuf, rate, secs);
+ }
+ break; }
+ default:
+ break;
+ }
+ }
+ }
+ }
+ char *path = fmt_ctx->url;
+ char *bp = strrchr(path, '/');
+ if( !bp ) bp = path; else ++bp;
+ char *cp = strrchr(bp, '.');
+ if( cp && (cp-=(8+1+6)) >= bp ) {
+ char sep[BCSTRLEN];
+ int year,mon,day, hour,min,sec, frm=0;
+ if( sscanf(cp,"%4d%2d%2d%[_-]%2d%2d%2d",
+ &year,&mon,&day, sep, &hour,&min,&sec) == 7 ) {
+ int ch = sep[0];
+ // year>=1970,mon=1..12,day=1..31, hour=0..23,min=0..59,sec=0..60
+ if( (ch=='_' || ch=='-' ) &&
+ year >= 1970 && mon>=1 && mon<=12 && day>=1 && day<=31 &&
+ hour>=0 && hour<24 && min>=0 && min<60 && sec>=0 && sec<=60 ) {
+ sprintf(tcbuf,"%d:%02d:%02d:%02d", hour,min,sec, frm);
+ return ff_get_timecode(tcbuf, rate, 0);
+ }
+ }
+ }
+ struct stat tst;
+ if( stat(path, &tst) >= 0 ) {
+ time_t t = (time_t)tst.st_mtim.tv_sec;
+ struct tm tm;
+ localtime_r(&t, &tm);
+ int64_t us = tst.st_mtim.tv_nsec / 1000;
+ int frm = us/1000000. * frame_rate;
+ sprintf(tcbuf,"%d:%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec, frm);
+ return ff_get_timecode(tcbuf, rate, 0);
+ }
+ return -1;
+}
+
+double FFMPEG::ff_get_timecode(char *str, AVRational rate, double pos)
+{
+ AVTimecode tc;
+ if( av_timecode_init_from_string(&tc, rate, str, fmt_ctx) )
+ return -1;
+ double secs = (double)tc.start / tc.fps - pos;
+ if( secs < 0 ) secs = 0;
+ return secs;
+}
+
+double FFMPEG::get_timecode(const char *path, int data_type, int channel, double rate)
+{
+ FFMPEG ffmpeg(0);
+ if( ffmpeg.init_decoder(path) ) return -1;
+ return ffmpeg.get_initial_timecode(data_type, channel, rate);
+}
+