void dvr_rec_subscribe(dvr_entry_t *de) { char buf[100]; int weight; dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name); int matroska = strcmp(cfg->dvr_format, "matroska") == 0; assert(de->de_s == NULL); snprintf(buf, sizeof(buf), "DVR: %s", de->de_title); streaming_queue_init(&de->de_sq, matroska ? 0 : SMT_TO_MASK(SMT_PACKET)); pthread_create(&de->de_thread, NULL, dvr_thread, de); if(de->de_pri < 5) weight = prio2weight[de->de_pri]; else weight = 300; tvhlog(LOG_DEBUG, "dvr", "Use %s container", cfg->dvr_format); if(matroska) { de->de_gh = globalheaders_create(&de->de_sq.sq_st); de->de_tsfix = tsfix_create(de->de_gh); de->de_s = subscription_create_from_channel(de->de_channel, weight, buf, de->de_tsfix, 0); // } else if { (Other containers) // mpegts works like rawts } else { de->de_s = subscription_create_from_channel(de->de_channel, weight, buf, &de->de_sq.sq_st, SUBSCRIPTION_RAW_MPEGTS); } }
static void dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss) { const source_info_t *si = &ss->ss_si; const streaming_start_component_t *ssc; int i; dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name); if (de->de_mkts) { /* reconfigured */ tvhlog(LOG_INFO, "dvr", "%s reconfigured service \"%s\"", de->de_ititle, si->si_service ?: "<N/A>"); goto reconfigured; }
void dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode) { dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name); assert(de->de_s != NULL); subscription_unsubscribe(de->de_s); streaming_target_deliver(&de->de_sq.sq_st, streaming_msg_create(SMT_EXIT)); pthread_join(de->de_thread, NULL); de->de_s = NULL; if(strcmp(cfg->dvr_format, "matroska") == 0) { tsfix_destroy(de->de_tsfix); globalheaders_destroy(de->de_gh); // } else if { (Other containers) // mpegts works like rawts } de->de_last_error = stopcode; }
static void * dvr_thread(void *aux) { dvr_entry_t *de = aux; dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name); streaming_queue_t *sq = &de->de_sq; streaming_message_t *sm; th_pkt_t *pkt; int run = 1; int started = 0; int comm_skip = (cfg->dvr_flags & DVR_SKIP_COMMERCIALS); int commercial = COMMERCIAL_UNKNOWN; pthread_mutex_lock(&sq->sq_mutex); while(run) { sm = TAILQ_FIRST(&sq->sq_queue); if(sm == NULL) { pthread_cond_wait(&sq->sq_cond, &sq->sq_mutex); continue; } TAILQ_REMOVE(&sq->sq_queue, sm, sm_link); pthread_mutex_unlock(&sq->sq_mutex); switch(sm->sm_type) { case SMT_PACKET: pkt = sm->sm_data; if(pkt->pkt_commercial == COMMERCIAL_YES) dvr_rec_set_state(de, DVR_RS_COMMERCIAL, 0); else dvr_rec_set_state(de, DVR_RS_RUNNING, 0); if(pkt->pkt_commercial == COMMERCIAL_YES && comm_skip) break; if(commercial != pkt->pkt_commercial) muxer_add_marker(de->de_mux); commercial = pkt->pkt_commercial; if(started) { muxer_write_pkt(de->de_mux, sm->sm_type, sm->sm_data); sm->sm_data = NULL; } break; case SMT_MPEGTS: if(started) { dvr_rec_set_state(de, DVR_RS_RUNNING, 0); muxer_write_pkt(de->de_mux, sm->sm_type, sm->sm_data); sm->sm_data = NULL; } break; case SMT_START: if(started && muxer_reconfigure(de->de_mux, sm->sm_data) < 0) { tvhlog(LOG_WARNING, "dvr", "Unable to reconfigure \"%s\"", de->de_filename ?: lang_str_get(de->de_title, NULL)); // Try to restart the recording if the muxer doesn't // support reconfiguration of the streams. dvr_thread_epilog(de); started = 0; } if(!started) { pthread_mutex_lock(&global_lock); dvr_rec_set_state(de, DVR_RS_WAIT_PROGRAM_START, 0); if(dvr_rec_start(de, sm->sm_data) == 0) { started = 1; dvr_entry_notify(de); htsp_dvr_entry_update(de); dvr_entry_save(de); } pthread_mutex_unlock(&global_lock); } break; case SMT_STOP: if(sm->sm_code == SM_CODE_SOURCE_RECONFIGURED) { // Subscription is restarting, wait for SMT_START } else if(sm->sm_code == 0) { // Recording is completed de->de_last_error = 0; tvhlog(LOG_INFO, "dvr", "Recording completed: \"%s\"", de->de_filename ?: lang_str_get(de->de_title, NULL)); dvr_thread_epilog(de); started = 0; }else if(de->de_last_error != sm->sm_code) {
static int dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss) { const source_info_t *si = &ss->ss_si; const streaming_start_component_t *ssc; int i; dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name); de->de_mux = muxer_create(de->de_mc); if(!de->de_mux) { dvr_rec_fatal_error(de, "Unable to create muxer"); return -1; } if(pvr_generate_filename(de, ss) != 0) { dvr_rec_fatal_error(de, "Unable to create directories"); return -1; } if(muxer_open_file(de->de_mux, de->de_filename)) { dvr_rec_fatal_error(de, "Unable to open file"); return -1; } if(muxer_init(de->de_mux, ss, lang_str_get(de->de_title, NULL))) { dvr_rec_fatal_error(de, "Unable to init file"); return -1; } if(cfg->dvr_flags & DVR_TAG_FILES && de->de_bcast) { if(muxer_write_meta(de->de_mux, de->de_bcast)) { dvr_rec_fatal_error(de, "Unable to write meta data"); return -1; } } tvhlog(LOG_INFO, "dvr", "%s from " "adapter: \"%s\", " "network: \"%s\", mux: \"%s\", provider: \"%s\", " "service: \"%s\"", de->de_filename ?: lang_str_get(de->de_title, NULL), si->si_adapter ?: "<N/A>", si->si_network ?: "<N/A>", si->si_mux ?: "<N/A>", si->si_provider ?: "<N/A>", si->si_service ?: "<N/A>"); tvhlog(LOG_INFO, "dvr", " # %-16s %-4s %-10s %-12s %-11s %-8s", "type", "lang", "resolution", "aspect ratio", "sample rate", "channels"); for(i = 0; i < ss->ss_num_components; i++) { ssc = &ss->ss_components[i]; char res[11]; char asp[6]; char sr[6]; char ch[7]; if(SCT_ISAUDIO(ssc->ssc_type)) { if(ssc->ssc_sri) snprintf(sr, sizeof(sr), "%d", sri_to_rate(ssc->ssc_sri)); else strcpy(sr, "?"); if(ssc->ssc_channels == 6) snprintf(ch, sizeof(ch), "5.1"); else if(ssc->ssc_channels == 0) strcpy(ch, "?"); else snprintf(ch, sizeof(ch), "%d", ssc->ssc_channels); } else { sr[0] = 0; ch[0] = 0; } if(SCT_ISVIDEO(ssc->ssc_type)) { if(ssc->ssc_width && ssc->ssc_height) snprintf(res, sizeof(res), "%dx%d", ssc->ssc_width, ssc->ssc_height); else strcpy(res, "?"); } else { res[0] = 0; } if(SCT_ISVIDEO(ssc->ssc_type)) { if(ssc->ssc_aspect_num && ssc->ssc_aspect_den) snprintf(asp, sizeof(asp), "%d:%d", ssc->ssc_aspect_num, ssc->ssc_aspect_den); else strcpy(asp, "?"); } else { asp[0] = 0; } tvhlog(LOG_INFO, "dvr", "%2d %-16s %-4s %-10s %-12s %-11s %-8s %s", ssc->ssc_index, streaming_component_type2txt(ssc->ssc_type), ssc->ssc_lang, res, asp, sr, ch, ssc->ssc_disabled ? "<disabled, no valid input>" : ""); } return 0; }
/** * Filename generator * * - convert from utf8 * - avoid duplicate filenames * */ static int pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss) { char fullname[1000]; char path[500]; int tally = 0; struct stat st; char filename[1000]; struct tm tm; dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name); dvr_make_title(filename, sizeof(filename), de); cleanupfilename(filename,cfg->dvr_flags); snprintf(path, sizeof(path), "%s", cfg->dvr_storage); /* Remove trailing slash */ if (path[strlen(path)-1] == '/') path[strlen(path)-1] = '\0'; /* Append per-day directory */ if(cfg->dvr_flags & DVR_DIR_PER_DAY) { localtime_r(&de->de_start, &tm); strftime(fullname, sizeof(fullname), "%F", &tm); cleanupfilename(fullname,cfg->dvr_flags); snprintf(path + strlen(path), sizeof(path) - strlen(path), "/%s", fullname); } /* Append per-channel directory */ if(cfg->dvr_flags & DVR_DIR_PER_CHANNEL) { char *chname = strdup(DVR_CH_NAME(de)); cleanupfilename(chname,cfg->dvr_flags); snprintf(path + strlen(path), sizeof(path) - strlen(path), "/%s", chname); free(chname); } // TODO: per-brand, per-season /* Append per-title directory */ if(cfg->dvr_flags & DVR_DIR_PER_TITLE) { char *title = strdup(lang_str_get(de->de_title, NULL)); cleanupfilename(title,cfg->dvr_flags); snprintf(path + strlen(path), sizeof(path) - strlen(path), "/%s", title); free(title); } /* */ if(makedirs(path, 0777) != 0) { return -1; } /* Construct final name */ snprintf(fullname, sizeof(fullname), "%s/%s.%s", path, filename, muxer_suffix(de->de_mux, ss)); while(1) { if(stat(fullname, &st) == -1) { tvhlog(LOG_DEBUG, "dvr", "File \"%s\" -- %s -- Using for recording", fullname, strerror(errno)); break; } tvhlog(LOG_DEBUG, "dvr", "Overwrite protection, file \"%s\" exists", fullname); tally++; snprintf(fullname, sizeof(fullname), "%s/%s-%d.%s", path, filename, tally, muxer_suffix(de->de_mux, ss)); } tvh_str_set(&de->de_filename, fullname); return 0; }
// if configured if(dae->dae_season) if (!e->episode->season || dae->dae_season != e->episode->season) return 0; if(dae->dae_brand) if (!e->episode->brand || dae->dae_brand != e->episode->brand) return 0; if(dae->dae_title != NULL && dae->dae_title[0] != '\0') { lang_str_ele_t *ls; if(!e->episode->title) return 0; RB_FOREACH(ls, e->episode->title, link) if (!regexec(&dae->dae_title_preg, ls->str, 0, NULL, 0)) break; if (!ls) return 0; } // Note: ignore channel test if we allow quality unlocking cfg = dvr_config_find_by_name_default(dae->dae_config_name); if (cfg->dvr_sl_quality_lock) if(dae->dae_channel != NULL && dae->dae_channel != e->channel) return 0; if(dae->dae_channel_tag != NULL) { LIST_FOREACH(ctm, &dae->dae_channel_tag->ct_ctms, ctm_tag_link) if(ctm->ctm_channel == e->channel) break; if(ctm == NULL) return 0; } if(dae->dae_content_type.code != 0) { if (!epg_genre_list_contains(&e->episode->genre, &dae->dae_content_type, 1))
/** * Filename generator * * - convert from utf8 * - avoid duplicate filenames * */ static int pvr_generate_filename(dvr_entry_t *de) { char fullname[1000]; char path[500]; int tally = 0; struct stat st; char *filename; struct tm tm; dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name); filename = strdup(de->de_ititle); cleanupfilename(filename,cfg->dvr_flags); snprintf(path, sizeof(path), "%s", cfg->dvr_storage); /* Append per-day directory */ if(cfg->dvr_flags & DVR_DIR_PER_DAY) { localtime_r(&de->de_start, &tm); strftime(fullname, sizeof(fullname), "%F", &tm); cleanupfilename(fullname,cfg->dvr_flags); snprintf(path + strlen(path), sizeof(path) - strlen(path), "/%s", fullname); } /* Append per-channel directory */ if(cfg->dvr_flags & DVR_DIR_PER_CHANNEL) { char *chname = strdup(de->de_channel->ch_name); cleanupfilename(chname,cfg->dvr_flags); snprintf(path + strlen(path), sizeof(path) - strlen(path), "/%s", chname); free(chname); } /* Append per-title directory */ if(cfg->dvr_flags & DVR_DIR_PER_TITLE) { char *title = strdup(de->de_title); cleanupfilename(title,cfg->dvr_flags); snprintf(path + strlen(path), sizeof(path) - strlen(path), "/%s", title); free(title); } /* */ if(makedirs(path) != 0) { free(filename); return -1; } /* Construct final name */ snprintf(fullname, sizeof(fullname), "%s/%s.%s", path, filename, cfg->dvr_file_postfix); while(1) { if(stat(fullname, &st) == -1) { tvhlog(LOG_DEBUG, "dvr", "File \"%s\" -- %s -- Using for recording", fullname, strerror(errno)); break; } tvhlog(LOG_DEBUG, "dvr", "Overwrite protection, file \"%s\" exists", fullname); tally++; snprintf(fullname, sizeof(fullname), "%s/%s-%d.%s", path, filename, tally, cfg->dvr_file_postfix); } tvh_str_set(&de->de_filename, fullname); free(filename); return 0; }
/** * Open a dump file which we write the entire mux output to */ static void dvb_adapter_open_dump_file(th_dvb_adapter_t *tda) { struct dmx_pes_filter_params dmx_param; char fullname[1000]; char path[500]; const char *fname = tda->tda_mux_current->tdmi_identifier; int fd = tvh_open(tda->tda_demux_path, O_RDWR, 0); if(fd == -1) return; memset(&dmx_param, 0, sizeof(dmx_param)); dmx_param.pid = 0x2000; dmx_param.input = DMX_IN_FRONTEND; dmx_param.output = DMX_OUT_TS_TAP; dmx_param.pes_type = DMX_PES_OTHER; dmx_param.flags = DMX_IMMEDIATE_START; if(ioctl(fd, DMX_SET_PES_FILTER, &dmx_param)) { tvhlog(LOG_ERR, "dvb", "\"%s\" unable to configure demuxer \"%s\" for all PIDs -- %s", fname, tda->tda_demux_path, strerror(errno)); close(fd); return; } snprintf(path, sizeof(path), "%s/muxdumps", dvr_config_find_by_name_default("")->dvr_storage); if(mkdir(path, 0777) && errno != EEXIST) { tvhlog(LOG_ERR, "dvb", "\"%s\" unable to create mux dump dir %s -- %s", fname, path, strerror(errno)); close(fd); return; } int attempt = 1; while(1) { struct stat st; snprintf(fullname, sizeof(fullname), "%s/%s.dump%d.ts", path, fname, attempt); if(stat(fullname, &st) == -1) break; attempt++; } int f = open(fullname, O_CREAT | O_TRUNC | O_WRONLY, 0777); if(f == -1) { tvhlog(LOG_ERR, "dvb", "\"%s\" unable to create mux dump file %s -- %s", fname, fullname, strerror(errno)); close(fd); return; } tvhlog(LOG_WARNING, "dvb", "\"%s\" writing to mux dump file %s", fname, fullname); tda->tda_allpids_dmx_fd = fd; tda->tda_dump_fd = f; }