Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
F
ffmpeg.wasm-core
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Linshizhi
ffmpeg.wasm-core
Commits
7ddb0ef9
Commit
7ddb0ef9
authored
Apr 21, 2013
by
Stefano Sabatini
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
lavfi: add zmq filters
parent
dc765627
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
329 additions
and
1 deletion
+329
-1
Changelog
Changelog
+1
-0
configure
configure
+5
-0
filters.texi
doc/filters.texi
+42
-0
Makefile
libavfilter/Makefile
+2
-0
allfilters.c
libavfilter/allfilters.c
+2
-0
f_zmq.c
libavfilter/f_zmq.c
+276
-0
version.h
libavfilter/version.h
+1
-1
No files found.
Changelog
View file @
7ddb0ef9
...
@@ -45,6 +45,7 @@ version <next>:
...
@@ -45,6 +45,7 @@ version <next>:
- RSD demuxer
- RSD demuxer
- RedSpark demuxer
- RedSpark demuxer
- ADPCM IMA Radical decoder
- ADPCM IMA Radical decoder
- zmq filters
version 1.2:
version 1.2:
...
...
configure
View file @
7ddb0ef9
...
@@ -236,6 +236,7 @@ External library support:
...
@@ -236,6 +236,7 @@ External library support:
--enable-libxavs enable AVS encoding via xavs [no]
--enable-libxavs enable AVS encoding via xavs [no]
--enable-libxvid enable Xvid encoding via xvidcore,
--enable-libxvid enable Xvid encoding via xvidcore,
native MPEG-4/Xvid encoder exists [no]
native MPEG-4/Xvid encoder exists [no]
--enable-libzmq enable message passing via libzmq [no]
--enable-openal enable OpenAL 1.1 capture support [no]
--enable-openal enable OpenAL 1.1 capture support [no]
--enable-opencl enable OpenCL code
--enable-opencl enable OpenCL code
--enable-openssl enable openssl [no]
--enable-openssl enable openssl [no]
...
@@ -1195,6 +1196,7 @@ EXTERNAL_LIBRARY_LIST="
...
@@ -1195,6 +1196,7 @@ EXTERNAL_LIBRARY_LIST="
libx264
libx264
libxavs
libxavs
libxvid
libxvid
libzmq
openal
openal
opencl
opencl
openssl
openssl
...
@@ -2122,6 +2124,7 @@ aresample_filter_deps="swresample"
...
@@ -2122,6 +2124,7 @@ aresample_filter_deps="swresample"
ass_filter_deps
=
"libass"
ass_filter_deps
=
"libass"
asyncts_filter_deps
=
"avresample"
asyncts_filter_deps
=
"avresample"
atempo_filter_deps
=
"avcodec rdft"
atempo_filter_deps
=
"avcodec rdft"
azmq_filter_deps
=
"libzmq"
blackframe_filter_deps
=
"gpl"
blackframe_filter_deps
=
"gpl"
boxblur_filter_deps
=
"gpl"
boxblur_filter_deps
=
"gpl"
colormatrix_filter_deps
=
"gpl"
colormatrix_filter_deps
=
"gpl"
...
@@ -2166,6 +2169,7 @@ yadif_filter_deps="gpl"
...
@@ -2166,6 +2169,7 @@ yadif_filter_deps="gpl"
pixfmts_super2xsai_test_deps
=
"super2xsai_filter"
pixfmts_super2xsai_test_deps
=
"super2xsai_filter"
tinterlace_merge_test_deps
=
"tinterlace_filter"
tinterlace_merge_test_deps
=
"tinterlace_filter"
tinterlace_pad_test_deps
=
"tinterlace_filter"
tinterlace_pad_test_deps
=
"tinterlace_filter"
zmq_filter_deps
=
"libzmq"
# libraries
# libraries
avcodec_deps
=
"avutil"
avcodec_deps
=
"avutil"
...
@@ -4055,6 +4059,7 @@ enabled libx264 && require libx264 x264.h x264_encoder_encode -lx264 &&
...
@@ -4055,6 +4059,7 @@ enabled libx264 && require libx264 x264.h x264_encoder_encode -lx264 &&
die
"ERROR: libx264 must be installed and version must be >= 0.118."
;
}
die
"ERROR: libx264 must be installed and version must be >= 0.118."
;
}
enabled libxavs
&&
require libxavs xavs.h xavs_encoder_encode
-lxavs
enabled libxavs
&&
require libxavs xavs.h xavs_encoder_encode
-lxavs
enabled libxvid
&&
require libxvid xvid.h xvid_global
-lxvidcore
enabled libxvid
&&
require libxvid xvid.h xvid_global
-lxvidcore
enabled libzmq
&&
require_pkg_config libzmq zmq.h zmq_ctx_new
enabled openal
&&
{
{
for
al_libs
in
"
${
OPENAL_LIBS
}
"
"-lopenal"
"-lOpenAL32"
;
do
enabled openal
&&
{
{
for
al_libs
in
"
${
OPENAL_LIBS
}
"
"-lopenal"
"-lOpenAL32"
;
do
check_lib
'AL/al.h'
alGetError
"
${
al_libs
}
"
&&
break
;
done
}
||
check_lib
'AL/al.h'
alGetError
"
${
al_libs
}
"
&&
break
;
done
}
||
die
"ERROR: openal not found"
;
}
&&
die
"ERROR: openal not found"
;
}
&&
...
...
doc/filters.texi
View file @
7ddb0ef9
...
@@ -8371,6 +8371,48 @@ ffmpeg -i INPUT -filter_complex asplit=5 OUTPUT
...
@@ -8371,6 +8371,48 @@ ffmpeg -i INPUT -filter_complex asplit=5 OUTPUT
@end example
@end example
@end itemize
@end itemize
@section zmq, azmq
Receive commands sent through a libzmq client, and forward them to
filters in the filtergraph.
@code{zmq} and @code{azmq} work as a pass-through filters. @code{zmq}
must be inserted between two video filters, @code{azmq} between two
audio filters.
To enable these filters you need to install the libzmq library and
headers and configure FFmpeg with @code{--enable-libzmq}.
For more information about libzmq see:
@url{http://www.zeromq.org/}
The @code{zmq} and @code{azmq} filters work as a libzmq server, which
receives messages sent through a network interface defined by the
@option{bind_address} option.
The received message must be in the form:
@example
@var{TARGET} @var{COMMAND} [@var{ARG}]
@end example
@var{TARGET} specifies the target of the command, usually the name of
the filter class or a specific filter instance name.
@var{COMMAND} specifies the name of the command for the target filter.
@var{ARG} is optional and specifies the optional argument list for the
given @var{COMMAND}.
Upon reception, the message is processed and the corresponding command
is injected into the filtergraph. Depending on the result, the filter
will send a reply to the client, adopting the format:
@example
@var{ERROR_CODE} @var{ERROR_REASON}
@var{MESSAGE}
@end example
@var{MESSAGE} is optional.
@c man end MULTIMEDIA FILTERS
@c man end MULTIMEDIA FILTERS
@chapter Multimedia Sources
@chapter Multimedia Sources
...
...
libavfilter/Makefile
View file @
7ddb0ef9
...
@@ -74,6 +74,7 @@ OBJS-$(CONFIG_ASTREAMSYNC_FILTER) += af_astreamsync.o
...
@@ -74,6 +74,7 @@ OBJS-$(CONFIG_ASTREAMSYNC_FILTER) += af_astreamsync.o
OBJS-$(CONFIG_ASYNCTS_FILTER)
+=
af_asyncts.o
OBJS-$(CONFIG_ASYNCTS_FILTER)
+=
af_asyncts.o
OBJS-$(CONFIG_ATEMPO_FILTER)
+=
af_atempo.o
OBJS-$(CONFIG_ATEMPO_FILTER)
+=
af_atempo.o
OBJS-$(CONFIG_ATRIM_FILTER)
+=
trim.o
OBJS-$(CONFIG_ATRIM_FILTER)
+=
trim.o
OBJS-$(CONFIG_AZMQ_FILTER)
+=
f_zmq.o
OBJS-$(CONFIG_BANDPASS_FILTER)
+=
af_biquads.o
OBJS-$(CONFIG_BANDPASS_FILTER)
+=
af_biquads.o
OBJS-$(CONFIG_BANDREJECT_FILTER)
+=
af_biquads.o
OBJS-$(CONFIG_BANDREJECT_FILTER)
+=
af_biquads.o
OBJS-$(CONFIG_BASS_FILTER)
+=
af_biquads.o
OBJS-$(CONFIG_BASS_FILTER)
+=
af_biquads.o
...
@@ -187,6 +188,7 @@ OBJS-$(CONFIG_VFLIP_FILTER) += vf_vflip.o
...
@@ -187,6 +188,7 @@ OBJS-$(CONFIG_VFLIP_FILTER) += vf_vflip.o
OBJS-$(CONFIG_VIDSTABDETECT_FILTER)
+=
vidstabutils.o
vf_vidstabdetect.o
OBJS-$(CONFIG_VIDSTABDETECT_FILTER)
+=
vidstabutils.o
vf_vidstabdetect.o
OBJS-$(CONFIG_VIDSTABTRANSFORM_FILTER)
+=
vidstabutils.o
vf_vidstabtransform.o
OBJS-$(CONFIG_VIDSTABTRANSFORM_FILTER)
+=
vidstabutils.o
vf_vidstabtransform.o
OBJS-$(CONFIG_YADIF_FILTER)
+=
vf_yadif.o
OBJS-$(CONFIG_YADIF_FILTER)
+=
vf_yadif.o
OBJS-$(CONFIG_ZMQ_FILTER)
+=
f_zmq.o
OBJS-$(CONFIG_CELLAUTO_FILTER)
+=
vsrc_cellauto.o
OBJS-$(CONFIG_CELLAUTO_FILTER)
+=
vsrc_cellauto.o
OBJS-$(CONFIG_COLOR_FILTER)
+=
vsrc_testsrc.o
OBJS-$(CONFIG_COLOR_FILTER)
+=
vsrc_testsrc.o
...
...
libavfilter/allfilters.c
View file @
7ddb0ef9
...
@@ -72,6 +72,7 @@ void avfilter_register_all(void)
...
@@ -72,6 +72,7 @@ void avfilter_register_all(void)
REGISTER_FILTER
(
ASYNCTS
,
asyncts
,
af
);
REGISTER_FILTER
(
ASYNCTS
,
asyncts
,
af
);
REGISTER_FILTER
(
ATEMPO
,
atempo
,
af
);
REGISTER_FILTER
(
ATEMPO
,
atempo
,
af
);
REGISTER_FILTER
(
ATRIM
,
atrim
,
af
);
REGISTER_FILTER
(
ATRIM
,
atrim
,
af
);
REGISTER_FILTER
(
AZMQ
,
azmq
,
af
);
REGISTER_FILTER
(
BANDPASS
,
bandpass
,
af
);
REGISTER_FILTER
(
BANDPASS
,
bandpass
,
af
);
REGISTER_FILTER
(
BANDREJECT
,
bandreject
,
af
);
REGISTER_FILTER
(
BANDREJECT
,
bandreject
,
af
);
REGISTER_FILTER
(
BASS
,
bass
,
af
);
REGISTER_FILTER
(
BASS
,
bass
,
af
);
...
@@ -184,6 +185,7 @@ void avfilter_register_all(void)
...
@@ -184,6 +185,7 @@ void avfilter_register_all(void)
REGISTER_FILTER
(
VIDSTABDETECT
,
vidstabdetect
,
vf
);
REGISTER_FILTER
(
VIDSTABDETECT
,
vidstabdetect
,
vf
);
REGISTER_FILTER
(
VIDSTABTRANSFORM
,
vidstabtransform
,
vf
);
REGISTER_FILTER
(
VIDSTABTRANSFORM
,
vidstabtransform
,
vf
);
REGISTER_FILTER
(
YADIF
,
yadif
,
vf
);
REGISTER_FILTER
(
YADIF
,
yadif
,
vf
);
REGISTER_FILTER
(
ZMQ
,
zmq
,
vf
);
REGISTER_FILTER
(
CELLAUTO
,
cellauto
,
vsrc
);
REGISTER_FILTER
(
CELLAUTO
,
cellauto
,
vsrc
);
REGISTER_FILTER
(
COLOR
,
color
,
vsrc
);
REGISTER_FILTER
(
COLOR
,
color
,
vsrc
);
...
...
libavfilter/f_zmq.c
0 → 100644
View file @
7ddb0ef9
/*
* Copyright (c) 2013 Stefano Sabatini
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file
* receive commands through libzeromq and broker them to filters
*/
#include <zmq.h>
#include "libavutil/avstring.h"
#include "libavutil/bprint.h"
#include "libavutil/opt.h"
#include "avfilter.h"
#include "internal.h"
#include "avfiltergraph.h"
#include "audio.h"
#include "video.h"
typedef
struct
{
const
AVClass
*
class
;
void
*
zmq
;
void
*
responder
;
char
*
bind_address
;
int
command_count
;
}
ZMQContext
;
#define OFFSET(x) offsetof(ZMQContext, x)
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_VIDEO_PARAM
static
const
AVOption
options
[]
=
{
{
"bind_address"
,
"set bind address"
,
OFFSET
(
bind_address
),
AV_OPT_TYPE_STRING
,
{.
str
=
"tcp://*:5555"
},
0
,
0
,
FLAGS
},
{
"b"
,
"set bind address"
,
OFFSET
(
bind_address
),
AV_OPT_TYPE_STRING
,
{.
str
=
"tcp://*:5555"
},
0
,
0
,
FLAGS
},
{
NULL
}
};
static
av_cold
int
init
(
AVFilterContext
*
ctx
)
{
ZMQContext
*
zmq
=
ctx
->
priv
;
zmq
->
zmq
=
zmq_ctx_new
();
if
(
!
zmq
->
zmq
)
{
av_log
(
ctx
,
AV_LOG_ERROR
,
"Could not create ZMQ context: %s
\n
"
,
zmq_strerror
(
errno
));
return
AVERROR_EXTERNAL
;
}
zmq
->
responder
=
zmq_socket
(
zmq
->
zmq
,
ZMQ_REP
);
if
(
!
zmq
->
responder
)
{
av_log
(
ctx
,
AV_LOG_ERROR
,
"Could not create ZMQ socket: %s
\n
"
,
zmq_strerror
(
errno
));
return
AVERROR_EXTERNAL
;
}
if
(
zmq_bind
(
zmq
->
responder
,
zmq
->
bind_address
)
==
-
1
)
{
av_log
(
ctx
,
AV_LOG_ERROR
,
"Could not bind ZMQ socket to address '%s': %s
\n
"
,
zmq
->
bind_address
,
zmq_strerror
(
errno
));
return
AVERROR_EXTERNAL
;
}
zmq
->
command_count
=
-
1
;
return
0
;
}
static
void
av_cold
uninit
(
AVFilterContext
*
ctx
)
{
ZMQContext
*
zmq
=
ctx
->
priv
;
zmq_close
(
zmq
->
responder
);
zmq_ctx_destroy
(
zmq
->
zmq
);
}
typedef
struct
{
char
*
target
,
*
command
,
*
arg
;
}
Command
;
#define SPACES " \f\t\n\r"
static
int
parse_command
(
Command
*
cmd
,
const
char
*
command_str
,
void
*
log_ctx
)
{
const
char
**
buf
=
&
command_str
;
cmd
->
target
=
av_get_token
(
buf
,
SPACES
);
if
(
!
cmd
->
target
||
!
cmd
->
target
[
0
])
{
av_log
(
log_ctx
,
AV_LOG_ERROR
,
"No target specified in command '%s'
\n
"
,
command_str
);
return
AVERROR
(
EINVAL
);
}
cmd
->
command
=
av_get_token
(
buf
,
SPACES
);
if
(
!
cmd
->
command
||
!
cmd
->
command
[
0
])
{
av_log
(
log_ctx
,
AV_LOG_ERROR
,
"No command specified in command '%s'
\n
"
,
command_str
);
return
AVERROR
(
EINVAL
);
}
cmd
->
arg
=
av_get_token
(
buf
,
SPACES
);
return
0
;
}
static
int
recv_msg
(
AVFilterContext
*
ctx
,
char
**
buf
,
int
*
buf_size
)
{
ZMQContext
*
zmq
=
ctx
->
priv
;
zmq_msg_t
msg
;
int
ret
=
0
;
if
(
zmq_msg_init
(
&
msg
)
==
-
1
)
{
av_log
(
ctx
,
AV_LOG_WARNING
,
"Could not initialize receive message: %s
\n
"
,
zmq_strerror
(
errno
));
return
AVERROR_EXTERNAL
;
}
if
(
zmq_msg_recv
(
&
msg
,
zmq
->
responder
,
ZMQ_DONTWAIT
)
==
-
1
)
{
if
(
errno
!=
EAGAIN
)
av_log
(
ctx
,
AV_LOG_WARNING
,
"Could not receive message: %s
\n
"
,
zmq_strerror
(
errno
));
ret
=
AVERROR_EXTERNAL
;
goto
end
;
}
*
buf_size
=
zmq_msg_size
(
&
msg
)
+
1
;
*
buf
=
av_malloc
(
*
buf_size
);
if
(
!*
buf
)
{
ret
=
AVERROR
(
ENOMEM
);
goto
end
;
}
memcpy
(
*
buf
,
zmq_msg_data
(
&
msg
),
*
buf_size
);
(
*
buf
)[
*
buf_size
-
1
]
=
0
;
end:
zmq_msg_close
(
&
msg
);
return
ret
;
}
static
int
filter_frame
(
AVFilterLink
*
inlink
,
AVFrame
*
ref
)
{
AVFilterContext
*
ctx
=
inlink
->
dst
;
ZMQContext
*
zmq
=
ctx
->
priv
;
while
(
1
)
{
char
cmd_buf
[
1024
];
char
*
recv_buf
,
*
send_buf
;
int
recv_buf_size
;
Command
cmd
=
{
0
};
int
ret
;
/* receive command */
if
(
recv_msg
(
ctx
,
&
recv_buf
,
&
recv_buf_size
)
<
0
)
break
;
zmq
->
command_count
++
;
/* parse command */
if
(
parse_command
(
&
cmd
,
recv_buf
,
ctx
)
<
0
)
{
av_log
(
ctx
,
AV_LOG_ERROR
,
"Could not parse command #%d
\n
"
,
zmq
->
command_count
);
goto
end
;
}
/* process command */
av_log
(
ctx
,
AV_LOG_VERBOSE
,
"Processing command #%d target:%s command:%s arg:%s
\n
"
,
zmq
->
command_count
,
cmd
.
target
,
cmd
.
command
,
cmd
.
arg
);
ret
=
avfilter_graph_send_command
(
inlink
->
graph
,
cmd
.
target
,
cmd
.
command
,
cmd
.
arg
,
cmd_buf
,
sizeof
(
cmd_buf
),
AVFILTER_CMD_FLAG_ONE
);
send_buf
=
av_asprintf
(
"%d %s%s%s"
,
-
ret
,
av_err2str
(
ret
),
cmd_buf
[
0
]
?
"
\n
"
:
""
,
cmd_buf
);
if
(
!
send_buf
)
{
ret
=
AVERROR
(
ENOMEM
);
goto
end
;
}
av_log
(
ctx
,
AV_LOG_VERBOSE
,
"Sending command reply for command #%d:
\n
%s
\n
"
,
zmq
->
command_count
,
send_buf
);
if
(
zmq_send
(
zmq
->
responder
,
send_buf
,
strlen
(
send_buf
),
0
)
==
-
1
)
av_log
(
ctx
,
AV_LOG_ERROR
,
"Failed to send reply for command #%d: %s
\n
"
,
zmq
->
command_count
,
zmq_strerror
(
ret
));
end:
av_freep
(
&
send_buf
);
av_freep
(
&
recv_buf
);
recv_buf_size
=
0
;
av_freep
(
&
cmd
.
target
);
av_freep
(
&
cmd
.
command
);
av_freep
(
&
cmd
.
arg
);
}
return
ff_filter_frame
(
ctx
->
outputs
[
0
],
ref
);
}
#if CONFIG_ZMQ_FILTER
#define zmq_options options
AVFILTER_DEFINE_CLASS
(
zmq
);
static
const
AVFilterPad
zmq_inputs
[]
=
{
{
.
name
=
"default"
,
.
type
=
AVMEDIA_TYPE_VIDEO
,
.
filter_frame
=
filter_frame
,
},
{
NULL
}
};
static
const
AVFilterPad
zmq_outputs
[]
=
{
{
.
name
=
"default"
,
.
type
=
AVMEDIA_TYPE_VIDEO
,
},
{
NULL
}
};
AVFilter
avfilter_vf_zmq
=
{
.
name
=
"zmq"
,
.
description
=
NULL_IF_CONFIG_SMALL
(
"Receive commands through ZMQ and broker them to filters."
),
.
init
=
init
,
.
uninit
=
uninit
,
.
priv_size
=
sizeof
(
ZMQContext
),
.
inputs
=
zmq_inputs
,
.
outputs
=
zmq_outputs
,
.
priv_class
=
&
zmq_class
,
};
#endif
#if CONFIG_AZMQ_FILTER
#define azmq_options options
AVFILTER_DEFINE_CLASS
(
azmq
);
static
const
AVFilterPad
azmq_inputs
[]
=
{
{
.
name
=
"default"
,
.
type
=
AVMEDIA_TYPE_AUDIO
,
.
get_audio_buffer
=
ff_null_get_audio_buffer
,
.
filter_frame
=
filter_frame
,
},
{
NULL
}
};
static
const
AVFilterPad
azmq_outputs
[]
=
{
{
.
name
=
"default"
,
.
type
=
AVMEDIA_TYPE_AUDIO
,
},
{
NULL
}
};
AVFilter
avfilter_af_azmq
=
{
.
name
=
"azmq"
,
.
description
=
NULL_IF_CONFIG_SMALL
(
"Receive commands through ZMQ and broker them to filters."
),
.
init
=
init
,
.
uninit
=
uninit
,
.
priv_size
=
sizeof
(
ZMQContext
),
.
inputs
=
azmq_inputs
,
.
outputs
=
azmq_outputs
,
.
priv_class
=
&
azmq_class
,
};
#endif
libavfilter/version.h
View file @
7ddb0ef9
...
@@ -29,7 +29,7 @@
...
@@ -29,7 +29,7 @@
#include "libavutil/avutil.h"
#include "libavutil/avutil.h"
#define LIBAVFILTER_VERSION_MAJOR 3
#define LIBAVFILTER_VERSION_MAJOR 3
#define LIBAVFILTER_VERSION_MINOR 6
5
#define LIBAVFILTER_VERSION_MINOR 6
6
#define LIBAVFILTER_VERSION_MICRO 100
#define LIBAVFILTER_VERSION_MICRO 100
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment