]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Add missing DVR files
authorAndreas Öman <andreas@lonelycoder.com>
Wed, 17 Sep 2008 21:08:17 +0000 (21:08 +0000)
committerAndreas Öman <andreas@lonelycoder.com>
Wed, 17 Sep 2008 21:08:17 +0000 (21:08 +0000)
dvr/dvr.h [new file with mode: 0644]
dvr/dvr_db.c [new file with mode: 0644]
dvr/dvr_rec.c [new file with mode: 0644]

diff --git a/dvr/dvr.h b/dvr/dvr.h
new file mode 100644 (file)
index 0000000..e8cbe21
--- /dev/null
+++ b/dvr/dvr.h
@@ -0,0 +1,99 @@
+/*
+ *  Digital Video Recorder
+ *  Copyright (C) 2008 Andreas Öman
+ *
+ *  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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DVR_H
+#define DVR_H
+
+#include <libavformat/avformat.h>
+#include "epg.h"
+#include "channels.h"
+#include "subscriptions.h"
+
+extern char *dvr_storage;
+extern char *dvr_format;
+extern char *dvr_file_postfix;
+
+
+
+typedef enum {
+  DVR_SCHEDULED,         /* Scheduled for recording (in the future) */
+  DVR_RECORDING,
+  DVR_COMPLETED          /* If recording failed, de->de_error is set to
+                           a string */
+
+} dvr_entry_sched_state_t;
+
+
+typedef struct dvr_entry {
+  LIST_ENTRY(dvr_entry) de_global_link;
+  int de_id;
+  
+  channel_t *de_channel;
+  LIST_ENTRY(dvr_entry) de_channel_link;
+
+  time_t de_start;
+  time_t de_stop;
+
+  char *de_creator;
+  char *de_filename;   /* Initially null if no filename has been
+                         generated yet */
+  char *de_title;      /* Title in UTF-8 (from EPG) */
+  char *de_desc;       /* Description in UTF-8 (from EPG) */
+
+  char *de_error;
+
+  dvr_entry_sched_state_t de_sched_state;
+  
+  gtimer_t de_timer;
+
+  /**
+   * Fields for recording
+   */
+
+  th_subscription_t *de_s;
+
+} dvr_entry_t;
+
+/**
+ * Prototypes
+ */
+void dvr_entry_create_by_event(event_t *e, const char *creator);
+
+void dvr_init(void);
+
+void dvr_rec_start(dvr_entry_t *de);
+
+dvr_entry_t *dvr_entry_find_by_id(int id);
+
+void dvr_entry_cancel(dvr_entry_t *de);
+
+
+/**
+ * Query interface
+ */
+typedef struct dvr_query_result {
+  dvr_entry_t **dqr_array;
+  int dqr_entries;
+  int dqr_alloced;
+} dvr_query_result_t;
+
+void dvr_query(dvr_query_result_t *dqr);
+void dvr_query_free(dvr_query_result_t *dqr);
+void dvr_query_sort(dvr_query_result_t *dqr);
+
+#endif /* DVR_H  */
diff --git a/dvr/dvr_db.c b/dvr/dvr_db.c
new file mode 100644 (file)
index 0000000..cb85e3a
--- /dev/null
@@ -0,0 +1,440 @@
+/*
+ *  Digital Video Recorder
+ *  Copyright (C) 2008 Andreas Öman
+ *
+ *  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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <pthread.h>
+#include <assert.h>
+#include <string.h>
+
+#include <libhts/htssettings.h>
+
+#include "tvhead.h"
+#include "dvr.h"
+#include "notify.h"
+
+char *dvr_storage;
+char *dvr_format;
+char *dvr_file_postfix;
+
+
+static int de_tally;
+
+static int dvr_retention_time = 86400 * 31;
+
+struct dvr_entry_list dvrentries;
+
+static void dvr_save(dvr_entry_t *de);
+
+static void dvr_timer_expire(void *aux);
+static void dvr_timer_start_recording(void *aux);
+
+
+
+/**
+ *
+ */
+static void
+dvrdb_changed(void)
+{
+  htsmsg_t *m = htsmsg_create();
+  htsmsg_add_u32(m, "reload", 1);
+  notify_by_msg("dvrdb", m);
+}
+
+
+/**
+ *
+ */
+static void
+dvr_entry_link(dvr_entry_t *de)
+{
+  time_t now, preamble;
+
+  LIST_INSERT_HEAD(&dvrentries, de, de_global_link);
+
+  time(&now);
+
+  preamble = de->de_start - 30;
+
+  if(now >= de->de_stop) {
+    de->de_sched_state = DVR_COMPLETED;
+    gtimer_arm_abs(&de->de_timer, dvr_timer_expire, de, 
+              de->de_stop + dvr_retention_time);
+
+  } else {
+    de->de_sched_state = DVR_SCHEDULED;
+
+    gtimer_arm_abs(&de->de_timer, dvr_timer_start_recording, de, preamble);
+  }
+}
+
+
+/**
+ *
+ */
+void
+dvr_entry_create_by_event(event_t *e, const char *creator)
+{
+  dvr_entry_t *de;
+  char tbuf[30];
+  struct tm tm;
+
+  if(e->e_channel == NULL || e->e_title == NULL)
+    return;
+
+  LIST_FOREACH(de, &e->e_channel->ch_dvrs, de_channel_link)
+    if(de->de_start == e->e_start)
+      return;
+
+  de = calloc(1, sizeof(dvr_entry_t));
+  de->de_id = ++de_tally;
+
+  de->de_channel = e->e_channel;
+  LIST_INSERT_HEAD(&de->de_channel->ch_dvrs, de, de_channel_link);
+
+  de->de_start   = e->e_start;
+  de->de_stop    = e->e_start + e->e_duration;
+
+  de->de_creator = strdup(creator);
+  de->de_title   = strdup(e->e_title);
+  de->de_desc    = e->e_desc  ? strdup(e->e_desc)  : NULL;
+
+  dvr_entry_link(de);
+
+  localtime_r(&de->de_start, &tm);
+  strftime(tbuf, sizeof(tbuf), "%c", &tm);
+
+  tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\" starting at %s, "
+        "scheduled for recording by \"%s\"",
+        de->de_title, de->de_channel->ch_name, tbuf, creator);
+        
+  dvrdb_changed();
+  dvr_save(de);
+}
+
+
+/**
+ *
+ */
+static void
+dvr_entry_destroy(dvr_entry_t *de)
+{
+  assert(de->de_s == NULL); /* Can't have any subscriptions running */
+  
+  hts_settings_remove("dvrdb/%d", de->de_id);
+
+  LIST_REMOVE(de, de_channel_link);
+
+  gtimer_disarm(&de->de_timer);
+
+  LIST_REMOVE(de, de_global_link);
+
+  free(de->de_creator);
+  free(de->de_title);
+  free(de->de_desc);
+
+  free(de);
+
+  dvrdb_changed();
+
+}
+
+
+/**
+ *
+ */
+static void
+dvr_db_load_one(htsmsg_t *c, int id)
+{
+  dvr_entry_t *de;
+  const char *s, *title, *creator;
+  channel_t *ch;
+  uint32_t start, stop;
+
+  if(htsmsg_get_u32(c, "start", &start))
+    return;
+  if(htsmsg_get_u32(c, "stop", &stop))
+    return;
+
+  if((s = htsmsg_get_str(c, "channel")) == NULL)
+    return;
+  if((ch = channel_find_by_name(s, 0)) == NULL)
+    return;
+
+  if((title = htsmsg_get_str(c, "title")) == NULL)
+    return;
+
+  if((creator = htsmsg_get_str(c, "creator")) == NULL)
+    return;
+
+  de = calloc(1, sizeof(dvr_entry_t));
+  de->de_id = id;
+
+  de_tally = MAX(id, de_tally);
+
+  de->de_channel = ch;
+  LIST_INSERT_HEAD(&de->de_channel->ch_dvrs, de, de_channel_link);
+
+  de->de_start   = start;
+  de->de_stop    = stop;
+  de->de_creator = strdup(creator);
+  de->de_title   = strdup(title);
+  
+  tvh_str_set(&de->de_desc,     htsmsg_get_str(c, "description"));
+  tvh_str_set(&de->de_filename, htsmsg_get_str(c, "filename"));
+  tvh_str_set(&de->de_error,    htsmsg_get_str(c, "error"));
+  dvr_entry_link(de);
+}
+
+
+/**
+ *
+ */
+static void
+dvr_db_load(void)
+{
+  htsmsg_t *l, *c;
+  htsmsg_field_t *f;
+
+  if((l = hts_settings_load("dvrdb")) != NULL) {
+    HTSMSG_FOREACH(f, l) {
+      if((c = htsmsg_get_msg_by_field(f)) == NULL)
+       continue;
+      dvr_db_load_one(c, atoi(f->hmf_name));
+    }
+  }
+}
+
+
+
+/**
+ *
+ */
+static void
+dvr_save(dvr_entry_t *de)
+{
+  htsmsg_t *m = htsmsg_create();
+
+  lock_assert(&global_lock);
+
+  htsmsg_add_str(m, "channel", de->de_channel->ch_name);
+  htsmsg_add_u32(m, "start", de->de_start);
+  htsmsg_add_u32(m, "stop", de->de_stop);
+  
+  htsmsg_add_str(m, "creator", de->de_creator);
+
+  if(de->de_filename != NULL)
+    htsmsg_add_str(m, "filename", de->de_filename);
+
+  htsmsg_add_str(m, "title", de->de_title);
+
+  if(de->de_desc != NULL)
+    htsmsg_add_str(m, "description", de->de_desc);
+
+
+  if(de->de_error != NULL)
+    htsmsg_add_str(m, "error", de->de_error);
+
+  hts_settings_save(m, "dvrdb/%d", de->de_id);
+  htsmsg_destroy(m);
+}
+
+
+/**
+ *
+ */
+static void
+dvr_timer_expire(void *aux)
+{
+  dvr_entry_t *de = aux;
+  dvr_entry_destroy(de);
+}
+
+
+/**
+ *
+ */
+static void
+dvr_stop_recording(dvr_entry_t *de, const char *errmsg)
+{
+  //  dvr_rec_stop(de);
+
+  de->de_sched_state = DVR_COMPLETED;
+  tvh_str_set(&de->de_error, errmsg);
+  errmsg = errmsg ?: "Recording completed OK";
+
+  tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\": %s",
+        de->de_title, de->de_channel->ch_name, errmsg);
+
+  dvrdb_changed();
+  gtimer_arm_abs(&de->de_timer, dvr_timer_expire, de, 
+                de->de_stop + dvr_retention_time);  
+}
+
+
+
+/**
+ *
+ */
+static void
+dvr_timer_stop_recording(void *aux)
+{
+  dvr_entry_t *de = aux;
+  dvr_stop_recording(de, NULL);
+}
+
+
+
+/**
+ *
+ */
+static void
+dvr_timer_start_recording(void *aux)
+{
+  dvr_entry_t *de = aux;
+
+  de->de_sched_state = DVR_RECORDING;
+
+  tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\" recorder starting",
+        de->de_title, de->de_channel->ch_name);
+
+  dvrdb_changed();
+
+  //  dvr_rec_start(de);
+
+  gtimer_arm_abs(&de->de_timer, dvr_timer_stop_recording, de, de->de_stop);
+}
+
+
+/**
+ *
+ */
+dvr_entry_t *
+dvr_entry_find_by_id(int id)
+{
+  dvr_entry_t *de;
+  LIST_FOREACH(de, &dvrentries, de_global_link)
+    if(de->de_id == id)
+      break;
+  return de;  
+}
+
+/**
+ *
+ */
+void
+dvr_entry_cancel(dvr_entry_t *de)
+{
+  switch(de->de_sched_state) {
+  case DVR_SCHEDULED:
+    dvr_entry_destroy(de);
+    break;
+
+  case DVR_RECORDING:
+    dvr_stop_recording(de, "Aborted by user");
+    break;
+
+  case DVR_COMPLETED:
+    dvr_entry_destroy(de);
+    break;
+  }
+}
+
+
+/**
+ *
+ */
+void
+dvr_init(void)
+{
+  dvr_storage      = strdup("/tmp");
+  dvr_format       = strdup("matroska");
+  dvr_file_postfix = strdup("mkv");
+
+  dvr_db_load();
+}
+
+
+/**
+ *
+ */
+static void
+dvr_query_add_entry(dvr_query_result_t *dqr, dvr_entry_t *de)
+{
+  if(dqr->dqr_entries == dqr->dqr_alloced) {
+    /* Need to alloc more space */
+
+    dqr->dqr_alloced = MAX(100, dqr->dqr_alloced * 2);
+    dqr->dqr_array = realloc(dqr->dqr_array, 
+                            dqr->dqr_alloced * sizeof(dvr_entry_t *));
+  }
+  dqr->dqr_array[dqr->dqr_entries++] = de;
+}
+
+
+/**
+ *
+ */
+void
+dvr_query(dvr_query_result_t *dqr)
+{
+  dvr_entry_t *de;
+
+  memset(dqr, 0, sizeof(dvr_query_result_t));
+
+  LIST_FOREACH(de, &dvrentries, de_global_link)
+    dvr_query_add_entry(dqr, de);
+}
+
+
+/**
+ *
+ */
+void
+dvr_query_free(dvr_query_result_t *dqr)
+{
+  free(dqr->dqr_array);
+}
+
+/**
+ * Sorting functions
+ */
+static int
+dvr_sort_start_descending(const void *A, const void *B)
+{
+  dvr_entry_t *a = *(dvr_entry_t **)A;
+  dvr_entry_t *b = *(dvr_entry_t **)B;
+  return b->de_start - a->de_start;
+}
+
+
+/**
+ *
+ */
+void
+dvr_query_sort(dvr_query_result_t *dqr)
+{
+  int (*sf)(const void *a, const void *b);
+
+  if(dqr->dqr_array == NULL)
+    return;
+
+  sf = dvr_sort_start_descending;
+  qsort(dqr->dqr_array, dqr->dqr_entries, sizeof(dvr_entry_t *), sf);
+}  
+
diff --git a/dvr/dvr_rec.c b/dvr/dvr_rec.c
new file mode 100644 (file)
index 0000000..6858714
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+ *  Digital Video Recorder
+ *  Copyright (C) 2008 Andreas Öman
+ *
+ *  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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <pthread.h>
+#include <assert.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <libavutil/avstring.h>
+
+#include "tvhead.h"
+#include "dvr.h"
+
+static void dvr_transport_available(dvr_entry_t *de);
+static void dvr_transport_unavailable(dvr_entry_t *de, const char *errmsg);
+
+/**
+ *
+ */
+static void
+dvr_subscription_callback(struct th_subscription *s,
+                         subscription_event_t event, void *opaque)
+{
+  dvr_entry_t *de = opaque;
+  const char *notifymsg = NULL;
+
+  switch(event) {
+  case SUBSCRIPTION_EVENT_INVALID:
+    abort();
+
+  case SUBSCRIPTION_TRANSPORT_RUN:
+    dvr_transport_available(de);
+    return;
+
+  case SUBSCRIPTION_NO_INPUT:
+    notifymsg = "No input detected";
+    break;
+
+  case SUBSCRIPTION_NO_DESCRAMBLER:
+    notifymsg = "No descrambler available";
+    break;
+
+  case SUBSCRIPTION_NO_ACCESS:
+    notifymsg = "Access denied";
+    break;
+
+  case SUBSCRIPTION_RAW_INPUT:
+    notifymsg = "Unable to reassemble packets from input";
+    break;
+
+  case SUBSCRIPTION_VALID_PACKETS:
+    return;
+
+  case SUBSCRIPTION_TRANSPORT_NOT_AVAILABLE:
+    notifymsg = "No transport available at the moment, automatic retry";
+    break;
+
+  case SUBSCRIPTION_TRANSPORT_LOST:
+    dvr_transport_unavailable(de, "Lost transport");
+    return;
+
+  case SUBSCRIPTION_DESTROYED:
+    dvr_transport_unavailable(de, NULL); /* Recording completed */
+    return;
+  }
+  if(notifymsg != NULL)
+    tvhlog(LOG_WARNING, "dvr", "\"%s\" on \"%s\": %s",
+          de->de_title, de->de_channel->ch_name, notifymsg);
+}
+
+
+/**
+ *
+ */
+void
+dvr_rec_start(dvr_entry_t *de)
+{
+  if(de->de_s != NULL)
+    return;
+
+  de->de_s = subscription_create_from_channel(de->de_channel, 1000, "pvr",
+                                             dvr_subscription_callback, de);
+  
+  
+}
+
+
+
+
+/**
+ * Replace any slash chars in a string with dash
+ */
+static void
+deslashify(char *s)
+{
+  int i, len = strlen(s);
+  for(i = 0; i < len; i++) if(s[i]  == '/')
+    s[i] = '-';
+}
+
+/**
+ * Filename generator
+ *
+ * - convert from utf8
+ * - avoid duplicate filenames
+ *
+ */
+static void
+pvr_generate_filename(dvr_entry_t *de)
+{
+  char fullname[1000];
+  int tally = 0;
+  struct stat st;
+  char *chname;
+  char *filename;
+
+  if(de->de_filename != NULL) {
+    free(de->de_filename);
+    de->de_filename = NULL;
+  }
+
+  filename = strdup(de->de_title);
+  deslashify(filename);
+
+  chname = strdup(de->de_channel->ch_name);
+  deslashify(chname);
+
+  snprintf(fullname, sizeof(fullname), "%s/%s-%s.%s",
+          dvr_storage, chname, filename, dvr_file_postfix);
+
+  while(1) {
+    if(stat(fullname, &st) == -1) {
+      tvhlog(LOG_DEBUG, "pvr", "File \"%s\" -- %s -- Using for recording",
+            fullname, strerror(errno));
+      break;
+    }
+
+    tvhlog(LOG_DEBUG, "pvr", "Overwrite protection, file \"%s\" exists", 
+          fullname);
+
+    tally++;
+
+    snprintf(fullname, sizeof(fullname), "%s/%s-%s-%d.%s",
+            dvr_storage, chname, filename, tally,dvr_file_postfix);
+  }
+
+  de->de_filename = strdup(fullname);
+
+  free(filename);
+  free(chname);
+}
+
+/**
+ *
+ */
+static void
+dvr_rec_fatal_error(dvr_entry_t *de, const char *fmt, ...)
+{
+  
+}
+/**
+ *
+ */
+static void
+dvr_transport_available(dvr_entry_t *de)
+{
+  AVOutputFormat *fmt;
+  AVFormatContext *fctx;
+  th_stream_t *st;
+  th_muxstream_t *tms;
+  AVCodecContext *ctx;
+  AVCodec *codec;
+  enum CodecID codec_id;
+  enum CodecType codec_type;
+  const char *codec_name;
+  char urlname[512];
+  int err;
+
+  th_transport_t *t = de->de_s->ths_transport;
+
+  pvr_generate_filename(de);
+
+  /* Find lavf format */
+
+  fmt = guess_format(dvr_format, NULL, NULL);
+  if(fmt == NULL) {
+    dvr_rec_fatal_error(de, "Unable to open file format \"%s\" for output",
+                       dvr_format);
+    return;
+  }
+
+  /* Init format context */
+
+  fctx = av_alloc_format_context();
+
+  av_strlcpy(fctx->title, de->de_title ?: "", 
+            sizeof(fctx->title));
+
+  av_strlcpy(fctx->comment, de->de_desc ?: "", 
+            sizeof(fctx->comment));
+
+  av_strlcpy(fctx->copyright, de->de_channel->ch_name, 
+            sizeof(fctx->copyright));
+
+  fctx->oformat = fmt;
+
+  /* Open output file */
+
+  snprintf(urlname, sizeof(urlname), "file:%s", de->de_filename);
+
+  if((err = url_fopen(&fctx->pb, urlname, URL_WRONLY)) < 0) {
+    av_free(fctx);
+    dvr_rec_fatal_error(de, "Unable to create output file \"%s\"",
+                       de->de_filename);
+    return;
+  }
+
+  av_set_parameters(fctx, NULL);
+
+
+  /**
+   * Setup each stream
+   */ 
+  LIST_FOREACH(st, &t->tht_streams, st_link) {
+    switch(st->st_type) {
+    default:
+      continue;
+    case HTSTV_MPEG2VIDEO:
+      codec_id   = CODEC_ID_MPEG2VIDEO;
+      codec_type = CODEC_TYPE_VIDEO;
+      codec_name = "mpeg2 video";
+      break;
+
+    case HTSTV_MPEG2AUDIO:
+      codec_id   = CODEC_ID_MP2;
+      codec_type = CODEC_TYPE_AUDIO;
+      codec_name = "mpeg2 audio";
+      break;
+
+    case HTSTV_AC3:
+      codec_id   = CODEC_ID_AC3;
+      codec_type = CODEC_TYPE_AUDIO;
+      codec_name = "AC3 audio";
+      break;
+
+    case HTSTV_H264:
+      codec_id   = CODEC_ID_H264;
+      codec_type = CODEC_TYPE_VIDEO;
+      codec_name = "h.264 video";
+      break;
+    }
+
+    codec = avcodec_find_decoder(codec_id);
+    if(codec == NULL) {
+      tvhlog(LOG_ERR, "dvr",
+            "%s - Cannot find codec for %s, ignoring stream", 
+            de->de_title, codec_name);
+      continue;
+    }
+
+    tms = calloc(1, sizeof(th_muxstream_t));
+    tms->tms_stream   = st;
+    tms->tms_index = fctx->nb_streams;
+
+    tms->tms_avstream = av_new_stream(fctx, fctx->nb_streams);
+
+    ctx = tms->tms_avstream->codec;
+    ctx->codec_id   = codec_id;
+    ctx->codec_type = codec_type;
+
+    if(avcodec_open(ctx, codec) < 0) {
+      tvhlog(LOG_ERR, "dvr",
+            "%s - Cannot open codec for %s, ignoring stream", 
+            de->de_title, codec_name);
+      free(ctx);
+      continue;
+    }
+
+    //    LIST_INSERT_HEAD(&tm->tm_streams, tms, tms_muxer_link0);
+    memcpy(tms->tms_avstream->language, tms->tms_stream->st_lang, 4);
+  }
+
+}
+
+/**
+ *
+ */
+static void
+dvr_transport_unavailable(dvr_entry_t *de, const char *errmsg)
+{
+
+
+}