diff --git a/Kyoo.Transcoder/CMakeLists.txt b/Kyoo.Transcoder/CMakeLists.txt
new file mode 100644
index 00000000..5b3d40eb
--- /dev/null
+++ b/Kyoo.Transcoder/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/transcoder.c src/subtitles.c)
\ No newline at end of file
diff --git a/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj b/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj
deleted file mode 100644
index d3fe88e3..00000000
--- a/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj
+++ /dev/null
@@ -1,195 +0,0 @@
-
-
-
-
- Debug
- Win32
-
-
- Release
- Win32
-
-
- Debug
- x64
-
-
- Release
- x64
-
-
-
- 16.0
- {E5EFA4B2-F09D-4C4F-83DD-A436CD60BB77}
- Win32Proj
- KyooTranscoder
- 10.0
-
-
-
- DynamicLibrary
- true
- v142
- Unicode
-
-
- DynamicLibrary
- false
- v142
- true
- Unicode
-
-
- DynamicLibrary
- true
- v142
- Unicode
-
-
- DynamicLibrary
- false
- v142
- true
- Unicode
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- $(ProjectDir)\ffmpeg\include;./include;$(IncludePath)
- $(ProjectDir)\ffmpeg\lib;$(LibraryPath)
- $(SolutionDir)Kyoo\Transcoder
-
-
- true
- $(SolutionDir)Kyoo\Transcoder
- $(ProjectDir)\ffmpeg\include;./include;$(IncludePath)
- $(ProjectDir)\ffmpeg\lib;$(LibraryPath)
-
-
- false
- $(ProjectDir)\ffmpeg\include;./include;$(IncludePath)
- $(ProjectDir)\ffmpeg\lib;$(LibraryPath)
- $(SolutionDir)Kyoo\Transcoder
-
-
- false
- $(SolutionDir)Kyoo\Transcoder
- $(ProjectDir)\ffmpeg\include;./include;$(IncludePath)
- $(ProjectDir)\ffmpeg\lib;$(LibraryPath)
-
-
-
- NotUsing
- Level3
- Disabled
- true
- WIN32;_DEBUG;KYOOTRANSCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);TRANSCODER_EXPORTS=1;_CRT_NONSTDC_NO_DEPRECATE
- true
- pch.h
- stdcpp17
-
-
- Windows
- true
- false
- $(ProjectDir)\ffmpeg\lib
- $(ProjectDir)ffmpeg\lib\*.lib;%(AdditionalDependencies)
-
-
-
-
- NotUsing
- Level3
- Disabled
- true
- _DEBUG;KYOOTRANSCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);TRANSCODER_EXPORTS=1;_CRT_NONSTDC_NO_DEPRECATE
- true
- pch.h
- %(AdditionalIncludeDirectories)
- stdcpp17
-
-
- Windows
- true
- false
- $(ProjectDir)\ffmpeg\lib
- $(ProjectDir)ffmpeg\lib\*.lib;%(AdditionalDependencies)
-
-
-
-
- NotUsing
- Level3
- MaxSpeed
- true
- true
- true
- WIN32;NDEBUG;KYOOTRANSCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);TRANSCODER_EXPORTS=1;_CRT_NONSTDC_NO_DEPRECATE
- true
- pch.h
- stdcpp17
-
-
- Windows
- true
- true
- true
- false
- $(ProjectDir)\ffmpeg\lib
- $(ProjectDir)ffmpeg\lib\*.lib;%(AdditionalDependencies)
-
-
-
-
- NotUsing
- Level3
- MaxSpeed
- true
- true
- true
- NDEBUG;KYOOTRANSCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);TRANSCODER_EXPORTS=1;_CRT_NONSTDC_NO_DEPRECATE
- true
- pch.h
- %(AdditionalIncludeDirectories)
- stdcpp17
-
-
- Windows
- true
- true
- true
- false
- $(ProjectDir)\ffmpeg\lib
- $(ProjectDir)ffmpeg\lib\*.lib;%(AdditionalDependencies)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj.filters b/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj.filters
deleted file mode 100644
index a89d7182..00000000
--- a/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj.filters
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
- {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
- cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
-
-
- {a553acdb-cb65-47bc-8809-c5374fe91cae}
-
-
- {93995380-89BD-4b04-88EB-625FBE52EBFB}
- h;hh;hpp;hxx;hm;inl;inc;ipp;xsd
-
-
- {851d2dff-3186-4d12-9f4a-4144143be6fb}
-
-
-
-
- Headers
-
-
- Headers
-
-
- Headers
-
-
-
-
- Source Files
-
-
- Source Files
-
-
-
\ No newline at end of file
diff --git a/Kyoo.Transcoder/include/compatibility.h b/Kyoo.Transcoder/include/compatibility.h
new file mode 100644
index 00000000..37f82c5e
--- /dev/null
+++ b/Kyoo.Transcoder/include/compatibility.h
@@ -0,0 +1,15 @@
+//
+// Created by Anonymus Raccoon on 16/12/2019.
+//
+
+#pragma once
+
+#include
+
+#ifdef __WIN32__
+ #define mkdir(c, m) mkdir(c)
+#endif
+
+#ifdef __MINGW32__
+ #define asprintf __mingw_asprintf
+#endif
\ No newline at end of file
diff --git a/Kyoo.Transcoder/include/export.h b/Kyoo.Transcoder/include/export.h
new file mode 100644
index 00000000..36dec349
--- /dev/null
+++ b/Kyoo.Transcoder/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/Kyoo.Transcoder/include/helper.h b/Kyoo.Transcoder/include/helper.h
index 30025384..719c8bf6 100644
--- a/Kyoo.Transcoder/include/helper.h
+++ b/Kyoo.Transcoder/include/helper.h
@@ -1,11 +1,14 @@
+//
+// Created by Anonymus Raccoon on 15/12/2019.
+//
+
#pragma once
-extern "C"
-{
- #include
- #include
- #include
-}
-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
+
+#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/Kyoo.Transcoder/include/stream.h b/Kyoo.Transcoder/include/stream.h
index 175e50c1..240fec6e 100644
--- a/Kyoo.Transcoder/include/stream.h
+++ b/Kyoo.Transcoder/include/stream.h
@@ -1,8 +1,8 @@
#pragma once
-#include
-#include
+#include
+#include
-extern "C" struct Stream
+typedef struct stream
{
char *title;
char *language;
@@ -10,40 +10,13 @@ extern "C" struct Stream
bool is_default;
bool is_forced;
char *path;
+} stream;
- Stream()
- : title(nullptr), language(nullptr), codec(nullptr), is_default(nullptr), is_forced(nullptr), path(nullptr) {}
-
- Stream(const char* title, const char* languageCode, const char* codec, bool isDefault, bool isForced)
- : title(nullptr), language(nullptr), codec(nullptr), is_default(isDefault), is_forced(isForced), path(nullptr)
- {
- if(title != nullptr)
- this->title= strdup(title);
-
- if (languageCode != nullptr)
- language = strdup(languageCode);
- else
- language = strdup("und");
-
- if (codec != nullptr)
- this->codec = strdup(codec);
- }
-
- Stream(const char *title, const char *languageCode, const char *codec, bool isDefault, bool isForced, const char *path)
- : title(nullptr), language(nullptr), codec(nullptr), is_default(isDefault), is_forced(isForced), path(nullptr)
- {
- if (title != nullptr)
- this->title = strdup(title);
-
- if (languageCode != nullptr)
- language = strdup(languageCode);
- else
- language = strdup("und");
-
- if (codec != nullptr)
- this->codec = strdup(codec);
-
- if (path != nullptr)
- this->path = strdup(path);
- }
-};
\ No newline at end of file
+#define NULLSTREAM (struct stream) { \
+ NULL, \
+ NULL, \
+ NULL, \
+ false, \
+ false, \
+ NULL \
+}
\ No newline at end of file
diff --git a/Kyoo.Transcoder/include/transcoder.h b/Kyoo.Transcoder/include/transcoder.h
index 47488853..1040a135 100644
--- a/Kyoo.Transcoder/include/transcoder.h
+++ b/Kyoo.Transcoder/include/transcoder.h
@@ -1,21 +1,17 @@
#pragma once
-#ifdef TRANSCODER_EXPORTS
-#define API __declspec(dllexport)
-#else
-#define API __declspec(dllimport)
-#endif
-
-#include
-#include "Stream.h"
+#include "export.h"
+#include "stream.h"
-extern "C" API int Init();
+API int init();
-extern "C" API int transmux(const char *path, const char *out_path, float *playable_duration);
+API int transmux(const char *path, const char *out_path, float *playable_duration);
-extern "C" API Stream *get_track_info(const char *path, int *stream_count, int *track_count);
+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.
-extern "C" API Stream* extract_subtitles(const char *path, const char *outPath, int *streamCount, int *subtitleCount);
+API stream* extract_subtitles(char *path, const char *outPath, int *streamCount, int *subtitleCount);
-extern "C" API void free_memory(Stream *streamsPtr);
\ No newline at end of file
+API void free_memory(stream *streamsPtr);
\ No newline at end of file
diff --git a/Kyoo.Transcoder/src/Transcoder.cpp b/Kyoo.Transcoder/src/Transcoder.cpp
deleted file mode 100644
index 8f4945ac..00000000
--- a/Kyoo.Transcoder/src/Transcoder.cpp
+++ /dev/null
@@ -1,293 +0,0 @@
-#include
-#include
-#include "transcoder.h"
-#include "helper.h"
-
-int Init()
-{
- return sizeof(Stream);
-}
-
-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;
-}
-
-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 nullptr;
-
- *stream_count = ctx->nb_streams;
- *track_count = 0;
- streams = new Stream[*stream_count];
-
- //Initialize output and set headers.
- 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] = Stream(codecpar->codec_type == AVMEDIA_TYPE_VIDEO ? "VIDEO" : NULL, // title
- languageptr ? languageptr->value : NULL, // language
- avcodec_get_name(codecpar->codec_id), // format
- stream->disposition & AV_DISPOSITION_DEFAULT, // isDefault
- stream->disposition & AV_DISPOSITION_FORCED, // isForced
- path); // path
- }
- else
- streams[i] = Stream();
- }
- avformat_close_input(&ctx);
- return streams;
-}
-
-Stream *extract_subtitles(const char *path, const char *out_path, int *stream_count, int *subtitle_count)
-{
- AVFormatContext *int_ctx = NULL;
- AVFormatContext **output_list;
- Stream *streams;
- AVPacket pkt;
- unsigned int out_count;
-
- if (open_input_context(&int_ctx, path) != 0)
- return nullptr;
-
- *stream_count = int_ctx->nb_streams;
- *subtitle_count = 0;
- streams = new Stream[*stream_count];
-
- out_count = int_ctx->nb_streams;
- output_list = new AVFormatContext *[out_count];
-
- //Initialize output and set headers.
- for (unsigned int i = 0; i < int_ctx->nb_streams; i++)
- {
- AVStream *in_stream = int_ctx->streams[i];
- const AVCodecParameters *in_codecpar = in_stream->codecpar;
-
- if (in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE)
- {
- output_list[i] = NULL;
- streams[i] = Stream();
- }
- else
- {
- *subtitle_count += 1;
-
- AVDictionaryEntry *languageptr = av_dict_get(in_stream->metadata, "language", NULL, 0);
-
- //Get metadata for file name
- streams[i] = Stream(NULL, //title
- languageptr ? languageptr->value : NULL, //language
- avcodec_get_name(in_codecpar->codec_id), //format
- in_stream->disposition & AV_DISPOSITION_DEFAULT, //isDefault
- in_stream->disposition & AV_DISPOSITION_FORCED); //isForced
-
- //Create the language subfolder
- std::stringstream out_strstream;
- out_strstream << out_path << (char)std::filesystem::path::preferred_separator << streams[i].language;
- std::filesystem::create_directory(out_strstream.str());
-
- //Get file name
- std::string file_name(path);
- size_t last_separator = file_name.find_last_of((char)std::filesystem::path::preferred_separator);
- file_name = file_name.substr(last_separator, file_name.find_last_of('.') - last_separator);
-
- //Construct output file name
- out_strstream << file_name << "." << streams[i].language;
-
- if (streams[i].is_default)
- out_strstream << ".default";
- if (streams[i].is_forced)
- out_strstream << ".forced";
-
- if (strcmp(streams[i].codec, "subrip") == 0)
- out_strstream << ".srt";
- else if (strcmp(streams[i].codec, "ass") == 0)
- out_strstream << ".ass";
- else
- {
- std::cout << "Unsupported subtitle codec: " << streams[i].codec << std::endl;
- output_list[i] = NULL;
- continue;
- }
-
- streams[i].path = strdup(out_strstream.str().c_str());
-
- std::cout << "Stream #" << i << "(" << streams[i].language << "), stream type: " << in_codecpar->codec_type << " codec: " << streams[i].codec << std::endl;
-
- AVFormatContext *out_ctx = NULL;
- if (avformat_alloc_output_context2(&out_ctx, NULL, NULL, streams[i].path) < 0)
- {
- std::cout << "Error: Couldn't create an output file." << std::endl;
- continue;
- }
-
- av_dict_copy(&out_ctx->metadata, int_ctx->metadata, NULL);
-
- AVStream *out_stream = copy_stream_to_output(out_ctx, in_stream);
- if (out_stream == NULL)
- goto end;
-
- av_dump_format(out_ctx, 0, streams[i].path, true);
-
- if (open_output_file_for_write(out_ctx, streams[i].path, NULL) != 0)
- goto end;
-
- output_list[i] = out_ctx;
-
- if (false)
- {
- end:
- if (out_ctx && !(out_ctx->flags & AVFMT_NOFILE))
- avio_closep(&out_ctx->pb);
- avformat_free_context(out_ctx);
-
- output_list[i] = nullptr;
- std::cout << "An error occured, cleaning up th output context for the stream #" << i << std::endl;
- }
- }
- }
-
- //Write subtitle data to files.
- while (av_read_frame(int_ctx, &pkt) == 0)
- {
- if ((unsigned int)pkt.stream_index >= out_count)
- continue;
-
- AVFormatContext *out_ctx = output_list[pkt.stream_index];
- if (out_ctx == nullptr)
- {
- 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)
- std::cout << "Error while writing a packet to the output file." << std::endl;
-
- av_packet_unref(&pkt);
- }
-
- avformat_close_input(&int_ctx);
-
- for (unsigned int i = 0; i < out_count; i++)
- {
- AVFormatContext *out_ctx = output_list[i];
-
- if (out_ctx == NULL)
- continue;
-
- av_write_trailer(out_ctx);
-
- if (out_ctx && !(out_ctx->flags & AVFMT_NOFILE))
- avio_closep(&out_ctx->pb);
- avformat_free_context(out_ctx);
- }
-
- delete[] output_list;
- return streams;
-}
-
-void free_memory(Stream *stream_ptr)
-{
- delete[] stream_ptr;
-}
\ No newline at end of file
diff --git a/Kyoo.Transcoder/src/helper.c b/Kyoo.Transcoder/src/helper.c
new file mode 100644
index 00000000..defc20da
--- /dev/null
+++ b/Kyoo.Transcoder/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/Kyoo.Transcoder/src/helper.cpp b/Kyoo.Transcoder/src/helper.cpp
deleted file mode 100644
index d390bc0a..00000000
--- a/Kyoo.Transcoder/src/helper.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-#include "helper.h"
-#include
-
-int open_input_context(AVFormatContext **in_ctx, const char *path)
-{
- if (avformat_open_input(in_ctx, path, NULL, NULL))
- {
- std::cout << "Error: Can't open the file at " << path << std::endl;
- return 1;
- }
- if (avformat_find_stream_info(*in_ctx, NULL) < 0)
- {
- std::cout << "Error: Could't find streams informations for the file at " << path << std::endl;
- 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)
- {
- std::cout << "Error: Couldn't create stream." << std::endl;
- return NULL;
- }
- if (avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar) < 0)
- {
- std::cout << "Error: Couldn't copy parameters to the output file." << std::endl;
- return NULL;
- }
- out_stream->codecpar->codec_tag = 0;
- avformat_transfer_internal_stream_timing_info(out_ctx->oformat, out_stream, in_stream, AVTimebaseSource::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;
-}
-
-constexpr enum AVRounding operator |(const enum AVRounding a, const enum AVRounding b)
-{
- return (enum AVRounding)(uint32_t(a) | uint32_t(b));
-}
-
-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)
- {
- std::cout << "Error: Couldn't open file at " << out_path << std::endl;
- return 1;
- }
- }
- else
- std::cout << "Output flag set to AVFMT_NOFILE." << std::endl;
- if (avformat_write_header(out_ctx, options) < 0)
- {
- std::cout << "Error: Couldn't write headers to file at " << out_path << std::endl;
- 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/Kyoo.Transcoder/src/subtitles.c b/Kyoo.Transcoder/src/subtitles.c
new file mode 100644
index 00000000..db1dcace
--- /dev/null
+++ b/Kyoo.Transcoder/src/subtitles.c
@@ -0,0 +1,164 @@
+//
+// Created by Anonymus Raccoon on 16/12/2019.
+//
+
+#include "transcoder.h"
+#include "stream.h"
+#include "helper.h"
+#include "compatibility.h"
+#include
+#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;
+
+ codec = strdup(avcodec_get_name(in_stream->codecpar->codec_id));
+ 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 (mkdir(substream->path, 0733) < 0) {
+ stream_free(substream);
+ 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);
+ return (0);
+}
+
+int copy_subtitle_stream(AVFormatContext **out, stream *s, AVFormatContext *int_ctx, AVStream *in_stream)
+{
+ AVFormatContext *out_ctx = NULL;
+ 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) {
+ *out = out_ctx;
+ return (0);
+ }
+ }
+
+ if (out_ctx && !(out_ctx->flags & AVFMT_NOFILE))
+ avio_closep(&out_ctx->pb);
+ avformat_free_context(out_ctx);
+
+ *out = NULL;
+ printf("An error occured, cleaning up th output context for the %s stream.\n", s->language);
+ return (-1);
+}
+
+stream *extract_subtitles(char *path, const char *out_path, int *stream_count, int *subtitle_count)
+{
+ AVFormatContext *int_ctx = NULL;
+ AVFormatContext **output_list;
+ stream *streams;
+ AVPacket pkt;
+ unsigned int out_count;
+ 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);
+ out_count = int_ctx->nb_streams;
+ output_list = malloc(sizeof(AVFormatContext *) * out_count);
+
+ for (unsigned int i = 0; i < int_ctx->nb_streams; i++) {
+ AVStream *in_stream = int_ctx->streams[i];
+
+ if (in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
+ output_list[i] = NULL;
+ streams[i] = NULLSTREAM;
+ } else {
+ 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 subtitle data to files.
+ while (av_read_frame(int_ctx, &pkt) == 0)
+ {
+ if ((unsigned int)pkt.stream_index >= out_count)
+ continue;
+
+ AVFormatContext *out_ctx = output_list[pkt.stream_index];
+ if (out_ctx == nullptr)
+ {
+ 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)
+ std::cout << "Error while writing a packet to the output file." << std::endl;
+
+ av_packet_unref(&pkt);
+ }
+
+ avformat_close_input(&int_ctx);
+
+ for (unsigned int i = 0; i < out_count; i++)
+ {
+ AVFormatContext *out_ctx = output_list[i];
+
+ if (out_ctx == NULL)
+ continue;
+
+ av_write_trailer(out_ctx);
+
+ if (out_ctx && !(out_ctx->flags & AVFMT_NOFILE))
+ avio_closep(&out_ctx->pb);
+ avformat_free_context(out_ctx);
+ }
+
+ delete[] output_list;
+ return streams;
+}
diff --git a/Kyoo.Transcoder/src/transcoder.c b/Kyoo.Transcoder/src/transcoder.c
new file mode 100644
index 00000000..995ce0bc
--- /dev/null
+++ b/Kyoo.Transcoder/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
diff --git a/Kyoo/Controllers/VideoController.cs b/Kyoo/Controllers/VideoController.cs
index 78d2d551..c5ebc43c 100644
--- a/Kyoo/Controllers/VideoController.cs
+++ b/Kyoo/Controllers/VideoController.cs
@@ -60,18 +60,20 @@ namespace Kyoo.Controllers
}
[HttpGet("transcode/{showSlug}-s{seasonNumber}e{episodeNumber}")]
- public IActionResult Transcode(string showSlug, long seasonNumber, long episodeNumber)
+ public async Task Transcode(string showSlug, long seasonNumber, long episodeNumber)
{
- return null;
- //WatchItem episode = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber);
+ WatchItem episode = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber);
- //if (episode != null && System.IO.File.Exists(episode.Path))
- //{
- // string path = transcoder.Transcode(episode.Path);
- // return PhysicalFile(path, "video/mp4", true); //Should use mpeg dash
- //}
- //else
- // return NotFound();
+ if (episode != null && System.IO.File.Exists(episode.Path))
+ {
+ string path = await transcoder.Transcode(episode);
+ if (path != null)
+ return PhysicalFile(path, "application/x-mpegURL ", true);
+ else
+ return StatusCode(500);
+ }
+ else
+ return NotFound();
}
}
}
\ No newline at end of file
diff --git a/Kyoo/InternalAPI/Transcoder/Transcoder.cs b/Kyoo/InternalAPI/Transcoder/Transcoder.cs
index 4e542381..986451bd 100644
--- a/Kyoo/InternalAPI/Transcoder/Transcoder.cs
+++ b/Kyoo/InternalAPI/Transcoder/Transcoder.cs
@@ -12,10 +12,12 @@ namespace Kyoo.InternalAPI
public class Transcoder : ITranscoder
{
private readonly string transmuxPath;
+ private readonly string transcodePath;
public Transcoder(IConfiguration config)
{
transmuxPath = config.GetValue("transmuxTempPath");
+ transcodePath = config.GetValue("transcodeTempPath");
Debug.WriteLine("&Api INIT (unmanaged stream size): " + TranscoderAPI.Init() + ", Stream size: " + Marshal.SizeOf());
}
@@ -61,10 +63,25 @@ namespace Kyoo.InternalAPI
return transmuxFailed ? null : manifest;
}
- public Task Transcode(WatchItem episode)
+ public async Task Transcode(WatchItem episode)
{
- //NOT IMPLEMENTED YET
- return null;
+ string folder = Path.Combine(transcodePath, episode.Link);
+ string manifest = Path.Combine(folder, episode.Link + ".m3u8");
+ float playableDuration = 0;
+ bool transmuxFailed = false;
+
+ Directory.CreateDirectory(folder);
+ Debug.WriteLine("&Transcoding " + episode.Link + " at " + episode.Path + ", outputPath: " + folder);
+
+ if (File.Exists(manifest))
+ return manifest;
+ Task.Run(() =>
+ {
+ transmuxFailed = TranscoderAPI.transcode(episode.Path, manifest.Replace('\\', '/'), out playableDuration) != 0;
+ });
+ while (playableDuration < 10 || (!File.Exists(manifest) && !transmuxFailed))
+ await Task.Delay(10);
+ return transmuxFailed ? null : manifest;
}
}
}
diff --git a/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs b/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs
index 977adf27..995805da 100644
--- a/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs
+++ b/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs
@@ -16,6 +16,9 @@ namespace Kyoo.InternalAPI.TranscoderLink
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
public extern static int transmux(string path, string out_path, out float playableDuration);
+
+ [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
+ public extern static int transcode(string path, string out_path, out float playableDuration);
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
private extern static IntPtr get_track_info(string path, out int array_length, out int track_count);
diff --git a/Kyoo/appsettings.json b/Kyoo/appsettings.json
index eb2882cb..afdca5d1 100644
--- a/Kyoo/appsettings.json
+++ b/Kyoo/appsettings.json
@@ -10,6 +10,7 @@
"databasePath": "C://Projects/database.db",
"transmuxTempPath": "C:\\\\Projects\\temp\\transmux",
+ "transcodeTempPath": "C:\\\\Projects\\temp\\transcode",
"peoplePath": "D:\\\\Videos\\People",
"plugins": "C:\\Projects\\Kyoo\\Debug",
"providerPlugins": "C://Projects/Plugins/Providers",