mirror of
https://github.com/zoriya/Kyoo.Transcoder.git
synced 2025-12-06 06:26:11 +00:00
Starting to rework the subtitle extraction to support fonts & only open the file one time
This commit is contained in:
@@ -9,11 +9,11 @@ include_directories(include)
|
||||
|
||||
add_library(transcoder SHARED
|
||||
src/helper.c
|
||||
src/subtitles.c
|
||||
src/info.c
|
||||
src/transmuxer.c
|
||||
src/path_helper.c
|
||||
src/destroyer.c)
|
||||
src/destroyer.c
|
||||
src/subtitles.c)
|
||||
set_property(TARGET transcoder PROPERTY C_STANDARD 11)
|
||||
|
||||
target_link_libraries(transcoder
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define _GNU_SOURCE // For asprintf
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __WIN32__
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
//
|
||||
// Created by Anonymus Raccoon on 16/12/2019.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
typedef enum
|
||||
{
|
||||
none = 0,
|
||||
video = 1,
|
||||
audio = 2,
|
||||
subtitle = 3
|
||||
subtitle = 3,
|
||||
font = 4
|
||||
} type;
|
||||
|
||||
typedef struct stream
|
||||
@@ -30,3 +36,9 @@ typedef struct stream
|
||||
NULL, \
|
||||
none \
|
||||
}
|
||||
|
||||
void extract_subtitle(stream *subtitle,
|
||||
const char *out_path,
|
||||
AVStream *stream,
|
||||
AVFormatContext *in_ctx,
|
||||
AVFormatContext *out_ctx);
|
||||
@@ -6,12 +6,7 @@ API int transmux(const char *path, const char *out_path, float *playable_duratio
|
||||
|
||||
//API int transcode(const char *path, const char *out_path, float *playable_duration);
|
||||
|
||||
API stream *get_track_info(const char *path, unsigned *stream_count, unsigned *track_count);
|
||||
|
||||
//Take the path of the file and the path of the output directory.
|
||||
// It will return the list of subtitle streams in the streams variable.
|
||||
// The int returned is the number of subtitles extracted.
|
||||
API stream *extract_subtitles(char *path, const char *out_path, unsigned *stream_count, unsigned *subtitle_count);
|
||||
API stream *extract_info(const char *path, const char *out_path, unsigned *stream_count, unsigned *track_count);
|
||||
|
||||
void destroy_stream(stream *s);
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ type type_fromffmpeg(int type)
|
||||
return audio;
|
||||
case AVMEDIA_TYPE_SUBTITLE:
|
||||
return subtitle;
|
||||
case AVMEDIA_TYPE_ATTACHMENT:
|
||||
return font;
|
||||
default:
|
||||
return none;
|
||||
}
|
||||
|
||||
90
src/info.c
90
src/info.c
@@ -12,37 +12,83 @@ int init()
|
||||
return sizeof(stream);
|
||||
}
|
||||
|
||||
stream *get_track_info(const char *path, unsigned *stream_count, unsigned *track_count)
|
||||
void write_to_outputs(AVFormatContext **output_list, AVFormatContext *in_ctx)
|
||||
{
|
||||
AVPacket pkt;
|
||||
|
||||
while (av_read_frame(in_ctx, &pkt) == 0) {
|
||||
AVFormatContext *out_ctx;
|
||||
|
||||
if ((unsigned)pkt.stream_index >= in_ctx->nb_streams)
|
||||
continue;
|
||||
out_ctx = output_list[pkt.stream_index];
|
||||
if (!out_ctx) {
|
||||
av_packet_unref(&pkt);
|
||||
continue;
|
||||
}
|
||||
process_packet(&pkt, in_ctx->streams[pkt.stream_index], out_ctx->streams[0]);
|
||||
pkt.stream_index = 0;
|
||||
if (av_interleaved_write_frame(out_ctx, &pkt) < 0)
|
||||
fprintf(stderr, "Error while writing a packet to the output file.\n");
|
||||
av_packet_unref(&pkt);
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < in_ctx->nb_streams; i++) {
|
||||
AVFormatContext *out_ctx = output_list[i];
|
||||
|
||||
if (!out_ctx)
|
||||
continue;
|
||||
av_write_trailer(out_ctx);
|
||||
if (!(out_ctx->flags & AVFMT_NOFILE))
|
||||
avio_closep(&out_ctx->pb);
|
||||
avformat_free_context(out_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
stream parse_stream(AVStream *stream, type stream_type, const char *path)
|
||||
{
|
||||
const AVCodecParameters *codecpar = stream->codecpar;
|
||||
AVDictionaryEntry *languageptr = av_dict_get(stream->metadata, "language", NULL, 0);
|
||||
|
||||
return (struct stream){
|
||||
NULL,
|
||||
languageptr ? strdup(languageptr->value) : NULL,
|
||||
strdup(avcodec_get_name(codecpar->codec_id)),
|
||||
stream->disposition & AV_DISPOSITION_DEFAULT,
|
||||
stream->disposition & AV_DISPOSITION_FORCED,
|
||||
strdup(path),
|
||||
stream_type
|
||||
};
|
||||
}
|
||||
|
||||
stream *extract_infos(const char *path, const char *out_path, unsigned *stream_count, unsigned *track_count)
|
||||
{
|
||||
AVFormatContext *ctx = NULL;
|
||||
stream *streams;
|
||||
AVFormatContext **output_list;
|
||||
stream *streams = calloc(ctx->nb_streams, sizeof(stream));
|
||||
|
||||
if (open_input_context(&ctx, path) != 0)
|
||||
return NULL;
|
||||
if (!streams || open_input_context(&ctx, path) != 0)
|
||||
return free(streams), NULL;
|
||||
*stream_count = ctx->nb_streams;
|
||||
*track_count = 0;
|
||||
streams = malloc(sizeof(stream) * *stream_count);
|
||||
for (unsigned i = 0; i < *stream_count; i++) {
|
||||
AVStream *stream = ctx->streams[i];
|
||||
const AVCodecParameters *codecpar = stream->codecpar;
|
||||
output_list = calloc(ctx->nb_streams, sizeof(AVFormatContext *));
|
||||
|
||||
if (type_fromffmpeg(codecpar->codec_type) != none) {
|
||||
AVDictionaryEntry *languageptr = av_dict_get(stream->metadata, "language", NULL, 0);
|
||||
if (output_list) {
|
||||
for (unsigned i = 0; i < *stream_count; i++) {
|
||||
AVStream *stream = ctx->streams[i];
|
||||
type stream_type = type_fromffmpeg(stream->codecpar->codec_type);
|
||||
|
||||
*track_count += 1;
|
||||
streams[i] = (struct stream){
|
||||
NULL,
|
||||
languageptr ? strdup(languageptr->value) : NULL,
|
||||
strdup(avcodec_get_name(codecpar->codec_id)),
|
||||
stream->disposition & AV_DISPOSITION_DEFAULT,
|
||||
stream->disposition & AV_DISPOSITION_FORCED,
|
||||
strdup(path),
|
||||
type_fromffmpeg(codecpar->codec_type)
|
||||
};
|
||||
if (stream_type != none) {
|
||||
*track_count += 1;
|
||||
streams[i] = parse_stream(stream, stream_type, path);
|
||||
if (stream_type == subtitle)
|
||||
extract_subtitle(&streams[i], out_path, stream, ctx, output_list[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
streams[i] = NULLSTREAM;
|
||||
}
|
||||
write_to_outputs(output_list, ctx);
|
||||
avformat_close_input(&ctx);
|
||||
if (!output_list)
|
||||
return free(streams), NULL;
|
||||
return streams;
|
||||
}
|
||||
140
src/old_subtitles.c
Normal file
140
src/old_subtitles.c
Normal file
@@ -0,0 +1,140 @@
|
||||
//
|
||||
// Created by Anonymus Raccoon on 16/12/2019.
|
||||
//
|
||||
|
||||
#include "stream.h"
|
||||
#include "helper.h"
|
||||
#include "path_helper.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <transcoder.h>
|
||||
|
||||
int get_subtitle_data(stream *substream, AVStream *in_stream, const char *file_name, const char *out_path)
|
||||
{
|
||||
AVDictionaryEntry *languageptr = av_dict_get(in_stream->metadata, "language", NULL, 0);
|
||||
char *codec = strdup(avcodec_get_name(in_stream->codecpar->codec_id));
|
||||
char *extension = get_extension_from_codec(codec);
|
||||
char *folder_path;
|
||||
|
||||
if (!extension)
|
||||
return -1;
|
||||
*substream = (stream) {
|
||||
NULL,
|
||||
languageptr ? strdup(languageptr->value) : NULL,
|
||||
codec,
|
||||
in_stream->disposition & AV_DISPOSITION_DEFAULT,
|
||||
in_stream->disposition & AV_DISPOSITION_FORCED,
|
||||
NULL,
|
||||
subtitle
|
||||
};
|
||||
asprintf(&folder_path, "%s/%s", out_path, substream->language);
|
||||
if (path_mkdir(folder_path, 0733) < 0) {
|
||||
if (!folder_path)
|
||||
free(folder_path);
|
||||
return -1;
|
||||
}
|
||||
asprintf(&substream->path, "%s/%s.%s%s%s%s", folder_path,
|
||||
file_name,
|
||||
substream->language,
|
||||
substream->is_default ? ".default" : "",
|
||||
substream->is_forced ? ".forced" : "",
|
||||
extension);
|
||||
free(folder_path);
|
||||
if (!substream->path)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void write_data(AVFormatContext *int_ctx, AVFormatContext **output_list, unsigned int out_count)
|
||||
{
|
||||
AVPacket pkt;
|
||||
|
||||
while (av_read_frame(int_ctx, &pkt) == 0) {
|
||||
AVFormatContext *out_ctx;
|
||||
|
||||
if ((unsigned)pkt.stream_index >= out_count)
|
||||
continue;
|
||||
out_ctx = output_list[pkt.stream_index];
|
||||
if (!out_ctx) {
|
||||
av_packet_unref(&pkt);
|
||||
continue;
|
||||
}
|
||||
process_packet(&pkt, int_ctx->streams[pkt.stream_index], out_ctx->streams[0]);
|
||||
pkt.stream_index = 0;
|
||||
if (av_interleaved_write_frame(out_ctx, &pkt) < 0)
|
||||
fprintf(stderr, "Error while writing a packet to the output file.\n");
|
||||
av_packet_unref(&pkt);
|
||||
}
|
||||
}
|
||||
|
||||
void finish_up(AVFormatContext *int_ctx, AVFormatContext **output_list, unsigned out_count)
|
||||
{
|
||||
avformat_close_input(&int_ctx);
|
||||
|
||||
for (unsigned i = 0; i < out_count; i++) {
|
||||
AVFormatContext *out_ctx = output_list[i];
|
||||
|
||||
if (!out_ctx)
|
||||
continue;
|
||||
av_write_trailer(out_ctx);
|
||||
if (!(out_ctx->flags & AVFMT_NOFILE))
|
||||
avio_closep(&out_ctx->pb);
|
||||
avformat_free_context(out_ctx);
|
||||
}
|
||||
free(output_list);
|
||||
}
|
||||
|
||||
int split_inputfile(AVFormatContext *int_ctx, AVFormatContext **output_list, stream *streams, char *path, const char *out_path)
|
||||
{
|
||||
int subcount = 0;
|
||||
char *file_name = path_getfilename(path);
|
||||
|
||||
if (!file_name)
|
||||
return -1;
|
||||
for (unsigned int i = 0; i < int_ctx->nb_streams; i++) {
|
||||
AVStream *in_stream = int_ctx->streams[i];
|
||||
|
||||
if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
|
||||
if (get_subtitle_data(streams + i, in_stream, file_name, out_path) == 0 &&
|
||||
copy_subtitle_stream(&output_list[i], streams + i, int_ctx, in_stream) == 0) {
|
||||
subcount += 1;
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr,"Couldn't copy the %s subtitle to output\n", streams[i].language);
|
||||
destroy_stream(&streams[i]);
|
||||
}
|
||||
streams[i] = NULLSTREAM;
|
||||
output_list[i] = NULL;
|
||||
}
|
||||
free(file_name);
|
||||
return subcount;
|
||||
}
|
||||
|
||||
stream *extract_subtitles(char *path, const char *out_path, unsigned *stream_count, unsigned *subtitle_count)
|
||||
{
|
||||
AVFormatContext *int_ctx = NULL;
|
||||
AVFormatContext **output_list;
|
||||
stream *streams;
|
||||
|
||||
if (open_input_context(&int_ctx, path) != 0)
|
||||
return NULL;
|
||||
*stream_count = int_ctx->nb_streams;
|
||||
streams = calloc(sizeof(stream), *stream_count);
|
||||
output_list = malloc(sizeof(AVFormatContext *) * int_ctx->nb_streams);
|
||||
if (streams && output_list) {
|
||||
*subtitle_count = split_inputfile(int_ctx, output_list, streams, path, out_path);
|
||||
if (*subtitle_count >= 0) {
|
||||
write_data(int_ctx, output_list, *stream_count);
|
||||
finish_up(int_ctx, output_list, *stream_count);
|
||||
return streams;
|
||||
}
|
||||
}
|
||||
*subtitle_count = 0;
|
||||
if (streams)
|
||||
free_streams(streams, (int)*stream_count);
|
||||
if (output_list)
|
||||
free(output_list);
|
||||
avformat_close_input(&int_ctx);
|
||||
return NULL;
|
||||
}
|
||||
@@ -51,12 +51,11 @@ char *get_extension_from_codec(char *codec)
|
||||
if (!codec)
|
||||
return NULL;
|
||||
if (!strcmp(codec, "subrip"))
|
||||
return(".srt");
|
||||
return ".srt";
|
||||
else if (!strcmp(codec, "ass"))
|
||||
return(".ass");
|
||||
return ".ass";
|
||||
else {
|
||||
printf("Unsupported subtitle codec: %s.\n", codec);
|
||||
free(codec);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
184
src/subtitles.c
184
src/subtitles.c
@@ -1,162 +1,72 @@
|
||||
//
|
||||
// Created by Anonymus Raccoon on 16/12/2019.
|
||||
// Created by anonymus-raccoon on 10/27/20.
|
||||
//
|
||||
|
||||
#include "compatibility.h"
|
||||
#include "path_helper.h"
|
||||
#include "stream.h"
|
||||
#include "helper.h"
|
||||
#include "path_helper.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <transcoder.h>
|
||||
#include <malloc.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int asprintf(char **strp, const char *fmt, ...);
|
||||
|
||||
int get_subtitle_data(stream *substream, AVStream *in_stream, const char *file_name, const char *out_path)
|
||||
// @return -2 on error, -1 if subtitle has alreaady been extracted, 0 on success.
|
||||
int create_out_path(stream *subtitle, const char *out_path)
|
||||
{
|
||||
AVDictionaryEntry *languageptr = av_dict_get(in_stream->metadata, "language", NULL, 0);
|
||||
char *codec = strdup(avcodec_get_name(in_stream->codecpar->codec_id));
|
||||
char *extension = get_extension_from_codec(codec);
|
||||
char *extension = get_extension_from_codec(subtitle->codec);
|
||||
char *folder_path;
|
||||
char *file_name;
|
||||
|
||||
if (!extension)
|
||||
return -1;
|
||||
*substream = (stream) {
|
||||
NULL,
|
||||
languageptr ? strdup(languageptr->value) : NULL,
|
||||
codec,
|
||||
in_stream->disposition & AV_DISPOSITION_DEFAULT,
|
||||
in_stream->disposition & AV_DISPOSITION_FORCED,
|
||||
NULL,
|
||||
subtitle
|
||||
};
|
||||
asprintf(&folder_path, "%s/%s", out_path, substream->language);
|
||||
return -2;
|
||||
file_name = path_getfilename(subtitle->path);
|
||||
asprintf(&folder_path, "%s/%s", out_path, subtitle->language);
|
||||
if (path_mkdir(folder_path, 0733) < 0) {
|
||||
if (!folder_path)
|
||||
free(folder_path);
|
||||
return -1;
|
||||
free(folder_path);
|
||||
free(file_name);
|
||||
return -2;
|
||||
}
|
||||
asprintf(&substream->path, "%s/%s.%s%s%s%s", folder_path,
|
||||
file_name,
|
||||
substream->language,
|
||||
substream->is_default ? ".default" : "",
|
||||
substream->is_forced ? ".forced" : "",
|
||||
extension);
|
||||
asprintf(&subtitle->path, "%s/%s.%s%s%s%s", folder_path,
|
||||
file_name,
|
||||
subtitle->language,
|
||||
subtitle->is_default ? ".default" : "",
|
||||
subtitle->is_forced ? ".forced" : "",
|
||||
extension);
|
||||
free(folder_path);
|
||||
if (!substream->path)
|
||||
return -1;
|
||||
return 0;
|
||||
free(file_name);
|
||||
if (!subtitle->path)
|
||||
return -2;
|
||||
return access(subtitle->path, F_OK) == 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
int copy_subtitle_stream(AVFormatContext **out_ctx, stream *s, AVFormatContext *int_ctx, AVStream *in_stream)
|
||||
int copy_subtitle_stream(AVFormatContext *out_ctx, stream *s, AVFormatContext *int_ctx, AVStream *in_stream)
|
||||
{
|
||||
AVStream *out_stream = NULL;
|
||||
|
||||
if (avformat_alloc_output_context2(out_ctx, NULL, NULL, s->path) < 0) {
|
||||
printf("Error: Couldn't create an output file.\n");
|
||||
if (avformat_alloc_output_context2(&out_ctx, NULL, NULL, s->path) < 0) {
|
||||
fprintf(stderr, "Error: Couldn't create an output file.\n");
|
||||
return -1;
|
||||
}
|
||||
av_dict_copy(&(*out_ctx)->metadata, int_ctx->metadata, 0);
|
||||
out_stream = copy_stream_to_output(*out_ctx, in_stream);
|
||||
if (out_stream) {
|
||||
if (open_output_file_for_write(*out_ctx, s->path, NULL) == 0)
|
||||
return 0;
|
||||
}
|
||||
if (*out_ctx && !((*out_ctx)->flags & AVFMT_NOFILE))
|
||||
avio_closep(&(*out_ctx)->pb);
|
||||
avformat_free_context(*out_ctx);
|
||||
|
||||
av_dict_copy(&out_ctx->metadata, int_ctx->metadata, 0);
|
||||
out_stream = copy_stream_to_output(out_ctx, in_stream);
|
||||
if (out_stream && open_output_file_for_write(out_ctx, s->path, NULL) == 0)
|
||||
return 0;
|
||||
|
||||
if (out_ctx && !(out_ctx->flags & AVFMT_NOFILE))
|
||||
avio_closep(&out_ctx->pb);
|
||||
avformat_free_context(out_ctx);
|
||||
fprintf(stderr, "An error occured, cleaning up th output context for the %s stream.\n", s->language);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void write_data(AVFormatContext *int_ctx, AVFormatContext **output_list, unsigned int out_count)
|
||||
void extract_subtitle(stream *subtitle,
|
||||
const char *out_path,
|
||||
AVStream *stream,
|
||||
AVFormatContext *in_ctx,
|
||||
AVFormatContext *out_ctx)
|
||||
{
|
||||
AVPacket pkt;
|
||||
|
||||
while (av_read_frame(int_ctx, &pkt) == 0) {
|
||||
AVFormatContext *out_ctx;
|
||||
|
||||
if ((unsigned)pkt.stream_index >= out_count)
|
||||
continue;
|
||||
out_ctx = output_list[pkt.stream_index];
|
||||
if (!out_ctx) {
|
||||
av_packet_unref(&pkt);
|
||||
continue;
|
||||
}
|
||||
process_packet(&pkt, int_ctx->streams[pkt.stream_index], out_ctx->streams[0]);
|
||||
pkt.stream_index = 0;
|
||||
if (av_interleaved_write_frame(out_ctx, &pkt) < 0)
|
||||
fprintf(stderr, "Error while writing a packet to the output file.\n");
|
||||
av_packet_unref(&pkt);
|
||||
}
|
||||
}
|
||||
|
||||
void finish_up(AVFormatContext *int_ctx, AVFormatContext **output_list, unsigned out_count)
|
||||
{
|
||||
avformat_close_input(&int_ctx);
|
||||
|
||||
for (unsigned i = 0; i < out_count; i++) {
|
||||
AVFormatContext *out_ctx = output_list[i];
|
||||
|
||||
if (!out_ctx)
|
||||
continue;
|
||||
av_write_trailer(out_ctx);
|
||||
if (!(out_ctx->flags & AVFMT_NOFILE))
|
||||
avio_closep(&out_ctx->pb);
|
||||
avformat_free_context(out_ctx);
|
||||
}
|
||||
free(output_list);
|
||||
}
|
||||
|
||||
int split_inputfile(AVFormatContext *int_ctx, AVFormatContext **output_list, stream *streams, char *path, const char *out_path)
|
||||
{
|
||||
int subcount = 0;
|
||||
char *file_name = path_getfilename(path);
|
||||
|
||||
if (!file_name)
|
||||
return -1;
|
||||
for (unsigned int i = 0; i < int_ctx->nb_streams; i++) {
|
||||
AVStream *in_stream = int_ctx->streams[i];
|
||||
|
||||
if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
|
||||
if (get_subtitle_data(streams + i, in_stream, file_name, out_path) == 0 &&
|
||||
copy_subtitle_stream(&output_list[i], streams + i, int_ctx, in_stream) == 0) {
|
||||
subcount += 1;
|
||||
continue;
|
||||
}
|
||||
fprintf(stderr,"Couldn't copy the %s subtitle to output\n", streams[i].language);
|
||||
destroy_stream(&streams[i]);
|
||||
}
|
||||
streams[i] = NULLSTREAM;
|
||||
output_list[i] = NULL;
|
||||
}
|
||||
free(file_name);
|
||||
return subcount;
|
||||
}
|
||||
|
||||
stream *extract_subtitles(char *path, const char *out_path, unsigned *stream_count, unsigned *subtitle_count)
|
||||
{
|
||||
AVFormatContext *int_ctx = NULL;
|
||||
AVFormatContext **output_list;
|
||||
stream *streams;
|
||||
|
||||
if (open_input_context(&int_ctx, path) != 0)
|
||||
return NULL;
|
||||
*stream_count = int_ctx->nb_streams;
|
||||
streams = calloc(sizeof(stream), *stream_count);
|
||||
output_list = malloc(sizeof(AVFormatContext *) * *stream_count);
|
||||
if (streams && output_list) {
|
||||
*subtitle_count = split_inputfile(int_ctx, output_list, streams, path, out_path);
|
||||
if (*subtitle_count >= 0) {
|
||||
write_data(int_ctx, output_list, *stream_count);
|
||||
finish_up(int_ctx, output_list, *stream_count);
|
||||
return streams;
|
||||
}
|
||||
}
|
||||
*subtitle_count = 0;
|
||||
if (streams)
|
||||
free_streams(streams, (int)*stream_count);
|
||||
if (output_list)
|
||||
free(output_list);
|
||||
return NULL;
|
||||
}
|
||||
if (create_out_path(subtitle, out_path) != 0)
|
||||
return;
|
||||
copy_subtitle_stream(out_ctx, subtitle, in_ctx, stream);
|
||||
}
|
||||
Reference in New Issue
Block a user