From 4c4201ee3e15f4df8dc83ab62732272ddd38bb0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20=C3=96man?= Date: Wed, 17 Sep 2008 21:08:17 +0000 Subject: [PATCH] Add missing DVR files --- dvr/dvr.h | 99 ++++++++++++ dvr/dvr_db.c | 440 ++++++++++++++++++++++++++++++++++++++++++++++++++ dvr/dvr_rec.c | 308 +++++++++++++++++++++++++++++++++++ 3 files changed, 847 insertions(+) create mode 100644 dvr/dvr.h create mode 100644 dvr/dvr_db.c create mode 100644 dvr/dvr_rec.c diff --git a/dvr/dvr.h b/dvr/dvr.h new file mode 100644 index 00000000..e8cbe21e --- /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 . + */ + +#ifndef DVR_H +#define DVR_H + +#include +#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 index 00000000..cb85e3a5 --- /dev/null +++ b/dvr/dvr_db.c @@ -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 . + */ + +#include +#include +#include + +#include + +#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 index 00000000..68587141 --- /dev/null +++ b/dvr/dvr_rec.c @@ -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 . + */ + +#include +#include +#include +#include + +#include + +#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) +{ + + +}