Commit c1412599 authored by Laurent Aimar's avatar Laurent Aimar

Added recording on the fly through sout.

parent d4e0e64e
...@@ -29,9 +29,9 @@ ...@@ -29,9 +29,9 @@
# include "config.h" # include "config.h"
#endif #endif
#include <vlc_common.h>
#include <stdio.h> #include <stdio.h>
#include <assert.h>
#include <vlc_common.h>
#include <vlc_input.h> #include <vlc_input.h>
#include <vlc_es_out.h> #include <vlc_es_out.h>
...@@ -40,6 +40,8 @@ ...@@ -40,6 +40,8 @@
#include "input_internal.h" #include "input_internal.h"
#include "../stream_output/stream_output.h"
#include <vlc_iso_lang.h> #include <vlc_iso_lang.h>
/* FIXME we should find a better way than including that */ /* FIXME we should find a better way than including that */
#include "../text/iso-639_def.h" #include "../text/iso-639_def.h"
...@@ -83,6 +85,7 @@ struct es_out_id_t ...@@ -83,6 +85,7 @@ struct es_out_id_t
char *psz_language_code; char *psz_language_code;
decoder_t *p_dec; decoder_t *p_dec;
decoder_t *p_dec_record;
/* Fields for Video with CC */ /* Fields for Video with CC */
bool pb_cc_present[4]; bool pb_cc_present[4];
...@@ -134,6 +137,9 @@ struct es_out_sys_t ...@@ -134,6 +137,9 @@ struct es_out_sys_t
/* Rate used to rescale ES ts */ /* Rate used to rescale ES ts */
int i_rate; int i_rate;
/* Record */
sout_instance_t *p_sout_record;
}; };
static es_out_id_t *EsOutAdd ( es_out_t *, es_format_t * ); static es_out_id_t *EsOutAdd ( es_out_t *, es_format_t * );
...@@ -266,6 +272,8 @@ es_out_t *input_EsOutNew( input_thread_t *p_input, int i_rate ) ...@@ -266,6 +272,8 @@ es_out_t *input_EsOutNew( input_thread_t *p_input, int i_rate )
p_sys->i_rate = i_rate; p_sys->i_rate = i_rate;
p_sys->p_sout_record = NULL;
return out; return out;
} }
...@@ -277,12 +285,14 @@ void input_EsOutDelete( es_out_t *out ) ...@@ -277,12 +285,14 @@ void input_EsOutDelete( es_out_t *out )
es_out_sys_t *p_sys = out->p_sys; es_out_sys_t *p_sys = out->p_sys;
int i; int i;
if( p_sys->p_sout_record )
input_EsOutSetRecord( out, false );
for( i = 0; i < p_sys->i_es; i++ ) for( i = 0; i < p_sys->i_es; i++ )
{ {
if( p_sys->es[i]->p_dec ) if( p_sys->es[i]->p_dec )
{
input_DecoderDelete( p_sys->es[i]->p_dec ); input_DecoderDelete( p_sys->es[i]->p_dec );
}
free( p_sys->es[i]->psz_language ); free( p_sys->es[i]->psz_language );
free( p_sys->es[i]->psz_language_code ); free( p_sys->es[i]->psz_language_code );
es_format_Clean( &p_sys->es[i]->fmt ); es_format_Clean( &p_sys->es[i]->fmt );
...@@ -301,7 +311,6 @@ void input_EsOutDelete( es_out_t *out ) ...@@ -301,7 +311,6 @@ void input_EsOutDelete( es_out_t *out )
free( p_sys->ppsz_sub_language[i] ); free( p_sys->ppsz_sub_language[i] );
free( p_sys->ppsz_sub_language ); free( p_sys->ppsz_sub_language );
} }
free( p_sys->es ); free( p_sys->es );
/* FIXME duplicate work EsOutProgramDel (but we cannot use it) add a EsOutProgramClean ? */ /* FIXME duplicate work EsOutProgramDel (but we cannot use it) add a EsOutProgramClean ? */
...@@ -351,7 +360,11 @@ static void EsOutDiscontinuity( es_out_t *out, bool b_flush, bool b_audio ) ...@@ -351,7 +360,11 @@ static void EsOutDiscontinuity( es_out_t *out, bool b_flush, bool b_audio )
/* Send a dummy block to let decoder know that /* Send a dummy block to let decoder know that
* there is a discontinuity */ * there is a discontinuity */
if( es->p_dec && ( !b_audio || es->fmt.i_cat == AUDIO_ES ) ) if( es->p_dec && ( !b_audio || es->fmt.i_cat == AUDIO_ES ) )
{
input_DecoderDiscontinuity( es->p_dec, b_flush ); input_DecoderDiscontinuity( es->p_dec, b_flush );
if( es->p_dec_record )
input_DecoderDiscontinuity( es->p_dec_record, b_flush );
}
} }
} }
void input_EsOutChangeRate( es_out_t *out, int i_rate ) void input_EsOutChangeRate( es_out_t *out, int i_rate )
...@@ -366,6 +379,73 @@ void input_EsOutChangeRate( es_out_t *out, int i_rate ) ...@@ -366,6 +379,73 @@ void input_EsOutChangeRate( es_out_t *out, int i_rate )
input_ClockSetRate( &p_sys->pgrm[i]->clock, i_rate ); input_ClockSetRate( &p_sys->pgrm[i]->clock, i_rate );
} }
int input_EsOutSetRecord( es_out_t *out, bool b_record )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
assert( ( b_record && !p_sys->p_sout_record ) || ( !b_record && p_sys->p_sout_record ) );
if( b_record )
{
char *psz_path = var_CreateGetString( p_input, "input-record-path" );
if( !psz_path || *psz_path == '\0' )
{
free( psz_path );
psz_path = strdup( config_GetHomeDir() );
}
char *psz_sout = NULL; // TODO conf
if( !psz_sout && psz_path )
{
char *psz_file = input_CreateFilename( VLC_OBJECT(p_input), psz_path, INPUT_RECORD_PREFIX, NULL );
if( psz_file )
{
if( asprintf( &psz_sout, "#record{dst-prefix='%s'}", psz_file ) < 0 )
psz_sout = NULL;
free( psz_file );
}
}
free( psz_path );
if( !psz_sout )
return VLC_EGENERIC;
p_sys->p_sout_record = sout_NewInstance( p_input, psz_sout );
free( psz_sout );
if( !p_sys->p_sout_record )
return VLC_EGENERIC;
for( int i = 0; i < p_sys->i_es; i++ )
{
es_out_id_t *p_es = p_sys->es[i];
if( !p_es->p_dec || p_es->p_master )
continue;
p_es->p_dec_record = input_DecoderNew( p_input, &p_es->fmt, p_sys->p_sout_record );
}
}
else
{
for( int i = 0; i < p_sys->i_es; i++ )
{
es_out_id_t *p_es = p_sys->es[i];
if( !p_es->p_dec_record )
continue;
input_DecoderDelete( p_es->p_dec_record );
p_es->p_dec_record = NULL;
}
sout_DeleteInstance( p_sys->p_sout_record );
p_sys->p_sout_record = NULL;
}
return VLC_SUCCESS;
}
void input_EsOutSetDelay( es_out_t *out, int i_cat, int64_t i_delay ) void input_EsOutSetDelay( es_out_t *out, int i_cat, int64_t i_delay )
{ {
es_out_sys_t *p_sys = out->p_sys; es_out_sys_t *p_sys = out->p_sys;
...@@ -411,6 +491,8 @@ bool input_EsOutDecodersEmpty( es_out_t *out ) ...@@ -411,6 +491,8 @@ bool input_EsOutDecodersEmpty( es_out_t *out )
if( es->p_dec && !input_DecoderEmpty( es->p_dec ) ) if( es->p_dec && !input_DecoderEmpty( es->p_dec ) )
return false; return false;
if( es->p_dec_record && !input_DecoderEmpty( es->p_dec_record ) )
return false;
} }
return true; return true;
} }
...@@ -994,9 +1076,10 @@ static es_out_id_t *EsOutAdd( es_out_t *out, es_format_t *fmt ) ...@@ -994,9 +1076,10 @@ static es_out_id_t *EsOutAdd( es_out_t *out, es_format_t *fmt )
es->psz_language = LanguageGetName( fmt->psz_language ); /* remember so we only need to do it once */ es->psz_language = LanguageGetName( fmt->psz_language ); /* remember so we only need to do it once */
es->psz_language_code = LanguageGetCode( fmt->psz_language ); es->psz_language_code = LanguageGetCode( fmt->psz_language );
es->p_dec = NULL; es->p_dec = NULL;
es->p_dec_record = NULL;
for( i = 0; i < 4; i++ ) for( i = 0; i < 4; i++ )
es->pb_cc_present[i] = false; es->pb_cc_present[i] = false;
es->p_master = false; es->p_master = NULL;
if( es->p_pgrm == p_sys->p_pgrm ) if( es->p_pgrm == p_sys->p_pgrm )
EsOutESVarUpdate( out, es, false ); EsOutESVarUpdate( out, es, false );
...@@ -1043,6 +1126,32 @@ static bool EsIsSelected( es_out_id_t *es ) ...@@ -1043,6 +1126,32 @@ static bool EsIsSelected( es_out_id_t *es )
return es->p_dec != NULL; return es->p_dec != NULL;
} }
} }
static void EsCreateDecoder( es_out_t *out, es_out_id_t *p_es )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
p_es->p_dec = input_DecoderNew( p_input, &p_es->fmt, p_input->p->p_sout );
if( p_es->p_dec && !p_es->p_master && p_sys->p_sout_record )
p_es->p_dec_record = input_DecoderNew( p_input, &p_es->fmt, p_sys->p_sout_record );
}
static void EsDestroyDecoder( es_out_t *out, es_out_id_t *p_es )
{
es_out_sys_t *p_sys = out->p_sys;
if( !p_es->p_dec )
return;
input_DecoderDelete( p_es->p_dec );
p_es->p_dec = NULL;
if( p_es->p_dec_record )
{
input_DecoderDelete( p_es->p_dec_record );
p_es->p_dec_record = NULL;
}
}
static void EsSelect( es_out_t *out, es_out_id_t *es ) static void EsSelect( es_out_t *out, es_out_id_t *es )
{ {
es_out_sys_t *p_sys = out->p_sys; es_out_sys_t *p_sys = out->p_sys;
...@@ -1099,7 +1208,8 @@ static void EsSelect( es_out_t *out, es_out_id_t *es ) ...@@ -1099,7 +1208,8 @@ static void EsSelect( es_out_t *out, es_out_id_t *es )
} }
es->i_preroll_end = -1; es->i_preroll_end = -1;
es->p_dec = input_DecoderNew( p_input, &es->fmt, false ); EsCreateDecoder( out, es );
if( es->p_dec == NULL || es->p_pgrm != p_sys->p_pgrm ) if( es->p_dec == NULL || es->p_pgrm != p_sys->p_pgrm )
return; return;
} }
...@@ -1163,8 +1273,7 @@ static void EsUnselect( es_out_t *out, es_out_id_t *es, bool b_update ) ...@@ -1163,8 +1273,7 @@ static void EsUnselect( es_out_t *out, es_out_id_t *es, bool b_update )
es->pb_cc_present[i] = false; es->pb_cc_present[i] = false;
} }
input_DecoderDelete( es->p_dec ); EsDestroyDecoder( out, es );
es->p_dec = NULL;
} }
if( !b_update ) if( !b_update )
...@@ -1450,6 +1559,12 @@ static int EsOutSend( es_out_t *out, es_out_id_t *es, block_t *p_block ) ...@@ -1450,6 +1559,12 @@ static int EsOutSend( es_out_t *out, es_out_id_t *es, block_t *p_block )
bool pb_cc[4]; bool pb_cc[4];
bool b_cc_new = false; bool b_cc_new = false;
int i; int i;
if( es->p_dec_record )
{
block_t *p_dup = block_Duplicate( p_block );
if( p_dup )
input_DecoderDecode( es->p_dec_record, p_dup );
}
input_DecoderDecode( es->p_dec, p_block ); input_DecoderDecode( es->p_dec, p_block );
/* Check CC status */ /* Check CC status */
...@@ -1510,7 +1625,8 @@ static void EsOutDel( es_out_t *out, es_out_id_t *es ) ...@@ -1510,7 +1625,8 @@ static void EsOutDel( es_out_t *out, es_out_id_t *es )
{ {
while( !out->p_sys->p_input->b_die && es->p_dec ) while( !out->p_sys->p_input->b_die && es->p_dec )
{ {
if( input_DecoderEmpty( es->p_dec ) ) if( input_DecoderEmpty( es->p_dec ) &&
( !es->p_dec_record || input_DecoderEmpty( es->p_dec_record ) ))
break; break;
msleep( 20*1000 ); msleep( 20*1000 );
} }
...@@ -1824,7 +1940,8 @@ static int EsOutControl( es_out_t *out, int i_query, va_list args ) ...@@ -1824,7 +1940,8 @@ static int EsOutControl( es_out_t *out, int i_query, va_list args )
es_format_t *p_fmt; es_format_t *p_fmt;
es = (es_out_id_t*) va_arg( args, es_out_id_t * ); es = (es_out_id_t*) va_arg( args, es_out_id_t * );
p_fmt = (es_format_t*) va_arg( args, es_format_t * ); p_fmt = (es_format_t*) va_arg( args, es_format_t * );
if( es == NULL ) return VLC_EGENERIC; if( es == NULL )
return VLC_EGENERIC;
if( p_fmt->i_extra ) if( p_fmt->i_extra )
{ {
...@@ -1832,13 +1949,12 @@ static int EsOutControl( es_out_t *out, int i_query, va_list args ) ...@@ -1832,13 +1949,12 @@ static int EsOutControl( es_out_t *out, int i_query, va_list args )
es->fmt.p_extra = realloc( es->fmt.p_extra, p_fmt->i_extra ); es->fmt.p_extra = realloc( es->fmt.p_extra, p_fmt->i_extra );
memcpy( es->fmt.p_extra, p_fmt->p_extra, p_fmt->i_extra ); memcpy( es->fmt.p_extra, p_fmt->p_extra, p_fmt->i_extra );
if( !es->p_dec ) return VLC_SUCCESS; if( !es->p_dec )
return VLC_SUCCESS;
#if 1 #if 1
input_DecoderDelete( es->p_dec ); EsDestroyDecoder( out, es );
es->p_dec = input_DecoderNew( p_sys->p_input,
&es->fmt, false );
EsCreateDecoder( out, es );
#else #else
es->p_dec->fmt_in.i_extra = p_fmt->i_extra; es->p_dec->fmt_in.i_extra = p_fmt->i_extra;
es->p_dec->fmt_in.p_extra = es->p_dec->fmt_in.p_extra =
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
#include <vlc_interface.h> #include <vlc_interface.h>
#include <vlc_url.h> #include <vlc_url.h>
#include <vlc_charset.h> #include <vlc_charset.h>
#include <vlc_strings.h>
#ifdef HAVE_SYS_STAT_H #ifdef HAVE_SYS_STAT_H
# include <sys/stat.h> # include <sys/stat.h>
...@@ -1946,16 +1947,20 @@ static bool Control( input_thread_t *p_input, int i_type, ...@@ -1946,16 +1947,20 @@ static bool Control( input_thread_t *p_input, int i_type,
break; break;
case INPUT_CONTROL_SET_RECORD_STATE: case INPUT_CONTROL_SET_RECORD_STATE:
if( p_input->p->input.b_can_record ) if( !!p_input->p->b_recording != !!val.b_bool )
{ {
if( !!p_input->p->b_recording != !!val.b_bool ) if( p_input->p->input.b_can_stream_record )
{ {
if( demux_Control( p_input->p->input.p_demux, if( demux_Control( p_input->p->input.p_demux,
DEMUX_SET_RECORD_STATE, val.b_bool ) ) DEMUX_SET_RECORD_STATE, val.b_bool ) )
val.b_bool = false; val.b_bool = false;
p_input->p->b_recording = val.b_bool;
} }
else
{
if( input_EsOutSetRecord( p_input->p->p_es_out, val.b_bool ) )
val.b_bool = false;
}
p_input->p->b_recording = val.b_bool;
var_Change( p_input, "record", VLC_VAR_SETVALUE, &val, NULL ); var_Change( p_input, "record", VLC_VAR_SETVALUE, &val, NULL );
...@@ -2365,9 +2370,11 @@ static int InputSourceInit( input_thread_t *p_input, ...@@ -2365,9 +2370,11 @@ static int InputSourceInit( input_thread_t *p_input,
goto error; goto error;
} }
if( demux_Control( in->p_demux, DEMUX_CAN_RECORD, &in->b_can_record ) ) if( demux_Control( in->p_demux, DEMUX_CAN_RECORD, &in->b_can_stream_record ) )
in->b_can_record = false; in->b_can_stream_record = false;
var_SetBool( p_input, "can-record", in->b_can_record ); if( !var_CreateGetBool( p_input, "input-record-native" ) )
in->b_can_stream_record = false;
var_SetBool( p_input, "can-record", true ); // Is it still needed ?
/* Get title from demux */ /* Get title from demux */
if( !p_input->b_preparsing && in->i_title <= 0 ) if( !p_input->b_preparsing && in->i_title <= 0 )
...@@ -2929,3 +2936,37 @@ vlc_event_manager_t *input_get_event_manager( input_thread_t *p_input ) ...@@ -2929,3 +2936,37 @@ vlc_event_manager_t *input_get_event_manager( input_thread_t *p_input )
{ {
return &p_input->p->event_manager; return &p_input->p->event_manager;
} }
/**/
/* TODO FIXME nearly the same logic that snapshot code */
char *input_CreateFilename( vlc_object_t *p_obj, const char *psz_path, const char *psz_prefix, const char *psz_extension )
{
char *psz_file;
DIR *path;
path = utf8_opendir( psz_path );
if( path )
{
closedir( path );
char *psz_tmp = str_format( p_obj, psz_prefix );
if( !psz_tmp )
return NULL;
filename_sanitize( psz_tmp );
if( asprintf( &psz_file, "%s"DIR_SEP"%s%s%s",
psz_path, psz_tmp,
psz_extension ? "." : "",
psz_extension ? psz_extension : "" ) < 0 )
psz_file = NULL;
free( psz_tmp );
return psz_file;
}
else
{
psz_file = str_format( p_obj, psz_path );
path_sanitize( psz_file );
return psz_file;
}
}
...@@ -63,7 +63,7 @@ typedef struct ...@@ -63,7 +63,7 @@ typedef struct
bool b_can_pause; bool b_can_pause;
bool b_can_pace_control; bool b_can_pace_control;
bool b_can_rate_control; bool b_can_rate_control;
bool b_can_record; bool b_can_stream_record;
bool b_rescale_ts; bool b_rescale_ts;
bool b_eof; /* eof of demuxer */ bool b_eof; /* eof of demuxer */
...@@ -343,6 +343,7 @@ es_out_t *input_EsOutNew( input_thread_t *, int i_rate ); ...@@ -343,6 +343,7 @@ es_out_t *input_EsOutNew( input_thread_t *, int i_rate );
void input_EsOutDelete( es_out_t * ); void input_EsOutDelete( es_out_t * );
es_out_id_t *input_EsOutGetFromID( es_out_t *, int i_id ); es_out_id_t *input_EsOutGetFromID( es_out_t *, int i_id );
void input_EsOutSetDelay( es_out_t *, int i_cat, int64_t ); void input_EsOutSetDelay( es_out_t *, int i_cat, int64_t );
int input_EsOutSetRecord( es_out_t *, bool b_record );
void input_EsOutChangeRate( es_out_t *, int ); void input_EsOutChangeRate( es_out_t *, int );
void input_EsOutChangeState( es_out_t * ); void input_EsOutChangeState( es_out_t * );
void input_EsOutChangePosition( es_out_t * ); void input_EsOutChangePosition( es_out_t * );
...@@ -419,6 +420,10 @@ static inline void input_ChangeState( input_thread_t *p_input, int state ) ...@@ -419,6 +420,10 @@ static inline void input_ChangeState( input_thread_t *p_input, int state )
input_ChangeStateWithVarCallback( p_input, state, true ); input_ChangeStateWithVarCallback( p_input, state, true );
} }
/* Helpers FIXME to export without input_ prefix */
char *input_CreateFilename( vlc_object_t *p_obj, const char *psz_path, const char *psz_prefix, const char *psz_extension );
#define INPUT_RECORD_PREFIX "vlc-record-%Y-%m-%d-%H:%M:%S-$p"
/* Access */ /* Access */
......
...@@ -28,9 +28,9 @@ ...@@ -28,9 +28,9 @@
#include <dirent.h> #include <dirent.h>
#include <vlc_common.h> #include <vlc_common.h>
#include <vlc_charset.h>
#include <vlc_strings.h> #include <vlc_strings.h>
#include <vlc_osd.h> #include <vlc_osd.h>
#include <vlc_charset.h>
#include <assert.h> #include <assert.h>
...@@ -731,37 +731,6 @@ static int AStreamControl( stream_t *s, int i_query, va_list args ) ...@@ -731,37 +731,6 @@ static int AStreamControl( stream_t *s, int i_query, va_list args )
/**************************************************************************** /****************************************************************************
* ARecord*: record stream functions * ARecord*: record stream functions
****************************************************************************/ ****************************************************************************/
/* TODO FIXME nearly the same logic that snapshot code */
static char *ARecordGetFileName( stream_t *s, const char *psz_path, const char *psz_prefix, const char *psz_extension )
{
char *psz_file;
DIR *path;
path = utf8_opendir( psz_path );
if( path )
{
closedir( path );
char *psz_tmp = str_format( s, psz_prefix );
if( !psz_tmp )
return NULL;
filename_sanitize( psz_tmp );
if( asprintf( &psz_file, "%s"DIR_SEP"%s.%s",
psz_path, psz_tmp, psz_extension ) < 0 )
psz_file = NULL;
free( psz_tmp );
return psz_file;
}
else
{
psz_file = str_format( s, psz_path );
path_sanitize( psz_file );
return psz_file;
}
}
static int ARecordStart( stream_t *s, const char *psz_extension ) static int ARecordStart( stream_t *s, const char *psz_extension )
{ {
stream_sys_t *p_sys = s->p_sys; stream_sys_t *p_sys = s->p_sys;
...@@ -786,7 +755,7 @@ static int ARecordStart( stream_t *s, const char *psz_extension ) ...@@ -786,7 +755,7 @@ static int ARecordStart( stream_t *s, const char *psz_extension )
/* Create file name /* Create file name
* TODO allow prefix configuration */ * TODO allow prefix configuration */
psz_file = ARecordGetFileName( s, psz_path, "vlc-record-%Y-%m-%d-%H:%M:%S-$p", psz_extension ); psz_file = input_CreateFilename( VLC_OBJECT(s), psz_path, INPUT_RECORD_PREFIX, psz_extension );
free( psz_path ); free( psz_path );
......
...@@ -679,6 +679,11 @@ static const char *const ppsz_clock_descriptions[] = ...@@ -679,6 +679,11 @@ static const char *const ppsz_clock_descriptions[] =
#define INPUT_RECORD_PATH_LONGTEXT N_( \ #define INPUT_RECORD_PATH_LONGTEXT N_( \
"Directory or filename where the records will be stored" ) "Directory or filename where the records will be stored" )
#define INPUT_RECORD_NATIVE_TEXT N_("Prefer native stream recording")
#define INPUT_RECORD_NATIVE_LONGTEXT N_( \
"When possible, the input stream will be recorded instead of using" \
"the stream output module" )
// DEPRECATED // DEPRECATED
#define SUB_CAT_LONGTEXT N_( \ #define SUB_CAT_LONGTEXT N_( \
"These options allow you to modify the behavior of the subpictures " \ "These options allow you to modify the behavior of the subpictures " \
...@@ -1743,6 +1748,8 @@ vlc_module_begin(); ...@@ -1743,6 +1748,8 @@ vlc_module_begin();
add_string( "input-record-path", NULL, NULL, INPUT_RECORD_PATH_TEXT, add_string( "input-record-path", NULL, NULL, INPUT_RECORD_PATH_TEXT,
INPUT_RECORD_PATH_LONGTEXT, true ); INPUT_RECORD_PATH_LONGTEXT, true );
add_bool( "input-record-native", true, NULL, INPUT_RECORD_NATIVE_TEXT,
INPUT_RECORD_NATIVE_LONGTEXT, true );
/* Decoder options */ /* Decoder options */
add_category_hint( N_("Decoders"), CODEC_CAT_LONGTEXT , true ); add_category_hint( N_("Decoders"), CODEC_CAT_LONGTEXT , true );
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment