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
841df7bf
Commit
841df7bf
authored
Jun 03, 2013
by
Stefano Sabatini
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
lavfi: port sab filter from libmpcodecs
parent
08041711
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
380 additions
and
2 deletions
+380
-2
LICENSE
LICENSE
+1
-0
configure
configure
+1
-0
filters.texi
doc/filters.texi
+36
-0
Makefile
libavfilter/Makefile
+2
-0
allfilters.c
libavfilter/allfilters.c
+1
-0
version.h
libavfilter/version.h
+2
-2
vf_sab.c
libavfilter/vf_sab.c
+337
-0
No files found.
LICENSE
View file @
841df7bf
...
...
@@ -40,6 +40,7 @@ Specifically, the GPL parts of FFmpeg are
- vf_noise.c
- vf_owdenoise.c
- vf_pp.c
- vf_sab.c
- vf_smartblur.c
- vf_stereo3d.c
- vf_super2xsai.c
...
...
configure
View file @
841df7bf
...
...
@@ -2168,6 +2168,7 @@ owdenoise_filter_deps="gpl"
pan_filter_deps
=
"swresample"
pp_filter_deps
=
"gpl postproc"
removelogo_filter_deps
=
"avcodec avformat swscale"
sab_filter_deps
=
"gpl swscale"
scale_filter_deps
=
"swscale"
smartblur_filter_deps
=
"gpl swscale"
showspectrum_filter_deps
=
"avcodec rdft"
...
...
doc/filters.texi
View file @
841df7bf
...
...
@@ -5772,6 +5772,42 @@ much, but it will increase the amount of blurring needed to cover over
the image and will destroy more information than necessary, and extra
pixels will slow things down on a large logo.
@section sab
Apply Shape Adaptive Blur.
The filter accepts the following options:
@table @option
@item luma_radius, lr
Set luma blur filter strength, must be a value in range 0.1-4.0, default
value is 1.0. A greater value will result in a more blurred image, and
in slower processing.
@item luma_pre_filter_radius, lpfr
Set luma pre-filter radius, must be a value in the 0.1-2.0 range, default
value is 1.0.
@item luma_strength, ls
Set luma maximum difference between pixels to still be considered, must
be a value in the 0.1-100.0 range, default value is 1.0.
@item chroma_radius, cr
Set chroma blur filter strength, must be a value in range 0.1-4.0. A
greater value will result in a more blurred image, and in slower
processing.
@item chroma_pre_filter_radius, cpfr
Set chroma pre-filter radius, must be a value in the 0.1-2.0 range.
@item chroma_strength, cs
Set chroma maximum difference between pixels to still be considered,
must be a value in the 0.1-100.0 range.
@end table
Each chroma option value, if not explicitly specified, is set to the
corresponding luma option value.
@section scale
Scale (resize) the input video, using the libswscale library.
...
...
libavfilter/Makefile
View file @
841df7bf
...
...
@@ -16,6 +16,7 @@ FFLIBS-$(CONFIG_PAN_FILTER) += swresample
FFLIBS-$(CONFIG_PP_FILTER)
+=
postproc
FFLIBS-$(CONFIG_REMOVELOGO_FILTER)
+=
avformat
avcodec
swscale
FFLIBS-$(CONFIG_RESAMPLE_FILTER)
+=
avresample
FFLIBS-$(CONFIG_SAB_FILTER)
+=
swscale
FFLIBS-$(CONFIG_SCALE_FILTER)
+=
swscale
FFLIBS-$(CONFIG_SHOWSPECTRUM_FILTER)
+=
avcodec
FFLIBS-$(CONFIG_SMARTBLUR_FILTER)
+=
swscale
...
...
@@ -168,6 +169,7 @@ OBJS-$(CONFIG_PIXDESCTEST_FILTER) += vf_pixdesctest.o
OBJS-$(CONFIG_PP_FILTER)
+=
vf_pp.o
OBJS-$(CONFIG_REMOVELOGO_FILTER)
+=
bbox.o
lswsutils.o
lavfutils.o
vf_removelogo.o
OBJS-$(CONFIG_SEPARATEFIELDS_FILTER)
+=
vf_separatefields.o
OBJS-$(CONFIG_SAB_FILTER)
+=
vf_sab.o
OBJS-$(CONFIG_SCALE_FILTER)
+=
vf_scale.o
OBJS-$(CONFIG_SELECT_FILTER)
+=
f_select.o
OBJS-$(CONFIG_SENDCMD_FILTER)
+=
f_sendcmd.o
...
...
libavfilter/allfilters.c
View file @
841df7bf
...
...
@@ -163,6 +163,7 @@ void avfilter_register_all(void)
REGISTER_FILTER
(
PIXDESCTEST
,
pixdesctest
,
vf
);
REGISTER_FILTER
(
PP
,
pp
,
vf
);
REGISTER_FILTER
(
REMOVELOGO
,
removelogo
,
vf
);
REGISTER_FILTER
(
SAB
,
sab
,
vf
);
REGISTER_FILTER
(
SCALE
,
scale
,
vf
);
REGISTER_FILTER
(
SELECT
,
select
,
vf
);
REGISTER_FILTER
(
SENDCMD
,
sendcmd
,
vf
);
...
...
libavfilter/version.h
View file @
841df7bf
...
...
@@ -30,8 +30,8 @@
#include "libavutil/avutil.h"
#define LIBAVFILTER_VERSION_MAJOR 3
#define LIBAVFILTER_VERSION_MINOR 7
4
#define LIBAVFILTER_VERSION_MICRO 10
1
#define LIBAVFILTER_VERSION_MINOR 7
5
#define LIBAVFILTER_VERSION_MICRO 10
0
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
LIBAVFILTER_VERSION_MINOR, \
...
...
libavfilter/vf_sab.c
0 → 100644
View file @
841df7bf
/*
* Copyright (c) 2002 Michael Niedermayer <michaelni@gmx.at>
*
* This file is part of FFmpeg.
*
* FFmpeg 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 2 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 General Public License for more details.
*
* You should have received a copy of the GNU 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
* Shape Adaptive Blur filter, ported from MPlayer libmpcodecs/vf_sab.c
*/
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "libswscale/swscale.h"
#include "avfilter.h"
#include "formats.h"
#include "internal.h"
typedef
struct
{
float
radius
;
float
pre_filter_radius
;
float
strength
;
float
quality
;
struct
SwsContext
*
pre_filter_context
;
uint8_t
*
pre_filter_buf
;
int
pre_filter_linesize
;
int
dist_width
;
int
dist_linesize
;
int
*
dist_coeff
;
#define COLOR_DIFF_COEFF_SIZE 512
int
color_diff_coeff
[
COLOR_DIFF_COEFF_SIZE
];
}
FilterParam
;
typedef
struct
{
const
AVClass
*
class
;
FilterParam
luma
;
FilterParam
chroma
;
int
hsub
;
int
vsub
;
unsigned
int
sws_flags
;
}
SabContext
;
static
int
query_formats
(
AVFilterContext
*
ctx
)
{
static
const
enum
AVPixelFormat
pix_fmts
[]
=
{
AV_PIX_FMT_YUV420P
,
AV_PIX_FMT_YUV410P
,
AV_PIX_FMT_YUV444P
,
AV_PIX_FMT_YUV422P
,
AV_PIX_FMT_YUV411P
,
AV_PIX_FMT_NONE
};
ff_set_common_formats
(
ctx
,
ff_make_format_list
(
pix_fmts
));
return
0
;
}
#define RADIUS_MIN 0.1
#define RADIUS_MAX 4.0
#define PRE_FILTER_RADIUS_MIN 0.1
#define PRE_FILTER_RADIUS_MAX 2.0
#define STRENGTH_MIN 0.1
#define STRENGTH_MAX 100.0
#define OFFSET(x) offsetof(SabContext, x)
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
static
const
AVOption
sab_options
[]
=
{
{
"luma_radius"
,
"set luma radius"
,
OFFSET
(
luma
.
radius
),
AV_OPT_TYPE_FLOAT
,
{.
dbl
=
1
.
0
},
RADIUS_MIN
,
RADIUS_MAX
,
.
flags
=
FLAGS
},
{
"lr"
,
"set luma radius"
,
OFFSET
(
luma
.
radius
),
AV_OPT_TYPE_FLOAT
,
{.
dbl
=
1
.
0
},
RADIUS_MIN
,
RADIUS_MAX
,
.
flags
=
FLAGS
},
{
"luma_pre_filter_radius"
,
"set luma pre-filter radius"
,
OFFSET
(
luma
.
pre_filter_radius
),
AV_OPT_TYPE_FLOAT
,
{.
dbl
=
1
.
0
},
PRE_FILTER_RADIUS_MIN
,
PRE_FILTER_RADIUS_MAX
,
.
flags
=
FLAGS
},
{
"lpfr"
,
"set luma pre-filter radius"
,
OFFSET
(
luma
.
pre_filter_radius
),
AV_OPT_TYPE_FLOAT
,
{.
dbl
=
1
.
0
},
PRE_FILTER_RADIUS_MIN
,
PRE_FILTER_RADIUS_MAX
,
.
flags
=
FLAGS
},
{
"luma_strength"
,
"set luma strength"
,
OFFSET
(
luma
.
strength
),
AV_OPT_TYPE_FLOAT
,
{.
dbl
=
1
.
0
},
STRENGTH_MIN
,
STRENGTH_MAX
,
.
flags
=
FLAGS
},
{
"ls"
,
"set luma strength"
,
OFFSET
(
luma
.
strength
),
AV_OPT_TYPE_FLOAT
,
{.
dbl
=
1
.
0
},
STRENGTH_MIN
,
STRENGTH_MAX
,
.
flags
=
FLAGS
},
{
"chroma_radius"
,
"set chroma radius"
,
OFFSET
(
chroma
.
radius
),
AV_OPT_TYPE_FLOAT
,
{.
dbl
=
RADIUS_MIN
-
1
},
RADIUS_MIN
-
1
,
RADIUS_MAX
,
.
flags
=
FLAGS
},
{
"cr"
,
"set chroma radius"
,
OFFSET
(
chroma
.
radius
),
AV_OPT_TYPE_FLOAT
,
{.
dbl
=
RADIUS_MIN
-
1
},
RADIUS_MIN
-
1
,
RADIUS_MAX
,
.
flags
=
FLAGS
},
{
"chroma_pre_filter_radius"
,
"set chroma pre-filter radius"
,
OFFSET
(
chroma
.
pre_filter_radius
),
AV_OPT_TYPE_FLOAT
,
{.
dbl
=
PRE_FILTER_RADIUS_MIN
-
1
},
PRE_FILTER_RADIUS_MIN
-
1
,
PRE_FILTER_RADIUS_MAX
,
.
flags
=
FLAGS
},
{
"cpfr"
,
"set chroma pre-filter radius"
,
OFFSET
(
chroma
.
pre_filter_radius
),
AV_OPT_TYPE_FLOAT
,
{.
dbl
=
PRE_FILTER_RADIUS_MIN
-
1
},
PRE_FILTER_RADIUS_MIN
-
1
,
PRE_FILTER_RADIUS_MAX
,
.
flags
=
FLAGS
},
{
"chroma_strength"
,
"set chroma strength"
,
OFFSET
(
chroma
.
strength
),
AV_OPT_TYPE_FLOAT
,
{.
dbl
=
STRENGTH_MIN
-
1
},
STRENGTH_MIN
-
1
,
STRENGTH_MAX
,
.
flags
=
FLAGS
},
{
"cs"
,
"set chroma strength"
,
OFFSET
(
chroma
.
strength
),
AV_OPT_TYPE_FLOAT
,
{.
dbl
=
STRENGTH_MIN
-
1
},
STRENGTH_MIN
-
1
,
STRENGTH_MAX
,
.
flags
=
FLAGS
},
{
NULL
}
};
AVFILTER_DEFINE_CLASS
(
sab
);
static
av_cold
int
init
(
AVFilterContext
*
ctx
)
{
SabContext
*
sab
=
ctx
->
priv
;
/* make chroma default to luma values, if not explicitly set */
if
(
sab
->
chroma
.
radius
<
RADIUS_MIN
)
sab
->
chroma
.
radius
=
sab
->
luma
.
radius
;
if
(
sab
->
chroma
.
pre_filter_radius
<
PRE_FILTER_RADIUS_MIN
)
sab
->
chroma
.
pre_filter_radius
=
sab
->
luma
.
pre_filter_radius
;
if
(
sab
->
chroma
.
strength
<
STRENGTH_MIN
)
sab
->
chroma
.
strength
=
sab
->
luma
.
strength
;
sab
->
luma
.
quality
=
sab
->
chroma
.
quality
=
3
.
0
;
sab
->
sws_flags
=
SWS_POINT
;
av_log
(
ctx
,
AV_LOG_VERBOSE
,
"luma_radius:%f luma_pre_filter_radius::%f luma_strength:%f "
"chroma_radius:%f chroma_pre_filter_radius:%f chroma_strength:%f
\n
"
,
sab
->
luma
.
radius
,
sab
->
luma
.
pre_filter_radius
,
sab
->
luma
.
strength
,
sab
->
chroma
.
radius
,
sab
->
chroma
.
pre_filter_radius
,
sab
->
chroma
.
strength
);
return
0
;
}
static
void
close_filter_param
(
FilterParam
*
f
)
{
if
(
f
->
pre_filter_context
)
{
sws_freeContext
(
f
->
pre_filter_context
);
f
->
pre_filter_context
=
NULL
;
}
av_freep
(
&
f
->
pre_filter_buf
);
av_freep
(
&
f
->
dist_coeff
);
}
static
av_cold
void
uninit
(
AVFilterContext
*
ctx
)
{
SabContext
*
sab
=
ctx
->
priv
;
close_filter_param
(
&
sab
->
luma
);
close_filter_param
(
&
sab
->
chroma
);
}
static
int
open_filter_param
(
FilterParam
*
f
,
int
width
,
int
height
,
unsigned
int
sws_flags
)
{
SwsVector
*
vec
;
SwsFilter
sws_f
;
int
i
,
x
,
y
;
int
linesize
=
FFALIGN
(
width
,
8
);
f
->
pre_filter_buf
=
av_malloc
(
linesize
*
height
);
if
(
!
f
->
pre_filter_buf
)
return
AVERROR
(
ENOMEM
);
f
->
pre_filter_linesize
=
linesize
;
vec
=
sws_getGaussianVec
(
f
->
pre_filter_radius
,
f
->
quality
);
sws_f
.
lumH
=
sws_f
.
lumV
=
vec
;
sws_f
.
chrH
=
sws_f
.
chrV
=
NULL
;
f
->
pre_filter_context
=
sws_getContext
(
width
,
height
,
AV_PIX_FMT_GRAY8
,
width
,
height
,
AV_PIX_FMT_GRAY8
,
sws_flags
,
&
sws_f
,
NULL
,
NULL
);
sws_freeVec
(
vec
);
vec
=
sws_getGaussianVec
(
f
->
strength
,
5
.
0
);
for
(
i
=
0
;
i
<
COLOR_DIFF_COEFF_SIZE
;
i
++
)
{
double
d
;
int
index
=
i
-
COLOR_DIFF_COEFF_SIZE
/
2
+
vec
->
length
/
2
;
if
(
index
<
0
||
index
>=
vec
->
length
)
d
=
0
.
0
;
else
d
=
vec
->
coeff
[
index
];
f
->
color_diff_coeff
[
i
]
=
(
int
)(
d
/
vec
->
coeff
[
vec
->
length
/
2
]
*
(
1
<<
12
)
+
0
.
5
);
}
sws_freeVec
(
vec
);
vec
=
sws_getGaussianVec
(
f
->
radius
,
f
->
quality
);
f
->
dist_width
=
vec
->
length
;
f
->
dist_linesize
=
FFALIGN
(
vec
->
length
,
8
);
f
->
dist_coeff
=
av_malloc
(
f
->
dist_width
*
f
->
dist_linesize
*
sizeof
(
*
f
->
dist_coeff
));
if
(
!
f
->
dist_coeff
)
return
AVERROR
(
ENOMEM
);
for
(
y
=
0
;
y
<
vec
->
length
;
y
++
)
{
for
(
x
=
0
;
x
<
vec
->
length
;
x
++
)
{
double
d
=
vec
->
coeff
[
x
]
*
vec
->
coeff
[
y
];
f
->
dist_coeff
[
x
+
y
*
f
->
dist_linesize
]
=
(
int
)(
d
*
(
1
<<
10
)
+
0
.
5
);
}
}
sws_freeVec
(
vec
);
return
0
;
}
static
int
config_props
(
AVFilterLink
*
inlink
)
{
SabContext
*
sab
=
inlink
->
dst
->
priv
;
const
AVPixFmtDescriptor
*
desc
=
av_pix_fmt_desc_get
(
inlink
->
format
);
int
ret
;
sab
->
hsub
=
desc
->
log2_chroma_w
;
sab
->
vsub
=
desc
->
log2_chroma_h
;
close_filter_param
(
&
sab
->
luma
);
ret
=
open_filter_param
(
&
sab
->
luma
,
inlink
->
w
,
inlink
->
h
,
sab
->
sws_flags
);
if
(
ret
<
0
)
return
ret
;
close_filter_param
(
&
sab
->
chroma
);
ret
=
open_filter_param
(
&
sab
->
chroma
,
FF_CEIL_RSHIFT
(
inlink
->
w
,
sab
->
hsub
),
FF_CEIL_RSHIFT
(
inlink
->
h
,
sab
->
vsub
),
sab
->
sws_flags
);
return
ret
;
}
#define NB_PLANES 4
static
void
blur
(
uint8_t
*
dst
,
const
int
dst_linesize
,
const
uint8_t
*
src
,
const
int
src_linesize
,
const
int
w
,
const
int
h
,
FilterParam
*
fp
)
{
int
x
,
y
;
FilterParam
f
=
*
fp
;
const
int
radius
=
f
.
dist_width
/
2
;
const
uint8_t
*
const
src2
[
NB_PLANES
]
=
{
src
};
int
src2_linesize
[
NB_PLANES
]
=
{
src_linesize
};
uint8_t
*
dst2
[
NB_PLANES
]
=
{
f
.
pre_filter_buf
};
int
dst2_linesize
[
NB_PLANES
]
=
{
f
.
pre_filter_linesize
};
sws_scale
(
f
.
pre_filter_context
,
src2
,
src2_linesize
,
0
,
h
,
dst2
,
dst2_linesize
);
#define UPDATE_FACTOR do { \
int factor; \
factor = f.color_diff_coeff[COLOR_DIFF_COEFF_SIZE/2 + pre_val - \
f.pre_filter_buf[ix + iy*f.pre_filter_linesize]] * f.dist_coeff[dx + dy*f.dist_linesize]; \
sum += src[ix + iy*src_linesize] * factor; \
div += factor; \
} while (0)
for
(
y
=
0
;
y
<
h
;
y
++
)
{
for
(
x
=
0
;
x
<
w
;
x
++
)
{
int
sum
=
0
;
int
div
=
0
;
int
dy
;
const
int
pre_val
=
f
.
pre_filter_buf
[
x
+
y
*
f
.
pre_filter_linesize
];
if
(
x
>=
radius
&&
x
<
w
-
radius
)
{
for
(
dy
=
0
;
dy
<
radius
*
2
+
1
;
dy
++
)
{
int
dx
;
int
iy
=
y
+
dy
-
radius
;
if
(
iy
<
0
)
iy
=
-
iy
;
else
if
(
iy
>=
h
)
iy
=
h
+
h
-
iy
-
1
;
for
(
dx
=
0
;
dx
<
radius
*
2
+
1
;
dx
++
)
{
const
int
ix
=
x
+
dx
-
radius
;
UPDATE_FACTOR
;
}
}
}
else
{
for
(
dy
=
0
;
dy
<
radius
*
2
+
1
;
dy
++
)
{
int
dx
;
int
iy
=
y
+
dy
-
radius
;
if
(
iy
<
0
)
iy
=
-
iy
;
else
if
(
iy
>=
h
)
iy
=
h
+
h
-
iy
-
1
;
for
(
dx
=
0
;
dx
<
radius
*
2
+
1
;
dx
++
)
{
int
ix
=
x
+
dx
-
radius
;
if
(
ix
<
0
)
ix
=
-
ix
;
else
if
(
ix
>=
w
)
ix
=
w
+
w
-
ix
-
1
;
UPDATE_FACTOR
;
}
}
}
dst
[
x
+
y
*
dst_linesize
]
=
(
sum
+
div
/
2
)
/
div
;
}
}
}
static
int
filter_frame
(
AVFilterLink
*
inlink
,
AVFrame
*
inpic
)
{
SabContext
*
sab
=
inlink
->
dst
->
priv
;
AVFilterLink
*
outlink
=
inlink
->
dst
->
outputs
[
0
];
AVFrame
*
outpic
;
outpic
=
ff_get_video_buffer
(
outlink
,
outlink
->
w
,
outlink
->
h
);
if
(
!
outpic
)
{
av_frame_free
(
&
inpic
);
return
AVERROR
(
ENOMEM
);
}
av_frame_copy_props
(
outpic
,
inpic
);
blur
(
outpic
->
data
[
0
],
outpic
->
linesize
[
0
],
inpic
->
data
[
0
],
inpic
->
linesize
[
0
],
inlink
->
w
,
inlink
->
h
,
&
sab
->
luma
);
if
(
inpic
->
data
[
2
])
{
int
cw
=
FF_CEIL_RSHIFT
(
inlink
->
w
,
sab
->
hsub
);
int
ch
=
FF_CEIL_RSHIFT
(
inlink
->
h
,
sab
->
vsub
);
blur
(
outpic
->
data
[
1
],
outpic
->
linesize
[
1
],
inpic
->
data
[
1
],
inpic
->
linesize
[
1
],
cw
,
ch
,
&
sab
->
chroma
);
blur
(
outpic
->
data
[
2
],
outpic
->
linesize
[
2
],
inpic
->
data
[
2
],
inpic
->
linesize
[
2
],
cw
,
ch
,
&
sab
->
chroma
);
}
av_frame_free
(
&
inpic
);
return
ff_filter_frame
(
outlink
,
outpic
);
}
static
const
AVFilterPad
sab_inputs
[]
=
{
{
.
name
=
"default"
,
.
type
=
AVMEDIA_TYPE_VIDEO
,
.
filter_frame
=
filter_frame
,
.
config_props
=
config_props
,
},
{
NULL
}
};
static
const
AVFilterPad
sab_outputs
[]
=
{
{
.
name
=
"default"
,
.
type
=
AVMEDIA_TYPE_VIDEO
,
},
{
NULL
}
};
AVFilter
avfilter_vf_sab
=
{
.
name
=
"sab"
,
.
description
=
NULL_IF_CONFIG_SMALL
(
"Apply shape adaptive blur."
),
.
priv_size
=
sizeof
(
SabContext
),
.
init
=
init
,
.
uninit
=
uninit
,
.
query_formats
=
query_formats
,
.
inputs
=
sab_inputs
,
.
outputs
=
sab_outputs
,
.
priv_class
=
&
sab_class
,
.
flags
=
AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC
,
};
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