Ejemplo n.º 1
0
static int
be_dvd_init(void)
{
  disc_scanner_t *ds = calloc(1, sizeof(disc_scanner_t));

  ds->ds_dev = strdup("/dev/dvd");
  callout_arm(&ds->ds_timer, dvdprobe, ds, 0);
  return 0;
}
Ejemplo n.º 2
0
void
blobcache_init(void)
{
#ifndef BC_USE_FILE_LOCKS
  int i;
  for(i = 0; i < BC_NUM_FILE_LOCKS; i++)
    hts_mutex_init(&blobcache_lock[i]);
#endif
  hts_mutex_init(&blobcache_mutex);
  callout_arm(&blobcache_callout, blobcache_do_prune, NULL, 1);
}
Ejemplo n.º 3
0
static void
ifv_autoclose(callout_t *c, void *aux)
{
  if(hts_mutex_trylock(&image_from_video_mutex[1])) {
    callout_arm(&thumb_flush_callout, ifv_autoclose, NULL, 5);
  } else {
    TRACE(TRACE_DEBUG, "Thumb", "Closing movie for thumb sources"); 
    ifv_close();
    hts_mutex_unlock(&image_from_video_mutex[1]);
  }
}
Ejemplo n.º 4
0
void *
notify_add(prop_t *root, notify_type_t type, const char *icon, int delay,
	   rstr_t *fmt, ...)
{
  char msg[256];
  prop_t *p;
  const char *typestr;
  int tl;
  va_list ap, apx;

  switch(type) {
  case NOTIFY_INFO:    typestr = "info";    tl = TRACE_INFO;  break;
  case NOTIFY_WARNING: typestr = "warning"; tl = TRACE_INFO;  break;
  case NOTIFY_ERROR:   typestr = "error";   tl = TRACE_ERROR; break;
  default: return NULL;
  }
  
  va_start(ap, fmt);
  va_copy(apx, ap);

  tracev(0, tl, "notify", rstr_get(fmt), ap);

  vsnprintf(msg, sizeof(msg), rstr_get(fmt), apx);

  va_end(ap);
  va_end(apx);

  rstr_release(fmt);

  p = prop_create_root(NULL);

  prop_set_string(prop_create(p, "text"), msg);
  prop_set_string(prop_create(p, "type"), typestr);

  if(icon != NULL)
    prop_set_string(prop_create(p, "icon"), icon);

  p = prop_ref_inc(p);

  if(prop_set_parent(p, root ?: notify_prop_entries))
    prop_destroy(p);

  if(delay != 0) {
    prop_t *r = NULL;
    if(delay < 0) {
      r = prop_ref_inc(p);
      delay = -delay;
    }
    callout_arm(NULL, notify_timeout, p, delay);
    return r;
  }
  return p;
}
Ejemplo n.º 5
0
static void
update_sleeptimer(void *opaque, int v)
{
  TRACE(TRACE_DEBUG, "runcontrol", "Sleep timer %s",
        v ? "enabled" : "disabled");
  sleeptimer_enabled = v;
  if(v) {
    prop_set_int(sleeptime_prop, 60);
    callout_arm(&sleep_timer, decrease_sleeptimer, NULL, 60);
  } else {
    callout_disarm(&sleep_timer);
  }
}
Ejemplo n.º 6
0
/**
 * Periodically check if we should auto standby
 */
static void
check_autostandby(callout_t *c, void *aux)
{
    int64_t idle = showtime_get_ts() - last_activity;

    idle /= (1000000 * 60); // Convert to minutes

    if(standby_delay && idle >= standby_delay && !active_media) {
        TRACE(TRACE_INFO, "runcontrol", "Automatic standby after %d minutes idle",
              standby_delay);
        showtime_shutdown(SHOWTIME_EXIT_STANDBY);
        return;
    }
    callout_arm(&autostandby_timer, check_autostandby, NULL, 1);
}
Ejemplo n.º 7
0
static void 
dvdprobe(callout_t *co, void *aux)
{
  disc_scanner_t *ds = aux;

  callout_arm(&ds->ds_timer, dvdprobe, ds, 1);

  if(ds->ds_disc_inserted == 0) {

    DI_Mount();

    if(DI_GetStatus() == DVD_NO_DISC)
      return;

    TRACE(TRACE_INFO, "DVD", "DVD inserted");
    ds->ds_disc_inserted = 1;
    ds->ds_disc_ready = 0;

  } else {
    uint32_t s = DI_GetStatus();


    if(s == DVD_UNKNOWN) {
      ds->ds_disc_inserted = 0;
      notify_add(NOTIFY_ERROR, NULL, 5, "Unknown disc inserted, ejecting...");
      DI_Eject();
      return;
    }

    if(!(s & DVD_READY)) {
      TRACE(TRACE_DEBUG, "DVD", "Waiting for disc ready state = %x", s);
      return;
    }

    if(!ds->ds_disc_ready)
      check_disc_type(ds);

    if(DI_GetCoverRegister(&s) || !(s & DVD_COVER_DISC_INSERTED)) {
      ds->ds_disc_inserted = 0;
      TRACE(TRACE_INFO, "DVD", "DVD no longer present");
      
      if(ds->ds_service != NULL) {
	service_destroy(ds->ds_service);
	ds->ds_service = NULL;
      }
    }
  }
}
Ejemplo n.º 8
0
static int
blobcache_save(int fd, const void *data, size_t size, time_t expire,
	       int lockhash)
{
  uint8_t buf[4];

#ifndef BC_USE_FILE_LOCKS
  hts_mutex_lock(&blobcache_lock[lockhash & BC_FILE_LOCKS_MASK]);
#else
  if(flock(fd, LOCK_EX))
    return -1;
#endif
  
  buf[0] = expire >> 24;
  buf[1] = expire >> 16;
  buf[2] = expire >> 8;
  buf[3] = expire;

  if(write(fd, buf, 4) != 4)
    goto bad;

  if(write(fd, data, size) != size)
    goto bad;

  if(ftruncate(fd, size + 4))
    goto bad;

  hts_mutex_lock(&blobcache_mutex);
  blobcache_size_current += size + 4;

  if(blobcache_size_current > blobcache_size_max) {

    if(!callout_isarmed(&blobcache_callout))
      callout_arm(&blobcache_callout, blobcache_do_prune, NULL, 5);
  }

  hts_mutex_unlock(&blobcache_mutex);

#ifndef BC_USE_FILE_LOCKS
  hts_mutex_unlock(&blobcache_lock[lockhash & BC_FILE_LOCKS_MASK]);
#endif
  return 0;
 bad:
#ifndef BC_USE_FILE_LOCKS
  hts_mutex_unlock(&blobcache_lock[lockhash & BC_FILE_LOCKS_MASK]);
#endif
  return -1;
}
Ejemplo n.º 9
0
static void
decrease_sleeptimer(callout_t *c, void *aux)
{
  if(!sleeptimer_enabled)
    return;

  sleeptime--;

  if(sleeptime < 0) {
    TRACE(TRACE_INFO, "runcontrol", "Automatic standby by sleep timer");
    app_shutdown(APP_EXIT_STANDBY);
    return;
  }
  prop_set_int_ex(sleeptime_prop, sleeptime_sub, sleeptime);
  callout_arm(&sleep_timer, decrease_sleeptimer, NULL, 60);

}
Ejemplo n.º 10
0
static void
init_autostandby(void)
{
    htsmsg_t *store = htsmsg_store_load("runcontrol");
    if(store == NULL)
        store = htsmsg_create_map();

    settings_create_int(settings_general, "autostandby", _p("Automatic standby"),
                        0, store, 0, 60, 5, set_autostandby, NULL,
                        SETTINGS_INITIAL_UPDATE, " min", NULL,
                        runcontrol_save_settings, NULL);

    last_activity = showtime_get_ts();

    prop_subscribe(0,
                   PROP_TAG_NAME("global", "media", "current", "playstatus"),
                   PROP_TAG_CALLBACK_STRING, current_media_playstatus, NULL,
                   NULL);

    callout_arm(&autostandby_timer, check_autostandby, NULL, 1);
}
Ejemplo n.º 11
0
static void
blobcache_put0(sqlite3 *db, const char *key, const char *stash,
               const void *data, size_t size, int maxage,
               const char *etag, time_t mtime)
{
    int rc;
    time_t now = time(NULL);

    sqlite3_stmt *stmt;

    rc = sqlite3_prepare_v2(db,
                            "INSERT OR REPLACE INTO item "
                            "(k, stash, payload, lastaccess, expiry, etag, modtime) "
                            "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
                            -1, &stmt, NULL);
    if(rc != SQLITE_OK) {
        TRACE(TRACE_ERROR, "SQLITE", "SQL Error at %s:%d",
              __FUNCTION__, __LINE__);
        return;
    }

    sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, stash, -1, SQLITE_STATIC);
    sqlite3_bind_blob(stmt, 3, data, size, SQLITE_STATIC);
    sqlite3_bind_int(stmt,  4, now);
    sqlite3_bind_int64(stmt,  5, (int64_t)now + maxage);

    if(etag != NULL)
        sqlite3_bind_text(stmt, 6, etag, -1, SQLITE_STATIC);

    if(mtime != 0)
        sqlite3_bind_int(stmt,  7, mtime);

    rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);

    int s = atomic_add(&estimated_cache_size, size) + size;
    if(blobcache_compute_maxsize(s) < s && !callout_isarmed(&blobcache_callout))
        callout_arm(&blobcache_callout, blobcache_do_prune, NULL, 5);
}
Ejemplo n.º 12
0
static void 
dvdprobe(callout_t *co, void *aux)
{
  disc_scanner_t *ds = aux;
  int fd;

  callout_arm(&ds->ds_timer, dvdprobe, ds, 1);

  fd = open(ds->ds_dev, O_RDONLY | O_NONBLOCK);

  if(fd == -1) {
    set_status(ds, DISC_NO_DRIVE, NULL);
  } else {
    if(ioctl(fd, CDROM_DRIVE_STATUS, NULL) == CDS_DISC_OK) {
      if(ds->ds_svc == NULL)
	check_disc_type(ds, fd);
    } else {
      set_status(ds, DISC_NO_DISC, NULL);
    }
    close(fd);
  }
}
Ejemplo n.º 13
0
static void
init_autostandby(void)
{
  setting_create(SETTING_INT, gconf.settings_general, SETTINGS_INITIAL_UPDATE,
                 SETTING_TITLE(_p("Automatic standby")),
                 SETTING_STORE("runcontrol", "autostandby"),
                 SETTING_WRITE_INT(&standby_delay),
                 SETTING_RANGE(0, 60),
                 SETTING_STEP(5),
                 SETTING_UNIT_CSTR("min"),
                 SETTING_ZERO_TEXT(_p("Off")),
                 NULL);

  last_activity = arch_get_ts();

  prop_subscribe(0,
		 PROP_TAG_NAME("global", "media", "current", "playstatus"),
		 PROP_TAG_CALLBACK_STRING, current_media_playstatus, NULL,
		 NULL);

  callout_arm(&autostandby_timer, check_autostandby, NULL, 1);
}
Ejemplo n.º 14
0
static void
darwin_init_cpu_monitor(void)
{
  kern_return_t r;
  processor_info_t pinfo;
  mach_msg_type_number_t msg_count;

  p_sys = prop_create(prop_get_global(), "system");

  r = host_processor_info(mach_host_self (),
                          PROCESSOR_CPU_LOAD_INFO,
                          &cpu_count,
                          (processor_info_array_t *)&pinfo,
                          &msg_count);
  if(r != KERN_SUCCESS) {
    TRACE(TRACE_ERROR, "darwin",
          "host_processor_info(PROCESSOR_CPU_LOAD_INFO) failed %d", r);
    return;
  }
  
  p_cpu = calloc(cpu_count, sizeof(prop_t *));
  p_load  = calloc(cpu_count, sizeof(prop_t *));
  last_total = calloc(cpu_count, sizeof(unsigned int));
  last_idle = calloc(cpu_count, sizeof(unsigned int));
  
  prop_set_int(prop_create(prop_create(p_sys, "cpuinfo"), "available"), 1);
  p_cpuroot =  prop_create(prop_create(p_sys, "cpuinfo"), "cpus");
  
  vm_deallocate(mach_task_self(),
                (vm_address_t)pinfo,
                (vm_size_t)sizeof(*pinfo) * msg_count);
  
  cpu_monitor_do();
  mem_monitor_do();
  callout_arm(&timer, timercb, NULL, 1);
}
Ejemplo n.º 15
0
static pixmap_t *
fa_image_from_video2(const char *url, const image_meta_t *im, 
		     const char *cacheid, char *errbuf, size_t errlen,
		     int sec, time_t mtime, cancellable_t *c)
{
  pixmap_t *pm = NULL;

  if(ifv_url == NULL || strcmp(url, ifv_url)) {
    // Need to open
    int i;
    AVFormatContext *fctx;
    fa_handle_t *fh = fa_open_ex(url, errbuf, errlen, FA_BUFFERED_BIG, NULL);

    if(fh == NULL)
      return NULL;

    AVIOContext *avio = fa_libav_reopen(fh, 0);

    if((fctx = fa_libav_open_format(avio, url, NULL, 0, NULL, 0, 0,
				    0)) == NULL) {
      fa_libav_close(avio);
      snprintf(errbuf, errlen, "Unable to open format");
      return NULL;
    }

    if(!strcmp(fctx->iformat->name, "avi"))
      fctx->flags |= AVFMT_FLAG_GENPTS;

    AVCodecContext *ctx = NULL;
    for(i = 0; i < fctx->nb_streams; i++) {
      if(fctx->streams[i]->codec != NULL && 
	 fctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
	ctx = fctx->streams[i]->codec;
	break;
      }
    }
    if(ctx == NULL) {
      fa_libav_close_format(fctx);
      return NULL;
    }

    AVCodec *codec = avcodec_find_decoder(ctx->codec_id);
    if(codec == NULL) {
      fa_libav_close_format(fctx);
      snprintf(errbuf, errlen, "Unable to find codec");
      return NULL;
    }

    if(avcodec_open2(ctx, codec, NULL) < 0) {
      fa_libav_close_format(fctx);
      snprintf(errbuf, errlen, "Unable to open codec");
      return NULL;
    }

    ifv_close();

    ifv_stream = i;
    ifv_url = strdup(url);
    ifv_fctx = fctx;
    ifv_ctx = ctx;
  }

  AVPacket pkt;
  AVFrame *frame = av_frame_alloc();
  int got_pic;


  AVStream *st = ifv_fctx->streams[ifv_stream];
  int64_t ts = av_rescale(sec, st->time_base.den, st->time_base.num);

  if(av_seek_frame(ifv_fctx, ifv_stream, ts, AVSEEK_FLAG_BACKWARD) < 0) {
    ifv_close();
    snprintf(errbuf, errlen, "Unable to seek to %"PRId64, ts);
    return NULL;
  }
  
  avcodec_flush_buffers(ifv_ctx);

#define MAX_FRAME_SCAN 500
  
  int cnt = MAX_FRAME_SCAN;
  while(1) {
    int r;

    r = av_read_frame(ifv_fctx, &pkt);

    if(r == AVERROR(EAGAIN))
      continue;

    if(r == AVERROR_EOF)
      break;

    if(cancellable_is_cancelled(c)) {
      snprintf(errbuf, errlen, "Cancelled");
      av_free_packet(&pkt);
      break;
    }

    if(r != 0) {
      ifv_close();
      break;
    }

    if(pkt.stream_index != ifv_stream) {
      av_free_packet(&pkt);
      continue;
    }
    cnt--;
    int want_pic = pkt.pts >= ts || cnt <= 0;

    ifv_ctx->skip_frame = want_pic ? AVDISCARD_DEFAULT : AVDISCARD_NONREF;
    
    avcodec_decode_video2(ifv_ctx, frame, &got_pic, &pkt);
    av_free_packet(&pkt);
    if(got_pic == 0 || !want_pic) {
      continue;
    }
    int w,h;

    if(im->im_req_width != -1 && im->im_req_height != -1) {
      w = im->im_req_width;
      h = im->im_req_height;
    } else if(im->im_req_width != -1) {
      w = im->im_req_width;
      h = im->im_req_width * ifv_ctx->height / ifv_ctx->width;

    } else if(im->im_req_height != -1) {
      w = im->im_req_height * ifv_ctx->width / ifv_ctx->height;
      h = im->im_req_height;
    } else {
      w = im->im_req_width;
      h = im->im_req_height;
    }

    pm = pixmap_create(w, h, PIXMAP_BGR32, 0);

    if(pm == NULL) {
      ifv_close();
      snprintf(errbuf, errlen, "Out of memory");
      av_free(frame);
      return NULL;
    }

    struct SwsContext *sws;
    sws = sws_getContext(ifv_ctx->width, ifv_ctx->height, ifv_ctx->pix_fmt,
			 w, h, AV_PIX_FMT_BGR32, SWS_BILINEAR,
                         NULL, NULL, NULL);
    if(sws == NULL) {
      ifv_close();
      snprintf(errbuf, errlen, "Scaling failed");
      pixmap_release(pm);
      av_free(frame);
      return NULL;
    }
    
    uint8_t *ptr[4] = {0,0,0,0};
    int strides[4] = {0,0,0,0};

    ptr[0] = pm->pm_pixels;
    strides[0] = pm->pm_linesize;

    sws_scale(sws, (const uint8_t **)frame->data, frame->linesize,
	      0, ifv_ctx->height, ptr, strides);

    sws_freeContext(sws);

    write_thumb(ifv_ctx, frame, w, h, cacheid, mtime);

    break;
  }

  av_frame_free(&frame);
  if(pm == NULL)
    snprintf(errbuf, errlen, "Frame not found (scanned %d)", 
	     MAX_FRAME_SCAN - cnt);

  avcodec_flush_buffers(ifv_ctx);
  callout_arm(&thumb_flush_callout, ifv_autoclose, NULL, 5);
  return pm;
}
Ejemplo n.º 16
0
static void 
timercb(callout_t *c, void *aux)
{
  callout_arm(&timer, timercb, NULL, 1);
  cpu_monitor_do();
}
Ejemplo n.º 17
0
static image_t *
fa_image_from_video2(const char *url, const image_meta_t *im,
		     const char *cacheid, char *errbuf, size_t errlen,
		     int sec, time_t mtime, cancellable_t *c)
{
  image_t *img = NULL;

  if(ifv_url == NULL || strcmp(url, ifv_url)) {
    // Need to open
    int i;
    AVFormatContext *fctx;
    fa_handle_t *fh = fa_open_ex(url, errbuf, errlen, FA_BUFFERED_BIG, NULL);

    if(fh == NULL)
      return NULL;

    AVIOContext *avio = fa_libav_reopen(fh, 0);

    if((fctx = fa_libav_open_format(avio, url, NULL, 0, NULL, 0, 0,
				    0)) == NULL) {
      fa_libav_close(avio);
      snprintf(errbuf, errlen, "Unable to open format");
      return NULL;
    }

    if(!strcmp(fctx->iformat->name, "avi"))
      fctx->flags |= AVFMT_FLAG_GENPTS;

    AVCodecContext *ctx = NULL;
    int vstream = 0;
    for(i = 0; i < fctx->nb_streams; i++) {
      AVStream *st = fctx->streams[i];
      AVCodecContext *c = st->codec;
      AVDictionaryEntry *mt;

      if(c == NULL)
        continue;

      switch(c->codec_type) {
      case AVMEDIA_TYPE_VIDEO:
        if(ctx == NULL) {
          vstream = i;
          ctx = fctx->streams[i]->codec;
        }
        break;

      case AVMEDIA_TYPE_ATTACHMENT:
        mt = av_dict_get(st->metadata, "mimetype", NULL, AV_DICT_IGNORE_SUFFIX);
        if(sec == -1 && mt != NULL &&
           (!strcmp(mt->value, "image/jpeg") ||
            !strcmp(mt->value, "image/png"))) {
          int64_t offset = st->attached_offset;
          int size = st->attached_size;
          fa_libav_close_format(fctx);
          return thumb_from_attachment(url, offset, size, errbuf, errlen,
                                       cacheid, mtime);
        }
        break;

      default:
        break;
      }
    }
    if(ctx == NULL) {
      fa_libav_close_format(fctx);
      return NULL;
    }

    AVCodec *codec = avcodec_find_decoder(ctx->codec_id);
    if(codec == NULL) {
      fa_libav_close_format(fctx);
      snprintf(errbuf, errlen, "Unable to find codec");
      return NULL;
    }

    if(avcodec_open2(ctx, codec, NULL) < 0) {
      fa_libav_close_format(fctx);
      snprintf(errbuf, errlen, "Unable to open codec");
      return NULL;
    }

    ifv_close();

    ifv_stream = vstream;
    ifv_url = strdup(url);
    ifv_fctx = fctx;
    ifv_ctx = ctx;
  }

  AVPacket pkt;
  AVFrame *frame = av_frame_alloc();
  int got_pic;

#define MAX_FRAME_SCAN 500

  int cnt = MAX_FRAME_SCAN;

  AVStream *st = ifv_fctx->streams[ifv_stream];

  if(sec == -1) {
    // Automatically try to find a good frame

    int duration_in_seconds = ifv_fctx->duration / 1000000;


    sec = MAX(1, duration_in_seconds * 0.05); // 5% of duration
    sec = MIN(sec, 150); // , buy no longer than 2:30 in

    sec = MAX(0, MIN(sec, duration_in_seconds - 1));
    cnt = 1;
  }


  int64_t ts = av_rescale(sec, st->time_base.den, st->time_base.num);
  int delayed_seek = 0;

  if(ifv_ctx->codec_id == AV_CODEC_ID_RV40 ||
     ifv_ctx->codec_id == AV_CODEC_ID_RV30) {
    // Must decode one frame
    delayed_seek = 1;
  } else {
    if(av_seek_frame(ifv_fctx, ifv_stream, ts, AVSEEK_FLAG_BACKWARD) < 0) {
      ifv_close();
      snprintf(errbuf, errlen, "Unable to seek to %"PRId64, ts);
      return NULL;
    }
  }

  avcodec_flush_buffers(ifv_ctx);

  while(1) {
    int r;

    r = av_read_frame(ifv_fctx, &pkt);

    if(r == AVERROR(EAGAIN))
      continue;

    if(r == AVERROR_EOF)
      break;

    if(cancellable_is_cancelled(c)) {
      snprintf(errbuf, errlen, "Cancelled");
      av_free_packet(&pkt);
      break;
    }

    if(r != 0) {
      ifv_close();
      break;
    }

    if(pkt.stream_index != ifv_stream) {
      av_free_packet(&pkt);
      continue;
    }
    cnt--;
    int want_pic = pkt.pts >= ts || cnt <= 0;

    ifv_ctx->skip_frame = want_pic ? AVDISCARD_DEFAULT : AVDISCARD_NONREF;

    avcodec_decode_video2(ifv_ctx, frame, &got_pic, &pkt);
    av_free_packet(&pkt);

    if(delayed_seek) {
      delayed_seek = 0;
      if(av_seek_frame(ifv_fctx, ifv_stream, ts, AVSEEK_FLAG_BACKWARD) < 0) {
        ifv_close();
        break;
      }
      continue;
    }


    if(got_pic == 0 || !want_pic) {
      continue;
    }
    int w,h;

    if(im->im_req_width != -1 && im->im_req_height != -1) {
      w = im->im_req_width;
      h = im->im_req_height;
    } else if(im->im_req_width != -1) {
      w = im->im_req_width;
      h = im->im_req_width * ifv_ctx->height / ifv_ctx->width;

    } else if(im->im_req_height != -1) {
      w = im->im_req_height * ifv_ctx->width / ifv_ctx->height;
      h = im->im_req_height;
    } else {
      w = im->im_req_width;
      h = im->im_req_height;
    }

    pixmap_t *pm = pixmap_create(w, h, PIXMAP_BGR32, 0);

    if(pm == NULL) {
      ifv_close();
      snprintf(errbuf, errlen, "Out of memory");
      av_free(frame);
      return NULL;
    }

    struct SwsContext *sws;
    sws = sws_getContext(ifv_ctx->width, ifv_ctx->height, ifv_ctx->pix_fmt,
			 w, h, AV_PIX_FMT_BGR32, SWS_BILINEAR,
                         NULL, NULL, NULL);
    if(sws == NULL) {
      ifv_close();
      snprintf(errbuf, errlen, "Scaling failed");
      pixmap_release(pm);
      av_free(frame);
      return NULL;
    }
    
    uint8_t *ptr[4] = {0,0,0,0};
    int strides[4] = {0,0,0,0};

    ptr[0] = pm->pm_data;
    strides[0] = pm->pm_linesize;

    sws_scale(sws, (const uint8_t **)frame->data, frame->linesize,
	      0, ifv_ctx->height, ptr, strides);

    sws_freeContext(sws);

    write_thumb(ifv_ctx, frame, w, h, cacheid, mtime);

    img = image_create_from_pixmap(pm);
    pixmap_release(pm);

    break;
  }

  av_frame_free(&frame);
  if(img == NULL)
    snprintf(errbuf, errlen, "Frame not found (scanned %d)", 
	     MAX_FRAME_SCAN - cnt);

  if(ifv_ctx != NULL) {
    avcodec_flush_buffers(ifv_ctx);
    callout_arm(&thumb_flush_callout, ifv_autoclose, NULL, 5);
  }
  return img;
}