Reworking the transcoder to work with a CMake in C (removing unnecesarry C++).

This commit is contained in:
Zoe Roux 2019-12-16 22:23:48 +01:00
parent fe9e6e7e50
commit 408352cc3c
17 changed files with 483 additions and 676 deletions

View File

@ -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)

View File

@ -1,195 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{E5EFA4B2-F09D-4C4F-83DD-A436CD60BB77}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>KyooTranscoder</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>$(ProjectDir)\ffmpeg\include;./include;$(IncludePath)</IncludePath>
<LibraryPath>$(ProjectDir)\ffmpeg\lib;$(LibraryPath)</LibraryPath>
<OutDir>$(SolutionDir)Kyoo\Transcoder</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)Kyoo\Transcoder</OutDir>
<IncludePath>$(ProjectDir)\ffmpeg\include;./include;$(IncludePath)</IncludePath>
<LibraryPath>$(ProjectDir)\ffmpeg\lib;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>$(ProjectDir)\ffmpeg\include;./include;$(IncludePath)</IncludePath>
<LibraryPath>$(ProjectDir)\ffmpeg\lib;$(LibraryPath)</LibraryPath>
<OutDir>$(SolutionDir)Kyoo\Transcoder</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)Kyoo\Transcoder</OutDir>
<IncludePath>$(ProjectDir)\ffmpeg\include;./include;$(IncludePath)</IncludePath>
<LibraryPath>$(ProjectDir)\ffmpeg\lib;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;KYOOTRANSCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);TRANSCODER_EXPORTS=1;_CRT_NONSTDC_NO_DEPRECATE</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>$(ProjectDir)\ffmpeg\lib</AdditionalLibraryDirectories>
<AdditionalDependencies>$(ProjectDir)ffmpeg\lib\*.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;KYOOTRANSCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);TRANSCODER_EXPORTS=1;_CRT_NONSTDC_NO_DEPRECATE</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>$(ProjectDir)\ffmpeg\lib</AdditionalLibraryDirectories>
<AdditionalDependencies>$(ProjectDir)ffmpeg\lib\*.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;KYOOTRANSCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);TRANSCODER_EXPORTS=1;_CRT_NONSTDC_NO_DEPRECATE</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>$(ProjectDir)\ffmpeg\lib</AdditionalLibraryDirectories>
<AdditionalDependencies>$(ProjectDir)ffmpeg\lib\*.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;KYOOTRANSCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);TRANSCODER_EXPORTS=1;_CRT_NONSTDC_NO_DEPRECATE</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalLibraryDirectories>$(ProjectDir)\ffmpeg\lib</AdditionalLibraryDirectories>
<AdditionalDependencies>$(ProjectDir)ffmpeg\lib\*.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="include/helper.h" />
<ClInclude Include="include/Stream.h" />
<ClInclude Include="include/Transcoder.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="src/helper.cpp" />
<ClCompile Include="src/Transcoder.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Models">
<UniqueIdentifier>{a553acdb-cb65-47bc-8809-c5374fe91cae}</UniqueIdentifier>
</Filter>
<Filter Include="Compiler Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Headers">
<UniqueIdentifier>{851d2dff-3186-4d12-9f4a-4144143be6fb}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="include/helper.h">
<Filter>Headers</Filter>
</ClInclude>
<ClInclude Include="include/Stream.h">
<Filter>Headers</Filter>
</ClInclude>
<ClInclude Include="include/Transcoder.h">
<Filter>Headers</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src/Transcoder.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src/helper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -0,0 +1,15 @@
//
// Created by Anonymus Raccoon on 16/12/2019.
//
#pragma once
#include <stdio.h>
#ifdef __WIN32__
#define mkdir(c, m) mkdir(c)
#endif
#ifdef __MINGW32__
#define asprintf __mingw_asprintf
#endif

View File

@ -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

View File

@ -1,11 +1,14 @@
//
// Created by Anonymus Raccoon on 15/12/2019.
//
#pragma once
extern "C"
{
#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);
#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);

View File

@ -1,8 +1,8 @@
#pragma once
#include <iostream>
#include <sstream>
#include <stdbool.h>
#include <stddef.h>
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);
}
};
#define NULLSTREAM (struct stream) { \
NULL, \
NULL, \
NULL, \
false, \
false, \
NULL \
}

View File

@ -1,21 +1,17 @@
#pragma once
#ifdef TRANSCODER_EXPORTS
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif
#include <iostream>
#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);
API void free_memory(stream *streamsPtr);

View File

@ -1,293 +0,0 @@
#include <filesystem>
#include <sstream>
#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;
}

View File

@ -0,0 +1,64 @@
#include <stdio.h>
#include <stdbool.h>
#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;
}

View File

@ -1,75 +0,0 @@
#include "helper.h"
#include <iostream>
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;
}

View File

@ -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 <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

View File

@ -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

View File

@ -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<IActionResult> 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();
}
}
}

View File

@ -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<string>("transmuxTempPath");
transcodePath = config.GetValue<string>("transcodeTempPath");
Debug.WriteLine("&Api INIT (unmanaged stream size): " + TranscoderAPI.Init() + ", Stream size: " + Marshal.SizeOf<Models.Watch.Stream>());
}
@ -61,10 +63,25 @@ namespace Kyoo.InternalAPI
return transmuxFailed ? null : manifest;
}
public Task<string> Transcode(WatchItem episode)
public async Task<string> 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;
}
}
}

View File

@ -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);

View File

@ -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",