commit 4c87b07a0c8b907bd0adeede7515ec56f8ac57ed Author: Zoe Roux Date: Fri Dec 20 19:09:22 2019 +0000 Cleaning the repo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..568de19 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/* +cmake-build-debug/* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6055287 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ffmpeg"] + path = ffmpeg + url = https://git.ffmpeg.org/ffmpeg.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..af75163 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.15) + +set (PROJECT_VERSION "1.0") +project(Kyoo.Transcoder VERSION ${PROJECT_VERSION}) + +include_directories(ffmpeg) +include_directories(ffmpeg/include) +include_directories(ffmpeg/include/libavcodec) +include_directories(ffmpeg/include/libavdevice) +include_directories(ffmpeg/include/libavfilter) +include_directories(ffmpeg/include/libavformat) +include_directories(ffmpeg/include/libavutil) +include_directories(ffmpeg/include/libpostproc) +include_directories(ffmpeg/include/libswresample) +include_directories(ffmpeg/include/libswscale) +include_directories(include) + +add_library(Kyoo.Transcoder SHARED + src/helper.c + src/subtitles.c) \ No newline at end of file diff --git a/ffmpeg b/ffmpeg new file mode 160000 index 0000000..a86bb2f --- /dev/null +++ b/ffmpeg @@ -0,0 +1 @@ +Subproject commit a86bb2f606da64d287b1dd807a4f691c39f1a1ec diff --git a/include/compatibility.h b/include/compatibility.h new file mode 100644 index 0000000..3929da4 --- /dev/null +++ b/include/compatibility.h @@ -0,0 +1,17 @@ +// +// Created by Anonymus Raccoon on 16/12/2019. +// + +#pragma once + +#include + +#ifdef __WIN32__ + #define kmkdir(dir, mode) mkdir(dir) +#else + #define kmkdir(dir, mode) mkdir(dir, mode) +#endif + +#ifdef __MINGW32__ + #define asprintf __mingw_asprintf +#endif \ No newline at end of file diff --git a/include/export.h b/include/export.h new file mode 100644 index 0000000..36dec34 --- /dev/null +++ b/include/export.h @@ -0,0 +1,11 @@ +// +// Created by Anonymus Raccoon on 15/12/2019. +// + +#pragma once + +#if defined _WIN32 || defined __CYGWIN__ + #define API __declspec(dllexport) +#else + #define API +#endif \ No newline at end of file diff --git a/include/helper.h b/include/helper.h new file mode 100644 index 0000000..719c8bf --- /dev/null +++ b/include/helper.h @@ -0,0 +1,14 @@ +// +// Created by Anonymus Raccoon on 15/12/2019. +// + +#pragma once + +#include "libavformat/avformat.h" +#include "libavutil/dict.h" +#include "libavutil/timestamp.h" + +int open_input_context(AVFormatContext **inputContext, const char *path); +AVStream *copy_stream_to_output(AVFormatContext *out_ctx, AVStream *in_stream); +int open_output_file_for_write(AVFormatContext *out_ctx, const char *out_path, AVDictionary **options); +void process_packet(AVPacket *pkt, AVStream *in_stream, AVStream *out_stream); \ No newline at end of file diff --git a/include/stream.h b/include/stream.h new file mode 100644 index 0000000..1ed712a --- /dev/null +++ b/include/stream.h @@ -0,0 +1,22 @@ +#pragma once +#include +#include + +typedef struct stream +{ + char *title; + char *language; + char *codec; + bool is_default; + bool is_forced; + char *path; +} stream; + +#define NULLSTREAM (struct stream) { \ + NULL, \ + NULL, \ + NULL, \ + false, \ + false, \ + NULL \ +} diff --git a/include/transcoder.h b/include/transcoder.h new file mode 100644 index 0000000..d55ae68 --- /dev/null +++ b/include/transcoder.h @@ -0,0 +1,14 @@ +#pragma once +#include "export.h" +#include "stream.h" + +//API int transmux(const char *path, const char *out_path, float *playable_duration); +// +//API int transcode(const char *path, const char *out_path, float *playable_duration); +// +//API stream *get_track_info(const char *path, int *stream_count, int *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 void free_memory(stream *streamsPtr); \ No newline at end of file diff --git a/src/helper.c b/src/helper.c new file mode 100644 index 0000000..defc20d --- /dev/null +++ b/src/helper.c @@ -0,0 +1,64 @@ +#include +#include +#include "helper.h" + +int open_input_context(AVFormatContext **in_ctx, const char *path) +{ + if (avformat_open_input(in_ctx, path, NULL, NULL)) { + printf("Error: Can't open the file at %s.\n", path); + return (1); + } + if (avformat_find_stream_info(*in_ctx, NULL) < 0) { + printf("Error: Could't find streams informations for the file at %s.\n", path); + return (1); + } + av_dump_format(*in_ctx, 0, path, false); + return (0); +} + +AVStream *copy_stream_to_output(AVFormatContext *out_ctx, AVStream *in_stream) +{ + AVStream *out_stream = avformat_new_stream(out_ctx, NULL); + + if (out_stream == NULL) { + printf("Error: Couldn't create stream.\n"); + return (NULL); + } + if (avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar) < 0) { + printf("Error: Couldn't copy parameters to the output file.\n"); + return (NULL); + } + out_stream->codecpar->codec_tag = 0; + avformat_transfer_internal_stream_timing_info(out_ctx->oformat, out_stream, in_stream, AVFMT_TBCF_AUTO); + out_stream->time_base = av_add_q(av_stream_get_codec_timebase(out_stream), (AVRational){0, 1}); + out_stream->duration = av_rescale_q(in_stream->duration, in_stream->time_base, out_stream->time_base); + out_stream->disposition = in_stream->disposition; + out_stream->avg_frame_rate = in_stream->avg_frame_rate; + out_stream->r_frame_rate = in_stream->r_frame_rate; + return (out_stream); +} + +int open_output_file_for_write(AVFormatContext *out_ctx, const char *out_path, AVDictionary **options) +{ + if (!(out_ctx->oformat->flags & AVFMT_NOFILE)) { + if (avio_open(&out_ctx->pb, out_path, AVIO_FLAG_WRITE) < 0) { + printf("Error: Couldn't open file at %s.\n", out_path); + return (1); + } + } + else + printf("Output flag set to AVFMT_NOFILE.\n"); + if (avformat_write_header(out_ctx, options) < 0) { + printf("Error: Couldn't write headers to file at %s.\n", out_path); + return (1); + } + return (0); +} + +void process_packet(AVPacket *pkt, AVStream *in_stream, AVStream *out_stream) +{ + pkt->pts = av_rescale_q_rnd(pkt->pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); + pkt->dts = av_rescale_q_rnd(pkt->dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); + pkt->duration = av_rescale_q(pkt->duration, in_stream->time_base, out_stream->time_base); + pkt->pos = -1; +} \ No newline at end of file diff --git a/src/subtitles.c b/src/subtitles.c new file mode 100644 index 0000000..80b2f7d --- /dev/null +++ b/src/subtitles.c @@ -0,0 +1,150 @@ +// +// Created by Anonymus Raccoon on 16/12/2019. +// + +#include "transcoder.h" +#include "stream.h" +#include "helper.h" +#include "compatibility.h" +#include +#include +#include +#include + +int get_subtitle_data(stream *substream, AVStream *in_stream, const char *folder_name, const char *out_path) +{ + AVDictionaryEntry *languageptr = av_dict_get(in_stream->metadata, "language", NULL, 0); + char *extension; + char *codec = strdup(avcodec_get_name(in_stream->codecpar->codec_id)); + + if (!codec) + return (-1); + if (!strcmp(codec, "subrip")) + extension = ".srt"; + else if (!strcmp(codec, "ass")) + extension = ".ass"; + else { + printf("Unsupported subtitle codec: %s.\n", codec); + free(codec); + return (-1); + } + *substream = (stream) { + NULL, + languageptr ? languageptr->value : NULL, + codec, + in_stream->disposition & AV_DISPOSITION_DEFAULT, + in_stream->disposition & AV_DISPOSITION_FORCED, + NULL + }; + asprintf(&substream->path, "%s/%s", out_path, substream->language); + if (!substream->path || kmkdir(substream->path, 0733) < 0) + return (-1); + asprintf(&substream->path, "%s/%s.%s%s%s%s", substream->path, + folder_name, + substream->language, + substream->is_default ? ".default" : "", + substream->is_forced ? ".forced" : "", + extension); + if (!substream->path) + return (-1); + return (0); +} + +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"); + 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) { + av_dump_format(out_ctx, 0, s->path, true); + 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); + 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) +{ + 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); +} + +stream *extract_subtitles(char *path, const char *out_path, unsigned *stream_count, unsigned *subtitle_count) +{ + AVFormatContext *int_ctx = NULL; + AVFormatContext **output_list; + stream *streams; + char *folder_name = strchr(path, '/'); + char *p; + + if (!folder_name) + return (NULL); + p = strchr(folder_name, '.'); + if (p) + *p = '\0'; + if (open_input_context(&int_ctx, path) != 0) + return (NULL); + *stream_count = int_ctx->nb_streams; + *subtitle_count = 0; + streams = malloc(sizeof(stream) * *stream_count); + output_list = malloc(sizeof(AVFormatContext *) * *stream_count); + for (unsigned int i = 0; i < *stream_count; 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, folder_name, out_path) == 0) { + if (copy_subtitle_stream(output_list[i], streams + i, int_ctx, in_stream) == 0) { + *subtitle_count += 1; + continue; + } + } + } + streams[i] = NULLSTREAM; + output_list[i] = NULL; + } + write_data(int_ctx, output_list, *stream_count); + finish_up(int_ctx, output_list, *stream_count); + return (streams); +} diff --git a/src/transcoder.c b/src/transcoder.c new file mode 100644 index 0000000..995ce0b --- /dev/null +++ b/src/transcoder.c @@ -0,0 +1,139 @@ +#pragma clang diagnostic push +#pragma ide diagnostic ignored "hicpp-signed-bitwise" +#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions" +#include "transcoder.h" +#include "helper.h" +#include "compatibility.h" + + +stream *get_track_info(const char *path, int *stream_count, int *track_count) +{ + AVFormatContext *ctx = NULL; + stream *streams; + + if (open_input_context(&ctx, path) != 0) + return (NULL); + *stream_count = ctx->nb_streams; + *track_count = 0; + streams = malloc(sizeof(stream) * *stream_count); + + for (int i = 0; i < *stream_count; i++) { + AVStream *stream = ctx->streams[i]; + const AVCodecParameters *codecpar = stream->codecpar; + + if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO || codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + AVDictionaryEntry *languageptr = av_dict_get(stream->metadata, "language", NULL, 0); + + *track_count += 1; + streams[i] = (struct stream){ + codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? "VIDEO" : 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) + }; + } + else + streams[i] = NULLSTREAM; + } + avformat_close_input(&ctx); + return (streams); +} + +int transmux(const char *path, const char *out_path, float *playable_duration) +{ + AVFormatContext *in_ctx = NULL; + AVFormatContext *out_ctx = NULL; + AVStream *stream; + AVPacket pkt; + AVDictionary *options = NULL; + int *stream_map; + int stream_count; + int ret = 0; + std::string seg_path = ((std::string)out_path).substr(0, strrchr(out_path, '/') - out_path).append("/segments/"); + + *playable_duration = 0; + if (open_input_context(&in_ctx, path) != 0) + return 1; + + if (avformat_alloc_output_context2(&out_ctx, NULL, NULL, out_path) < 0) + { + std::cout << "Error: Couldn't create an output file." << std::endl; + return 1; + } + + stream_map = new int[in_ctx->nb_streams]; + stream_count = 0; + + for (unsigned int i = 0; i < in_ctx->nb_streams; i++) + { + stream = in_ctx->streams[i]; + if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) + { + stream_map[i] = stream_count; + stream_count++; + if (copy_stream_to_output(out_ctx, stream) == NULL) + return 1; + } + else if (stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) //Should support multi-audio on a good format. + { + stream_map[i] = stream_count; + stream_count++; + if (copy_stream_to_output(out_ctx, stream) == NULL) + return 1; + } + else + stream_map[i] = -1; + } + + av_dump_format(out_ctx, 0, out_path, true); + std::filesystem::create_directory(seg_path); + av_dict_set(&options, "hls_segment_filename", seg_path.append("%v-%03d.ts").c_str(), 0); + av_dict_set(&options, "hls_base_url", "segment/", 0); + av_dict_set(&options, "hls_list_size", "0", 0); + av_dict_set(&options, "streaming", "1", 0); + + if (open_output_file_for_write(out_ctx, out_path, &options) != 0) + return 1; + + while (av_read_frame(in_ctx, &pkt) == 0) + { + if ((unsigned int)pkt.stream_index >= in_ctx->nb_streams || stream_map[pkt.stream_index] < 0) + { + av_packet_unref(&pkt); + continue; + } + + stream = in_ctx->streams[pkt.stream_index]; + pkt.stream_index = stream_map[pkt.stream_index]; + process_packet(pkt, stream, out_ctx->streams[pkt.stream_index]); + if (pkt.stream_index == 0) + *playable_duration += pkt.duration * (float)out_ctx->streams[pkt.stream_index]->time_base.num / out_ctx->streams[pkt.stream_index]->time_base.den; + + if (av_interleaved_write_frame(out_ctx, &pkt) < 0) + std::cout << "Error while writing a packet to the output file." << std::endl; + + av_packet_unref(&pkt); + } + + av_dict_free(&options); + av_write_trailer(out_ctx); + avformat_close_input(&in_ctx); + + if (out_ctx && !(out_ctx->oformat->flags & AVFMT_NOFILE)) + avio_close(out_ctx->pb); + avformat_free_context(out_ctx); + delete[] stream_map; + + if (ret < 0 && ret != AVERROR_EOF) + return 1; + + return 0; +} + +void free_memory(Stream *stream_ptr) +{ + delete[] stream_ptr; +} +#pragma clang diagnostic pop \ No newline at end of file