Пример #1
0
/* fas_seek_to_frame */
fas_error_type fas_seek_to_frame (fas_context_ref_type context, int target_index)
{

  fas_error_type fas_error;

  if ((NULL == context) || (FAS_FALSE == context->is_video_active))
    return private_show_error("invalid or unopened context", FAS_INVALID_ARGUMENT);

  //  printf("seeking to %d (from %d)!\n", target_index, context->current_frame_index);
  if (target_index == context->current_frame_index)
    return FAS_SUCCESS;

  fas_error = fas_seek_to_nearest_key (context, target_index);

  if (fas_error != FAS_SUCCESS)
    return private_show_error("error advancing to key frame before seek", fas_error);

  if (fas_get_frame_index(context) > target_index)
    return private_show_error("error advancing to key frame before seek (index isn't right)", fas_error);

  while (fas_get_frame_index(context) < target_index)
    {
      if (fas_frame_available(context))
	fas_step_forward(context);
      else
	return private_show_error("error advancing to request frame (probably out of range)", FAS_SEEK_ERROR);
    }


  return FAS_SUCCESS;
}
Пример #2
0
seek_error_type seek_get_nearest_entry_by_time (seek_table_type *table, seek_entry_type *entry, double time, int offset)
{
  /* using offset>0 returns a modified seek_entry that sets the 'time-to-seek' to be $offset keyframes in the past. 
   */

  if (NULL == table || NULL == table->array || table->num_entries <= 0) {
    return private_show_error ("NULL or invalid seek table", seek_bad_argument);
  }

  if (NULL == entry) {
    return private_show_error ("NULL entry buffer (for return)", seek_bad_argument);
  }

  if (time < table->array[0].time)
    return private_show_error ("tried to seek to frame index before first frame", seek_bad_argument);

  int i;
  for (i=0; i < table->num_entries; i++)
    if (table->array[i].time > time)
      break;

  i = i-1;

  if (i<offset)   /* target was lower than first element (including offset) */
    return private_show_error ("target index out of table range (too small)", seek_bad_argument);
  
  *entry = table->array[i];
  (*entry).first_packet_dts = table->array[i-offset].first_packet_dts;

  return seek_no_error;
}
Пример #3
0
/* private_complete_seek_table */
fas_error_type private_complete_seek_table (fas_context_ref_type context)
{
  fas_error_type fas_error;

  if ((NULL == context) || (FAS_FALSE == context->is_video_active))
    return private_show_error("invalid or unopened context", FAS_INVALID_ARGUMENT);

  if (context->seek_table.completed)
    return FAS_SUCCESS;

  fas_error = fas_seek_to_nearest_key(context, context->seek_table.num_frames + FIRST_FRAME_INDEX - 1);
  if (FAS_SUCCESS != fas_error)
    return private_show_error("failed when trying to complete seek table (1) (first frame not labeled keyframe?)", fas_error);

  while (fas_frame_available(context))
    {
      //      printf("%d\n", context->seek_table.num_frames);
      fas_step_forward(context);
    }

  if (!context->seek_table.completed)
    return private_show_error("failed when trying to complete seek table (2)", FAS_SEEK_ERROR);

  return FAS_SUCCESS;
}
Пример #4
0
int fas_get_frame_count (fas_context_ref_type context)
{
  int current_frame;
  int fast;
  fas_error_type fas_error;

  fast = fas_get_frame_count_fast(context);
  if (fast >= 0)
    return fast;

  current_frame = fas_get_frame_index(context);


  fas_error = private_complete_seek_table(context);
  if (FAS_SUCCESS != fas_error)
  {
    private_show_error("failed in get_frame_count trying to complete the seek table", fas_error);
    return -1;
  }

  //  seek_show_raw_table(stderr, context->seek_table);

  fas_error = fas_seek_to_frame(context, current_frame);
  if (FAS_SUCCESS != fas_error)
  {
    private_show_error("failed in get_frame_count when trying to seek back to original location", fas_error);
    return -1;
  }

  fast = fas_get_frame_count_fast(context);
  if (fast < 0)
    private_show_warning("get_frame_count failed");

  return fast;
}
Пример #5
0
int fas_get_frame_index (fas_context_ref_type context)
{
  if (NULL == context)
    return private_show_error("NULL context provided for fas_get_frame_index()", FAS_INVALID_ARGUMENT);

  if (FAS_TRUE != context->is_video_active)
    return private_show_error("No video is open for fas_get_frame_index()", FAS_INVALID_ARGUMENT);

  return context->current_frame_index;
}
Пример #6
0
seek_error_type seek_show_table (seek_table_type table)
{
  seek_entry_type *entry;
  int index;

  if (NULL == table.array || table.num_entries <= 0) {
    return private_show_error ("NULL or invalid seek table", seek_bad_argument);
  }
  
  int completed_flag = 0;
  if (table.completed == seek_true)
    completed_flag = 1;

  fprintf (stderr, "--- Seek Table Dump ---\n");
  fprintf (stderr, "n_frames: %d   n_entries: %d   completed: %d\n",table.num_frames, table.num_entries, completed_flag);
  for (index = 0; index < table.num_entries; index++)
  {
    entry = &(table.array[index]);

    fprintf (stderr, "  %04d %lf --> %08lld (%08lld)\n", entry->display_index, entry->time, entry->first_packet_dts, entry->last_packet_dts);
  }

  fprintf (stderr, "-----------------------\n");

  return seek_no_error;
}
Пример #7
0
/* fas_put_seek_table */
fas_error_type fas_put_seek_table (fas_context_ref_type context, seek_table_type table)
{
  if (NULL == context || FAS_FALSE == context->is_video_active)
    return private_show_error("null context or inactive video", FAS_INVALID_ARGUMENT);

  seek_release_table (&context->seek_table);
  context->seek_table = seek_copy_table(table);

  return FAS_SUCCESS;
}
Пример #8
0
static seek_error_type private_resize_table (seek_table_type *table, int new_size)
{
  seek_entry_type *new_array = NULL;

  if (table == NULL || new_size < 0) {
    return private_show_error ("invalid argument for private_resize_table()", seek_malloc_failed);
  }

  new_array = (seek_entry_type *)malloc (sizeof (seek_entry_type) * new_size);
  if (NULL == new_array) {
    return private_show_error ("unable to allocate more space for table", seek_malloc_failed);
  }

  memcpy (new_array, table->array, table->allocated_size * sizeof (seek_entry_type));
  free (table->array);

  table->allocated_size = new_size;
  table->array          = new_array;
    
  return seek_no_error;
}
Пример #9
0
fas_boolean_type fas_frame_available (fas_context_ref_type context)
{
  if (NULL == context)
    {
      private_show_error("NULL context provided for fas_get_frame_index()", FAS_INVALID_ARGUMENT);
      return FAS_FALSE;
    }

  if (!context->is_video_active)
    return FAS_FALSE;

  return context->is_frame_available;
}
Пример #10
0
seek_error_type seek_append_table_entry (seek_table_type *table, seek_entry_type entry)
{

  if (NULL == table || NULL == table->array) 
    return private_show_error("null or invalid seek table", seek_bad_argument);

  if (table->num_entries != 0)
    if (table->array[table->num_entries - 1].display_index >= entry.display_index)
	return seek_no_error;

  if (table->num_entries == table->allocated_size)
    {
      seek_error_type error = private_resize_table (table, table->num_entries * 2);
      if (error != seek_no_error)
	return private_show_error ("unable to resize seek table", error);
    }

  table->array[table->num_entries] = entry;
  table->num_entries++;

  return seek_no_error;
}
Пример #11
0
int fas_get_frame_count_fast (fas_context_ref_type context)
{

  if (NULL == context || FAS_FALSE == context->is_video_active)
    {
      private_show_error("NULL or invalid context", FAS_INVALID_ARGUMENT);
      return -1;
    }

  if (context->seek_table.completed == seek_true)
    return context->seek_table.num_frames;

  return -1;
}
Пример #12
0
fas_error_type private_convert_to_gray8 (fas_context_ref_type ctx)
{
  static struct SwsContext *img_convert_ctx;
  int w = ctx->codec_context->width;
  int h = ctx->codec_context->height;

  img_convert_ctx = sws_getContext(w, h, ctx->codec_context->pix_fmt,
                                   w, h, PIX_FMT_GRAY8, SWS_BICUBIC,
                                   NULL, NULL, NULL);

  if(img_convert_ctx == NULL)
    private_show_error("cannot initialize the conversion context", FAS_DECODING_ERROR);

  if (ctx->gray8_already_converted)
    return FAS_SUCCESS;

  if(sws_scale(img_convert_ctx, ctx->frame_buffer->data, ctx->frame_buffer->linesize,
               0, h, ctx->gray8_frame_buffer->data, ctx->gray8_frame_buffer->linesize))
     private_show_error("error converting to gray8", FAS_DECODING_ERROR);

  ctx->gray8_already_converted = FAS_TRUE;

  return FAS_SUCCESS;
}
Пример #13
0
/* fas_close_video */
fas_error_type fas_close_video (fas_context_ref_type context)
{
  if (NULL == context)
    return private_show_error("NULL context provided for fas_close_video()", FAS_INVALID_ARGUMENT);

  if (!(context->is_video_active))
    {
      private_show_warning ("Redundant attempt to close an inactive video");
      return FAS_SUCCESS;
    }

  if (context->codec_context)
    if (avcodec_find_decoder (context->codec_context->codec_id))
      avcodec_close(context->codec_context);

  if (context->format_context)
    av_close_input_file (context->format_context);

  if (context->rgb_frame_buffer)
    av_free (context->rgb_frame_buffer);

  if (context->gray8_frame_buffer)
    av_free (context->gray8_frame_buffer);

  if (context->rgb_buffer)
    av_free(context->rgb_buffer);

  if (context->gray8_buffer)
    av_free(context->gray8_buffer);

  if (context->frame_buffer)
    av_free (context->frame_buffer);

  seek_release_table (&(context->seek_table));

  context->is_video_active = FAS_FALSE;

  free (context);

  return FAS_SUCCESS;
}
Пример #14
0
seek_error_type seek_show_raw_table (FILE* file, seek_table_type table)
{
  seek_entry_type *entry;
  int index;

  if (NULL == table.array || table.num_entries <= 0)
    return private_show_error ("NULL or invalid seek table", seek_bad_argument);
  
  int completed_flag = 0;
  if (table.completed == seek_true)
    completed_flag = 1;
  
  fprintf(file, "%d %d %d\n", table.num_frames, table.num_entries, completed_flag);
  for (index = 0; index < table.num_entries; index++)
  {
    entry = &(table.array[index]);

    fprintf (file, "%d %lf %lld %lld\n", entry->display_index, entry->time, entry->first_packet_dts, entry->last_packet_dts);
  }
  return seek_no_error;
}
Пример #15
0
/* fas_open_video */
fas_error_type fas_open_video (fas_context_ref_type *context_ptr, const char *file_path)
{
  int stream_idx;
  int numBytes;
  fas_context_ref_type fas_context;
  AVCodec *codec;

  if (NULL == context_ptr)
    return private_show_error("NULL context pointer provided", FAS_INVALID_ARGUMENT);

  *context_ptr = NULL; // set returned context to NULL in case of error

  fas_context = (fas_context_ref_type) malloc(sizeof(fas_context_type));
  memset(fas_context, 0, sizeof(fas_context_type));

  if (NULL == fas_context)
    return private_show_error("unable to allocate buffer", FAS_OUT_OF_MEMORY);

  fas_context->is_video_active        = FAS_TRUE;
  fas_context->is_frame_available     = FAS_TRUE;
  fas_context->current_frame_index    = FIRST_FRAME_INDEX - 1;
  fas_context->current_dts            = AV_NOPTS_VALUE;
  fas_context->previous_dts           = AV_NOPTS_VALUE;
  fas_context->keyframe_packet_dts    = AV_NOPTS_VALUE;
  fas_context->first_dts              = AV_NOPTS_VALUE;
  fas_context->seek_table = seek_init_table(-1); /* default starting size */

  if (av_open_input_file(&(fas_context->format_context), file_path, NULL, 0, NULL ) != 0)
  {
    fas_close_video(fas_context);
    return private_show_error("failure to open file", FAS_UNSUPPORTED_FORMAT);
  }

  if (av_find_stream_info (fas_context->format_context) < 0)
  {
    fas_close_video(fas_context);
    return private_show_error("could not extract stream information", FAS_UNSUPPORTED_FORMAT);
  }

  if (SHOW_WARNING_MESSAGES)
    av_dump_format(fas_context->format_context, 0, file_path, 0);

  for (stream_idx = 0; stream_idx < fas_context->format_context->nb_streams; stream_idx++)
  {
    if (fas_context->format_context->streams[stream_idx]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
    {
      fas_context->stream_idx = stream_idx;
      fas_context->codec_context  = fas_context->format_context->streams[stream_idx]->codec;
      break;
    }
  }

  if (fas_context->codec_context == 0)
  {
    fas_close_video(fas_context);
    return private_show_error("failure to find a video stream", FAS_UNSUPPORTED_FORMAT);
  }

  codec = avcodec_find_decoder(fas_context->codec_context->codec_id);

  if (!codec)
  {
    fas_context->codec_context = 0;
    fas_close_video(fas_context);
    return private_show_error("failed to find correct video codec", FAS_UNSUPPORTED_CODEC);
  }

  if (avcodec_open(fas_context->codec_context, codec) < 0)
  {
    fas_context->codec_context = 0;
    fas_close_video(fas_context);
    return private_show_error("failed to open codec", FAS_UNSUPPORTED_CODEC);
  }

  fas_context->frame_buffer = avcodec_alloc_frame();
  if (fas_context->frame_buffer == NULL)
  {
    fas_close_video(fas_context);
    return private_show_error("failed to allocate frame buffer", FAS_OUT_OF_MEMORY);
  }

  fas_context->rgb_frame_buffer = avcodec_alloc_frame();
  if (fas_context->rgb_frame_buffer == NULL)
  {
    fas_close_video(fas_context);
    return private_show_error("failed to allocate rgb frame buffer", FAS_OUT_OF_MEMORY);
  }
  numBytes = avpicture_get_size(PIX_FMT_RGB24, fas_context->codec_context->width, fas_context->codec_context->height);
  fas_context->rgb_buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
  avpicture_fill((AVPicture *)fas_context->rgb_frame_buffer, fas_context->rgb_buffer, PIX_FMT_RGB24, fas_context->codec_context->width, fas_context->codec_context->height);

  fas_context->gray8_frame_buffer = avcodec_alloc_frame();
  if (fas_context->gray8_frame_buffer == NULL)
  {
    fas_close_video(fas_context);
    return private_show_error("failed to allocate gray8 frame buffer", FAS_OUT_OF_MEMORY);
  }

  fas_context->rgb_buffer = 0;
  fas_context->gray8_buffer = 0;
  fas_context->rgb_already_converted = FAS_FALSE;
  fas_context->gray8_already_converted = FAS_FALSE;

  *context_ptr = fas_context;

  if (FAS_SUCCESS != fas_step_forward(*context_ptr))
    return private_show_error("failure decoding first frame", FAS_NO_MORE_FRAMES);

  if (!fas_frame_available(*context_ptr))
    return private_show_error("couldn't find a first frame (no valid frames in video stream)", FAS_NO_MORE_FRAMES);

  return FAS_SUCCESS;
}
Пример #16
0
/* fas_step_forward */
fas_error_type fas_step_forward (fas_context_ref_type context)
{
  AVPacket packet;
  int frameFinished;


  if ((NULL == context) || (FAS_TRUE != context->is_video_active)) {
    return private_show_error("invalid or unopened context", FAS_INVALID_ARGUMENT);
  }

  if (!context->is_frame_available)
    {
      private_show_warning ("tried to advance after end of frames");
      return FAS_SUCCESS;
    }

  context->current_frame_index++;

  while (FAS_TRUE)
    {
      if (av_read_frame(context->format_context, &packet) < 0)
	{
	  /* finished */
	  context->is_frame_available = FAS_FALSE;
	  context->seek_table.completed = seek_true;
	  return FAS_SUCCESS;
	}

      if (packet.stream_index == context->stream_idx)
	{
	  context->previous_dts = context->current_dts;
	  context->current_dts = packet.dts;

	  /* seek support: set first_dts */
	  if (context->first_dts == AV_NOPTS_VALUE)
	    context->first_dts = packet.dts;

	  /* seek support: set key-packet info to previous packet's dts, when possible */
	  /* note this -1 approach to setting the packet is a workaround for a common failure. setting
	     to 0 would work just incur a huge penalty in videos that needed -1. Might be worth testing.
	  */
	  if (packet.flags & AV_PKT_FLAG_KEY)
	    {
	      //fprintf(stderr, "Packet: (F:%d %lld %lld)\n", context->current_frame_index, packet.pts, packet.dts);

	      if (context->previous_dts == AV_NOPTS_VALUE)
		context->keyframe_packet_dts = packet.dts;
	      else
		context->keyframe_packet_dts = context->previous_dts;
	    }

	  avcodec_decode_video2(context->codec_context, context->frame_buffer, &frameFinished, &packet);

	  if (frameFinished)
	    {
	      /* seek support: (try to) add entry to seek_table */
	      if (context->frame_buffer->key_frame)
		{
		  //		fprintf(stderr, "Frame : (PXX F%d: %lld %lld)\n", context->current_frame_index, packet.pts, packet.dts);

		  seek_entry_type entry;
		  entry.display_index = context->current_frame_index;
		  entry.first_packet_dts = context->keyframe_packet_dts;
		  entry.last_packet_dts = packet.dts;

		  if (fas_get_frame_index(context) == FIRST_FRAME_INDEX)
		    entry.first_packet_dts = context->first_dts;

		  seek_append_table_entry(&context->seek_table, entry);
		}

	      if (context->current_frame_index - FIRST_FRAME_INDEX + 1 > context->seek_table.num_frames)
		context->seek_table.num_frames = context->current_frame_index - FIRST_FRAME_INDEX + 1;

	      break;
	    }
	}

      av_free_packet(&packet);
    }

  context->rgb_already_converted = FAS_FALSE;
  context->gray8_already_converted = FAS_FALSE;
  av_free_packet(&packet);
  return FAS_SUCCESS;
}
Пример #17
0
fas_error_type fas_get_frame(fas_context_ref_type context, fas_raw_image_type *image_ptr)
{
  int buffer_size;
  fas_error_type fas_error;
  int j;
  unsigned char *from;
  unsigned char *to;

  if (NULL == context || FAS_FALSE == context->is_video_active)
    return private_show_error("null context or inactive video", FAS_INVALID_ARGUMENT);

  if (NULL == image_ptr)
    return private_show_error("null image_ptr on get_frame", FAS_INVALID_ARGUMENT);

  if (!fas_frame_available(context))
    return private_show_error("no frame available for extraction", FAS_NO_MORE_FRAMES);

  memset (image_ptr, 0, sizeof (fas_raw_image_type));

  switch (fmt)
  {
  case PIX_FMT_RGB24:
	  image_ptr->bytes_per_line = context->codec_context->width * 3;
	  image_ptr->color_space    = FAS_RGB24;
	  break;
  case PIX_FMT_BGR24:
	  image_ptr->bytes_per_line = context->codec_context->width * 3;
	  image_ptr->color_space    = FAS_BGR24;
	  break;
  case PIX_FMT_ARGB:
	  image_ptr->bytes_per_line = context->codec_context->width * 4;
	  image_ptr->color_space    = FAS_ARGB32;
	  break;
  case PIX_FMT_ABGR:
	  image_ptr->bytes_per_line = context->codec_context->width * 4;
	  image_ptr->color_space    = FAS_ABGR32;
	  break;
  case PIX_FMT_YUV420P:
	  image_ptr->bytes_per_line = (context->codec_context->width * 3) >> 1;
	  image_ptr->color_space    = FAS_YUV420P;
	  break;
  case PIX_FMT_YUYV422:
	  image_ptr->bytes_per_line = context->codec_context->width * 2;
	  image_ptr->color_space    = FAS_YUYV422;
	  break;
  case PIX_FMT_UYVY422:
	  image_ptr->bytes_per_line = context->codec_context->width * 2;
	  image_ptr->color_space    = FAS_UYVY422;
	  break;
  case PIX_FMT_YUV422P:
	  image_ptr->bytes_per_line = context->codec_context->width * 2;
	  image_ptr->color_space    = FAS_YUV422P;
	  break;
  case PIX_FMT_YUV444P:
	  image_ptr->bytes_per_line = context->codec_context->width * 3;
	  image_ptr->color_space    = FAS_YUV444P;
	  break;
  }

  buffer_size = image_ptr->bytes_per_line * context->codec_context->height;

  image_ptr->data = (unsigned char *)malloc (buffer_size);
  if (NULL == image_ptr->data)
    return private_show_error("unable to allocate space for RGB image", FAS_OUT_OF_MEMORY);

  image_ptr->width          = context->codec_context->width;
  image_ptr->height         = context->codec_context->height;


  fas_error = private_convert_to_rgb(context);


  for (j=0;j<context->codec_context->height; j++)
    {
      from = context->rgb_frame_buffer->data[0] + j*context->rgb_frame_buffer->linesize[0];
      to = image_ptr->data + j*image_ptr->bytes_per_line;

      memcpy(to, from, image_ptr->bytes_per_line);
    }

  if (FAS_SUCCESS != fas_error)
    return private_show_error("unable to convert image to RGB", FAS_FAILURE);

  return FAS_SUCCESS;
}
Пример #18
0
/* private_seek_to_nearest_key */
fas_error_type private_seek_to_nearest_key (fas_context_ref_type context, int target_index, int offset)
{
  fas_error_type fas_error;
  seek_entry_type seek_entry;
  seek_error_type seek_error;
  int flags = 0;

  if ((NULL == context) || (FAS_TRUE != context->is_video_active))
    return private_show_error("invalid or unopened context", FAS_INVALID_ARGUMENT);

#ifdef _DEBUG
  printf("HERE: from: %d to: %d offset: %d\n", context->current_frame_index, target_index, offset);
#endif
  seek_error = seek_get_nearest_entry(&(context->seek_table), &seek_entry, target_index, offset);

  if (seek_error != seek_no_error)
    return private_show_error("error while searching seek table", FAS_SEEK_ERROR);

  if (seek_entry.display_index == context->current_frame_index)
    return FAS_SUCCESS;

#ifdef _DEBUG
  printf("HERE: from: %d to: %d (%d) offset: %d\n", context->current_frame_index, target_index, seek_entry.display_index, offset);
  printf("trying to seek to %d (%lld->%lld)\n", seek_entry.display_index, seek_entry.first_packet_dts, seek_entry.last_packet_dts);
#endif

  // if something goes terribly wrong, return bad current_frame_index
  context->current_frame_index = -2;
  context->is_frame_available = FAS_TRUE;

  if (seek_entry.first_packet_dts <= context->current_dts)
    flags = AVSEEK_FLAG_BACKWARD;

  //  printf("av_seek_frame: %lld\n", seek_entry.first_packet_dts);
  if (av_seek_frame(context->format_context, context->stream_idx, seek_entry.first_packet_dts, flags) < 0)
    return private_show_error("seek to keyframe failed", FAS_SEEK_ERROR);

  avcodec_flush_buffers (context->codec_context);

  fas_error = fas_step_forward (context);

  if (fas_error != FAS_SUCCESS || !context->is_frame_available)
  {
    // something bad has happened, try previous keyframe
    private_show_warning("processing of seeked keyframe failed, trying previous keyframe");
    return private_seek_to_nearest_key(context, target_index, offset + 1);
  }

  while (context->current_dts < seek_entry.last_packet_dts)
  {
#ifdef _DEBUG
    printf("frame-times: current: %lld target: %lld is_key: %d\n", context->current_dts, seek_entry.last_packet_dts, context->frame_buffer->key_frame);
#endif
    fas_error = fas_step_forward(context);
    if (fas_error != FAS_SUCCESS)
      return private_show_error("unable to process up to target frame (fas_seek_to_frame)", fas_error);
  }

#ifdef _DEBUG
  printf("keyframe vitals: %d looking_for: %lld at: %lld\n", seek_entry.display_index, seek_entry.last_packet_dts, context->current_dts);
#endif
  if (context->current_dts != seek_entry.last_packet_dts)
  {
    /* seek to last key-frame, but look for this one */
    private_show_warning("missed keyframe, trying previous keyframe");
    return private_seek_to_nearest_key(context, target_index, offset + 1);
  }

  /**
   * Ideally, we could just check if the frame decoded is of the correct time
   * stamp, but we need several ugly workarounds:
   *
   * 1) Some videos have bad keyframes that don't get decoded properly. In this
   *    cases, we need to go back a keyframe.
   *
   * 2) Other times, none of the frames are labeled keyframes. In these cases,
   *    we need to allow seeking to frame 0 even when it's not labeled as a
   *    keyframe. Messy set of conditions.
   */

  if ((!context->frame_buffer->key_frame) && (seek_entry.display_index != 0))
  {
    private_show_warning("found keyframe, but not labeled as keyframe, so trying previous keyframe.");
    /* seek & look for previous keyframe */
    /* REMOVE FROM TABLE?                */
    return private_seek_to_nearest_key(context, seek_entry.display_index - 1, 0);
  }

  context->current_frame_index = seek_entry.display_index;

  return FAS_SUCCESS;
}