Message ID | 20181205175209.26199-4-onemda@gmail.com |
---|---|
State | Accepted |
Headers | show |
Hi Paul On Wed, Dec 5, 2018 at 9:52 AM Paul B Mahol <onemda@gmail.com> wrote: > Signed-off-by: Paul B Mahol <onemda@gmail.com> > --- > libavformat/mxfenc.c | 49 ++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 49 insertions(+) > > diff --git a/libavformat/mxfenc.c b/libavformat/mxfenc.c > index 3549b4137d..e481b19ff3 100644 > --- a/libavformat/mxfenc.c > +++ b/libavformat/mxfenc.c > @@ -146,6 +146,7 @@ enum ULIndex { > INDEX_JPEG2000, > INDEX_H264, > INDEX_S436M, > + INDEX_PRORES, > }; > > static const struct { > @@ -159,6 +160,7 @@ static const struct { > { AV_CODEC_ID_DNXHD, INDEX_DNXHD }, > { AV_CODEC_ID_JPEG2000, INDEX_JPEG2000 }, > { AV_CODEC_ID_H264, INDEX_H264 }, > + { AV_CODEC_ID_PRORES, INDEX_PRORES }, > { AV_CODEC_ID_NONE } > }; > > @@ -314,6 +316,11 @@ static const MXFContainerEssenceEntry > mxf_essence_container_uls[] = { > { > 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x17,0x01,0x02,0x00 > }, > { > 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x01,0x5C,0x00 > }, > mxf_write_s436m_anc_desc }, > + // ProRes > + { { > 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x0d,0x01,0x03,0x01,0x02,0x1c,0x01,0x00 > }, > + { > 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0d,0x01,0x03,0x01,0x15,0x01,0x17,0x00 > }, > + { > 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x03,0x00 > }, > + mxf_write_cdci_desc }, > { { > 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 > }, > { > 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 > }, > { > 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 > }, > @@ -1945,6 +1952,43 @@ static int mxf_write_partition(AVFormatContext *s, > int bodysid, > return 0; > } > > +static const struct { > + int profile; > + UID codec_ul; > +} mxf_prores_codec_uls[] = { > + { FF_PROFILE_PRORES_PROXY, { > 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x01,0x00 > } }, > + { FF_PROFILE_PRORES_LT, { > 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x02,0x00 > } }, > + { FF_PROFILE_PRORES_STANDARD, { > 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x03,0x00 > } }, > + { FF_PROFILE_PRORES_HQ, { > 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x04,0x00 > } }, > + { FF_PROFILE_PRORES_4444, { > 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x05,0x00 > } }, > + { FF_PROFILE_PRORES_XQ, { > 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x06,0x00 > } }, > +}; > + > +static int mxf_parse_prores_frame(AVFormatContext *s, AVStream *st, > AVPacket *pkt) > +{ > + MXFContext *mxf = s->priv_data; > + MXFStreamContext *sc = st->priv_data; > + int i, profile; > + > + if (mxf->header_written) > + return 1; > + > + sc->codec_ul = NULL; > + profile = st->codecpar->profile; > We should fetch the profile from the bitstream.
On 12/6/18, Baptiste Coudurier <baptiste.coudurier@gmail.com> wrote: > Hi Paul > > On Wed, Dec 5, 2018 at 9:52 AM Paul B Mahol <onemda@gmail.com> wrote: > >> Signed-off-by: Paul B Mahol <onemda@gmail.com> >> --- >> libavformat/mxfenc.c | 49 ++++++++++++++++++++++++++++++++++++++++++++ >> 1 file changed, 49 insertions(+) >> >> diff --git a/libavformat/mxfenc.c b/libavformat/mxfenc.c >> index 3549b4137d..e481b19ff3 100644 >> --- a/libavformat/mxfenc.c >> +++ b/libavformat/mxfenc.c >> @@ -146,6 +146,7 @@ enum ULIndex { >> INDEX_JPEG2000, >> INDEX_H264, >> INDEX_S436M, >> + INDEX_PRORES, >> }; >> >> static const struct { >> @@ -159,6 +160,7 @@ static const struct { >> { AV_CODEC_ID_DNXHD, INDEX_DNXHD }, >> { AV_CODEC_ID_JPEG2000, INDEX_JPEG2000 }, >> { AV_CODEC_ID_H264, INDEX_H264 }, >> + { AV_CODEC_ID_PRORES, INDEX_PRORES }, >> { AV_CODEC_ID_NONE } >> }; >> >> @@ -314,6 +316,11 @@ static const MXFContainerEssenceEntry >> mxf_essence_container_uls[] = { >> { >> 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x17,0x01,0x02,0x00 >> }, >> { >> 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x01,0x5C,0x00 >> }, >> mxf_write_s436m_anc_desc }, >> + // ProRes >> + { { >> 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x0d,0x01,0x03,0x01,0x02,0x1c,0x01,0x00 >> }, >> + { >> 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0d,0x01,0x03,0x01,0x15,0x01,0x17,0x00 >> }, >> + { >> 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x03,0x00 >> }, >> + mxf_write_cdci_desc }, >> { { >> 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 >> }, >> { >> 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 >> }, >> { >> 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 >> }, >> @@ -1945,6 +1952,43 @@ static int mxf_write_partition(AVFormatContext *s, >> int bodysid, >> return 0; >> } >> >> +static const struct { >> + int profile; >> + UID codec_ul; >> +} mxf_prores_codec_uls[] = { >> + { FF_PROFILE_PRORES_PROXY, { >> 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x01,0x00 >> } }, >> + { FF_PROFILE_PRORES_LT, { >> 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x02,0x00 >> } }, >> + { FF_PROFILE_PRORES_STANDARD, { >> 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x03,0x00 >> } }, >> + { FF_PROFILE_PRORES_HQ, { >> 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x04,0x00 >> } }, >> + { FF_PROFILE_PRORES_4444, { >> 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x05,0x00 >> } }, >> + { FF_PROFILE_PRORES_XQ, { >> 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x06,0x00 >> } }, >> +}; >> + >> +static int mxf_parse_prores_frame(AVFormatContext *s, AVStream *st, >> AVPacket *pkt) >> +{ >> + MXFContext *mxf = s->priv_data; >> + MXFStreamContext *sc = st->priv_data; >> + int i, profile; >> + >> + if (mxf->header_written) >> + return 1; >> + >> + sc->codec_ul = NULL; >> + profile = st->codecpar->profile; >> > > We should fetch the profile from the bitstream. > AFAIK profile is not stored in it.
Hi Paul, On Wed, Dec 5, 2018 at 3:18 PM Paul B Mahol <onemda@gmail.com> wrote: > On 12/6/18, Baptiste Coudurier <baptiste.coudurier@gmail.com> wrote: > > Hi Paul > > > > On Wed, Dec 5, 2018 at 9:52 AM Paul B Mahol <onemda@gmail.com> wrote: > > > >> Signed-off-by: Paul B Mahol <onemda@gmail.com> > >> --- > >> libavformat/mxfenc.c | 49 ++++++++++++++++++++++++++++++++++++++++++++ > >> 1 file changed, 49 insertions(+) > >> > >> [...] > >> > >> +static int mxf_parse_prores_frame(AVFormatContext *s, AVStream *st, > >> AVPacket *pkt) > >> +{ > >> + MXFContext *mxf = s->priv_data; > >> + MXFStreamContext *sc = st->priv_data; > >> + int i, profile; > >> + > >> + if (mxf->header_written) > >> + return 1; > >> + > >> + sc->codec_ul = NULL; > >> + profile = st->codecpar->profile; > >> > > > > We should fetch the profile from the bitstream. > > > > AFAIK profile is not stored in it. > Right. My thinking is how would this work going from MOV to MXF ? Or would it work when encoding ? Thanks!
On 12/6/18, Baptiste Coudurier <baptiste.coudurier@gmail.com> wrote: > Hi Paul, > > On Wed, Dec 5, 2018 at 3:18 PM Paul B Mahol <onemda@gmail.com> wrote: > >> On 12/6/18, Baptiste Coudurier <baptiste.coudurier@gmail.com> wrote: >> > Hi Paul >> > >> > On Wed, Dec 5, 2018 at 9:52 AM Paul B Mahol <onemda@gmail.com> wrote: >> > >> >> Signed-off-by: Paul B Mahol <onemda@gmail.com> >> >> --- >> >> libavformat/mxfenc.c | 49 ++++++++++++++++++++++++++++++++++++++++++++ >> >> 1 file changed, 49 insertions(+) >> >> >> >> [...] >> >> >> >> +static int mxf_parse_prores_frame(AVFormatContext *s, AVStream *st, >> >> AVPacket *pkt) >> >> +{ >> >> + MXFContext *mxf = s->priv_data; >> >> + MXFStreamContext *sc = st->priv_data; >> >> + int i, profile; >> >> + >> >> + if (mxf->header_written) >> >> + return 1; >> >> + >> >> + sc->codec_ul = NULL; >> >> + profile = st->codecpar->profile; >> >> >> > >> > We should fetch the profile from the bitstream. >> > >> >> AFAIK profile is not stored in it. >> > > Right. My thinking is how would this work going from MOV to MXF ? Or would > it work when encoding ? It works when encoding and stream copying, last time I tried.
On Thu, Dec 6, 2018 at 7:45 AM Paul B Mahol <onemda@gmail.com> wrote: > On 12/6/18, Baptiste Coudurier <baptiste.coudurier@gmail.com> wrote: > > Hi Paul, > > > > On Wed, Dec 5, 2018 at 3:18 PM Paul B Mahol <onemda@gmail.com> wrote: > > > >> On 12/6/18, Baptiste Coudurier <baptiste.coudurier@gmail.com> wrote: > >> > Hi Paul > >> > > >> > On Wed, Dec 5, 2018 at 9:52 AM Paul B Mahol <onemda@gmail.com> wrote: > >> > > >> >> Signed-off-by: Paul B Mahol <onemda@gmail.com> > >> >> --- > >> >> libavformat/mxfenc.c | 49 > ++++++++++++++++++++++++++++++++++++++++++++ > >> >> 1 file changed, 49 insertions(+) > >> >> > >> >> [...] > >> >> > >> >> +static int mxf_parse_prores_frame(AVFormatContext *s, AVStream *st, > >> >> AVPacket *pkt) > >> >> +{ > >> >> + MXFContext *mxf = s->priv_data; > >> >> + MXFStreamContext *sc = st->priv_data; > >> >> + int i, profile; > >> >> + > >> >> + if (mxf->header_written) > >> >> + return 1; > >> >> + > >> >> + sc->codec_ul = NULL; > >> >> + profile = st->codecpar->profile; > >> >> > >> > > >> > We should fetch the profile from the bitstream. > >> > > >> > >> AFAIK profile is not stored in it. > >> > > > > Right. My thinking is how would this work going from MOV to MXF ? Or > would > > it work when encoding ? > > It works when encoding and stream copying, last time I tried. > After looking at the code, it seems like it would work assuming essence comes from MXF or MOV, raw would not work though but we can accept that. So I think the patch is fine, as well as the demuxer one, could you please apply the demuxer one first ? Thanks!
On 12/6/18, Baptiste Coudurier <baptiste.coudurier@gmail.com> wrote: > On Thu, Dec 6, 2018 at 7:45 AM Paul B Mahol <onemda@gmail.com> wrote: > >> On 12/6/18, Baptiste Coudurier <baptiste.coudurier@gmail.com> wrote: >> > Hi Paul, >> > >> > On Wed, Dec 5, 2018 at 3:18 PM Paul B Mahol <onemda@gmail.com> wrote: >> > >> >> On 12/6/18, Baptiste Coudurier <baptiste.coudurier@gmail.com> wrote: >> >> > Hi Paul >> >> > >> >> > On Wed, Dec 5, 2018 at 9:52 AM Paul B Mahol <onemda@gmail.com> wrote: >> >> > >> >> >> Signed-off-by: Paul B Mahol <onemda@gmail.com> >> >> >> --- >> >> >> libavformat/mxfenc.c | 49 >> ++++++++++++++++++++++++++++++++++++++++++++ >> >> >> 1 file changed, 49 insertions(+) >> >> >> >> >> >> [...] >> >> >> >> >> >> +static int mxf_parse_prores_frame(AVFormatContext *s, AVStream *st, >> >> >> AVPacket *pkt) >> >> >> +{ >> >> >> + MXFContext *mxf = s->priv_data; >> >> >> + MXFStreamContext *sc = st->priv_data; >> >> >> + int i, profile; >> >> >> + >> >> >> + if (mxf->header_written) >> >> >> + return 1; >> >> >> + >> >> >> + sc->codec_ul = NULL; >> >> >> + profile = st->codecpar->profile; >> >> >> >> >> > >> >> > We should fetch the profile from the bitstream. >> >> > >> >> >> >> AFAIK profile is not stored in it. >> >> >> > >> > Right. My thinking is how would this work going from MOV to MXF ? Or >> would >> > it work when encoding ? >> >> It works when encoding and stream copying, last time I tried. >> > > After looking at the code, it seems like it would work assuming essence > comes from MXF or MOV, > raw would not work though but we can accept that. > > So I think the patch is fine, as well as the demuxer one, could you please > apply the demuxer one first ? It is listed before muxer anyway, muxer patch is last one. Or there is something I'm missing.
On 12/6/18, Paul B Mahol <onemda@gmail.com> wrote: > On 12/6/18, Baptiste Coudurier <baptiste.coudurier@gmail.com> wrote: >> On Thu, Dec 6, 2018 at 7:45 AM Paul B Mahol <onemda@gmail.com> wrote: >> >>> On 12/6/18, Baptiste Coudurier <baptiste.coudurier@gmail.com> wrote: >>> > Hi Paul, >>> > >>> > On Wed, Dec 5, 2018 at 3:18 PM Paul B Mahol <onemda@gmail.com> wrote: >>> > >>> >> On 12/6/18, Baptiste Coudurier <baptiste.coudurier@gmail.com> wrote: >>> >> > Hi Paul >>> >> > >>> >> > On Wed, Dec 5, 2018 at 9:52 AM Paul B Mahol <onemda@gmail.com> >>> >> > wrote: >>> >> > >>> >> >> Signed-off-by: Paul B Mahol <onemda@gmail.com> >>> >> >> --- >>> >> >> libavformat/mxfenc.c | 49 >>> ++++++++++++++++++++++++++++++++++++++++++++ >>> >> >> 1 file changed, 49 insertions(+) >>> >> >> >>> >> >> [...] >>> >> >> >>> >> >> +static int mxf_parse_prores_frame(AVFormatContext *s, AVStream >>> >> >> *st, >>> >> >> AVPacket *pkt) >>> >> >> +{ >>> >> >> + MXFContext *mxf = s->priv_data; >>> >> >> + MXFStreamContext *sc = st->priv_data; >>> >> >> + int i, profile; >>> >> >> + >>> >> >> + if (mxf->header_written) >>> >> >> + return 1; >>> >> >> + >>> >> >> + sc->codec_ul = NULL; >>> >> >> + profile = st->codecpar->profile; >>> >> >> >>> >> > >>> >> > We should fetch the profile from the bitstream. >>> >> > >>> >> >>> >> AFAIK profile is not stored in it. >>> >> >>> > >>> > Right. My thinking is how would this work going from MOV to MXF ? Or >>> would >>> > it work when encoding ? >>> >>> It works when encoding and stream copying, last time I tried. >>> >> >> After looking at the code, it seems like it would work assuming essence >> comes from MXF or MOV, >> raw would not work though but we can accept that. >> >> So I think the patch is fine, as well as the demuxer one, could you >> please >> apply the demuxer one first ? > > It is listed before muxer anyway, muxer patch is last one. > Or there is something I'm missing. > will apply.
diff --git a/libavformat/mxfenc.c b/libavformat/mxfenc.c index 3549b4137d..e481b19ff3 100644 --- a/libavformat/mxfenc.c +++ b/libavformat/mxfenc.c @@ -146,6 +146,7 @@ enum ULIndex { INDEX_JPEG2000, INDEX_H264, INDEX_S436M, + INDEX_PRORES, }; static const struct { @@ -159,6 +160,7 @@ static const struct { { AV_CODEC_ID_DNXHD, INDEX_DNXHD }, { AV_CODEC_ID_JPEG2000, INDEX_JPEG2000 }, { AV_CODEC_ID_H264, INDEX_H264 }, + { AV_CODEC_ID_PRORES, INDEX_PRORES }, { AV_CODEC_ID_NONE } }; @@ -314,6 +316,11 @@ static const MXFContainerEssenceEntry mxf_essence_container_uls[] = { { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0D,0x01,0x03,0x01,0x17,0x01,0x02,0x00 }, { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0a,0x04,0x01,0x02,0x02,0x01,0x01,0x5C,0x00 }, mxf_write_s436m_anc_desc }, + // ProRes + { { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x0d,0x01,0x03,0x01,0x02,0x1c,0x01,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x01,0x02,0x01,0x01,0x0d,0x01,0x03,0x01,0x15,0x01,0x17,0x00 }, + { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x03,0x00 }, + mxf_write_cdci_desc }, { { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, @@ -1945,6 +1952,43 @@ static int mxf_write_partition(AVFormatContext *s, int bodysid, return 0; } +static const struct { + int profile; + UID codec_ul; +} mxf_prores_codec_uls[] = { + { FF_PROFILE_PRORES_PROXY, { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x01,0x00 } }, + { FF_PROFILE_PRORES_LT, { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x02,0x00 } }, + { FF_PROFILE_PRORES_STANDARD, { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x03,0x00 } }, + { FF_PROFILE_PRORES_HQ, { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x04,0x00 } }, + { FF_PROFILE_PRORES_4444, { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x05,0x00 } }, + { FF_PROFILE_PRORES_XQ, { 0x06,0x0E,0x2B,0x34,0x04,0x01,0x01,0x0d,0x04,0x01,0x02,0x02,0x03,0x06,0x06,0x00 } }, +}; + +static int mxf_parse_prores_frame(AVFormatContext *s, AVStream *st, AVPacket *pkt) +{ + MXFContext *mxf = s->priv_data; + MXFStreamContext *sc = st->priv_data; + int i, profile; + + if (mxf->header_written) + return 1; + + sc->codec_ul = NULL; + profile = st->codecpar->profile; + for (i = 0; i < FF_ARRAY_ELEMS(mxf_prores_codec_uls); i++) { + if (profile == mxf_prores_codec_uls[i].profile) { + sc->codec_ul = &mxf_prores_codec_uls[i].codec_ul; + break; + } + } + if (!sc->codec_ul) + return 0; + + sc->frame_size = pkt->size; + + return 1; +} + static const struct { int cid; UID codec_ul; @@ -2736,6 +2780,11 @@ static int mxf_write_packet(AVFormatContext *s, AVPacket *pkt) av_log(s, AV_LOG_ERROR, "could not get dnxhd profile\n"); return -1; } + } else if (st->codecpar->codec_id == AV_CODEC_ID_PRORES) { + if (!mxf_parse_prores_frame(s, st, pkt)) { + av_log(s, AV_LOG_ERROR, "could not get prores profile\n"); + return -1; + } } else if (st->codecpar->codec_id == AV_CODEC_ID_DVVIDEO) { if (!mxf_parse_dv_frame(s, st, pkt)) { av_log(s, AV_LOG_ERROR, "could not get dv profile\n");
Signed-off-by: Paul B Mahol <onemda@gmail.com> --- libavformat/mxfenc.c | 49 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+)