static void dvr_rec_fatal_error(dvr_entry_t *de, const char *fmt, ...) { char msgbuf[256]; va_list ap; va_start(ap, fmt); vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); va_end(ap); tvhlog(LOG_ERR, "dvr", "Recording error: \"%s\": %s", de->de_filename ?: lang_str_get(de->de_title, NULL), msgbuf); }
int dvr_rec_subscribe(dvr_entry_t *de) { char buf[100]; int weight; profile_t *pro; profile_chain_t *prch; struct sockaddr sa; access_t *aa; uint32_t rec_count, net_count; int c1, c2; assert(de->de_s == NULL); assert(de->de_chain == NULL); if(de->de_pri >= 0 && de->de_pri < ARRAY_SIZE(prio2weight)) weight = prio2weight[de->de_pri]; else weight = 300; snprintf(buf, sizeof(buf), "DVR: %s", lang_str_get(de->de_title, NULL)); if (de->de_owner && de->de_owner[0] != '\0') aa = access_get_by_username(de->de_owner); else if (de->de_creator && de->de_creator[0] != '\0' && tcp_get_ip_from_str(de->de_creator, &sa) != NULL) aa = access_get_by_addr(&sa); else { tvherror(LS_DVR, "unable to find access (owner '%s', creator '%s')", de->de_owner, de->de_creator); return -EPERM; } if (aa->aa_conn_limit || aa->aa_conn_limit_dvr) { rec_count = dvr_usage_count(aa); net_count = aa->aa_conn_limit ? tcp_connection_count(aa) : 0; /* the rule is: allow if one condition is OK */ c1 = aa->aa_conn_limit ? rec_count + net_count >= aa->aa_conn_limit : -1; c2 = aa->aa_conn_limit_dvr ? rec_count >= aa->aa_conn_limit_dvr : -1; if (c1 && c2) { tvherror(LS_DVR, "multiple connections are not allowed for user '%s' from '%s' " "(limit %u, dvr limit %u, active DVR %u, streaming %u)", aa->aa_username ?: "", aa->aa_representative ?: "", aa->aa_conn_limit, aa->aa_conn_limit_dvr, rec_count, net_count); access_destroy(aa); return -EOVERFLOW; }
void dvr_rec_subscribe(dvr_entry_t *de) { char buf[100]; int weight; profile_t *pro; profile_chain_t *prch; assert(de->de_s == NULL); assert(de->de_chain == NULL); if(de->de_pri < ARRAY_SIZE(prio2weight)) weight = prio2weight[de->de_pri]; else weight = 300; snprintf(buf, sizeof(buf), "DVR: %s", lang_str_get(de->de_title, NULL)); pro = de->de_config->dvr_profile; prch = malloc(sizeof(*prch)); profile_chain_init(prch, pro, de->de_channel); if (profile_chain_open(prch, &de->de_config->dvr_muxcnf, 0, 0)) { tvherror("dvr", "unable to create new channel streaming chain for '%s'", channel_get_name(de->de_channel)); return; } de->de_s = subscription_create_from_channel(prch, weight, buf, prch->prch_flags, NULL, NULL, NULL); if (de->de_s == NULL) { tvherror("dvr", "unable to create new channel subcription for '%s'", channel_get_name(de->de_channel)); profile_chain_close(prch); free(prch); de->de_chain = NULL; return; } de->de_chain = prch; tvhthread_create(&de->de_thread, NULL, dvr_thread, de); }
int dvr_rec_subscribe(dvr_entry_t *de) { char buf[100]; int weight; profile_t *pro; profile_chain_t *prch; struct sockaddr sa; access_t *aa; uint32_t rec_count, net_count; assert(de->de_s == NULL); assert(de->de_chain == NULL); if(de->de_pri > 0 && de->de_pri < ARRAY_SIZE(prio2weight)) weight = prio2weight[de->de_pri]; else weight = 300; snprintf(buf, sizeof(buf), "DVR: %s", lang_str_get(de->de_title, NULL)); if (de->de_owner && de->de_owner[0] != '\0') aa = access_get_by_username(de->de_owner); else if (de->de_creator && de->de_creator[0] != '\0' && tcp_get_ip_from_str(de->de_creator, &sa) != NULL) aa = access_get_by_addr(&sa); else { tvherror("dvr", "unable to find access"); return -1; } if (aa->aa_conn_limit) { rec_count = dvr_usage_count(aa); net_count = tcp_connection_count(aa); if (rec_count + net_count >= aa->aa_conn_limit) { tvherror("dvr", "multiple connections are not allowed for user '%s' from '%s' " "(limit %u, active streaming %u, active DVR %u)", aa->aa_username ?: "", aa->aa_representative ?: "", aa->aa_conn_limit, rec_count, net_count); return -1; }
void dvr_rec_subscribe(dvr_entry_t *de) { char buf[100]; int weight; streaming_target_t *st; int flags; assert(de->de_s == NULL); if(de->de_pri < 5) weight = prio2weight[de->de_pri]; else weight = 300; snprintf(buf, sizeof(buf), "DVR: %s", lang_str_get(de->de_title, NULL)); if(de->de_mc == MC_PASS) { streaming_queue_init(&de->de_sq, SMT_PACKET); de->de_gh = NULL; de->de_tsfix = NULL; st = &de->de_sq.sq_st; flags = SUBSCRIPTION_RAW_MPEGTS; } else { streaming_queue_init(&de->de_sq, 0); de->de_gh = globalheaders_create(&de->de_sq.sq_st); st = de->de_tsfix = tsfix_create(de->de_gh); tsfix_set_start_time(de->de_tsfix, de->de_start - (60 * de->de_start_extra)); flags = 0; } de->de_s = subscription_create_from_channel(de->de_channel, weight, buf, st, flags, NULL, NULL, NULL); pthread_create(&de->de_thread, NULL, dvr_thread, de); }
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; }
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) { // Error during recording dvr_rec_set_state(de, DVR_RS_ERROR, sm->sm_code); tvhlog(LOG_ERR, "dvr", "Recording stopped: \"%s\": %s", de->de_filename ?: lang_str_get(de->de_title, NULL), streaming_code2txt(sm->sm_code)); dvr_thread_epilog(de); started = 0; } break; case SMT_SERVICE_STATUS: if(sm->sm_code & TSS_PACKETS) { } else if(sm->sm_code & (TSS_GRACEPERIOD | TSS_ERRORS)) { int code = SM_CODE_UNDEFINED_ERROR;
/** * Filename generator * * - convert from utf8 * - avoid duplicate filenames * */ static int pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss) { char fullname[PATH_MAX]; char path[PATH_MAX]; int tally = 0; struct stat st; char *filename, *s; struct tm tm; dvr_config_t *cfg; if (de == NULL) return -1; cfg = de->de_config; strncpy(path, cfg->dvr_storage, sizeof(path)); path[sizeof(path)-1] = '\0'; /* Remove trailing slash */ if (path[strlen(path)-1] == '/') path[strlen(path)-1] = '\0'; /* Append per-day directory */ if (cfg->dvr_dir_per_day) { localtime_r(&de->de_start, &tm); strftime(fullname, sizeof(fullname), "%F", &tm); s = cleanup_filename(fullname, cfg); if (s == NULL) return -1; snprintf(path + strlen(path), sizeof(path) - strlen(path), "/%s", s); free(s); } /* Append per-channel directory */ if (cfg->dvr_channel_dir) { char *chname = strdup(DVR_CH_NAME(de)); s = cleanup_filename(chname, cfg); free(chname); if (s == NULL) return -1; snprintf(path + strlen(path), sizeof(path) - strlen(path), "/%s", s); free(s); } // TODO: per-brand, per-season /* Append per-title directory */ if (cfg->dvr_title_dir) { char *title = strdup(lang_str_get(de->de_title, NULL)); s = cleanup_filename(title, cfg); free(title); if (s == NULL) return -1; snprintf(path + strlen(path), sizeof(path) - strlen(path), "/%s", s); free(s); } if (makedirs(path, cfg->dvr_muxcnf.m_directory_permissions) != 0) return -1; /* Construct final name */ dvr_make_title(fullname, sizeof(fullname), de); filename = cleanup_filename(fullname, cfg); if (filename == NULL) return -1; snprintf(fullname, sizeof(fullname), "%s/%s.%s", path, filename, muxer_suffix(de->de_chain->prch_muxer, 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_chain->prch_muxer, ss)); } free(filename); tvh_str_set(&de->de_filename, fullname); return 0; }