Starting to implement dash streaming.

This commit is contained in:
Zoe Roux 2019-11-08 20:09:15 +01:00
parent ecdd85b82d
commit 05d297201e
30 changed files with 328 additions and 322 deletions

9
.gitignore vendored
View File

@ -1,3 +1,12 @@
## PROJECT CUSTOM IGNORES
# Transcoder build (auto generated from Kyoo.Transcoder sub-project)
Kyoo/Transcoder/Kyoo.Transcoder.dll
Kyoo/Transcoder/Kyoo.Transcoder.ilk
Kyoo/Transcoder/Kyoo.Transcoder.lib
Kyoo/Transcoder/Kyoo.Transcoder.pdb
Kyoo/Transcoder/Kyoo.Transcoder.exp
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##

View File

@ -72,35 +72,35 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>$(ProjectDir)\ffmpeg\include;$(IncludePath)</IncludePath>
<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;$(IncludePath)</IncludePath>
<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;$(IncludePath)</IncludePath>
<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;$(IncludePath)</IncludePath>
<IncludePath>$(ProjectDir)\ffmpeg\include;./include;$(IncludePath)</IncludePath>
<LibraryPath>$(ProjectDir)\ffmpeg\lib;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;KYOOTRANSCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);TRANSCODER_EXPORTS=1</PreprocessorDefinitions>
<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>
@ -115,11 +115,11 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;KYOOTRANSCODER_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions);TRANSCODER_EXPORTS=1</PreprocessorDefinitions>
<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>
@ -135,13 +135,13 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<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</PreprocessorDefinitions>
<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>
@ -158,13 +158,13 @@
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<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</PreprocessorDefinitions>
<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>
@ -181,19 +181,12 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="src/framework.h" />
<ClInclude Include="src/pch.h" />
<ClInclude Include="src/Stream.h" />
<ClInclude Include="src/Transcoder.h" />
<ClInclude Include="include/helper.h" />
<ClInclude Include="include/Stream.h" />
<ClInclude Include="include/Transcoder.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="src/dllmain.cpp" />
<ClCompile Include="src/pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="src/helper.cpp" />
<ClCompile Include="src/Transcoder.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

View File

@ -12,30 +12,27 @@
<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="src/Transcoder.h">
<Filter>Source Files</Filter>
<ClInclude Include="include/helper.h">
<Filter>Headers</Filter>
</ClInclude>
<ClInclude Include="src/pch.h">
<Filter>Compiler Files</Filter>
<ClInclude Include="include/Stream.h">
<Filter>Headers</Filter>
</ClInclude>
<ClInclude Include="src/framework.h">
<Filter>Compiler Files</Filter>
</ClInclude>
<ClInclude Include="src/Stream.h">
<Filter>Models</Filter>
<ClInclude Include="include/Transcoder.h">
<Filter>Headers</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src/Transcoder.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src/pch.cpp">
<Filter>Compiler Files</Filter>
</ClCompile>
<ClCompile Include="src/dllmain.cpp">
<Filter>Compiler Files</Filter>
<ClCompile Include="src/helper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -24,14 +24,14 @@ extern "C" struct Stream
: title(NULL), language(NULL), codec(NULL), isDefault(isDefault), isForced(isForced), path(NULL)
{
if(title != NULL)
this->title= _strdup(title);
this->title= strdup(title);
if (languageCode != NULL)
language = _strdup(languageCode);
language = strdup(languageCode);
else
language = _strdup("und");
language = strdup("und");
if (codec != NULL)
this->codec = _strdup(codec);
this->codec = strdup(codec);
}
};

View File

@ -1,197 +1,85 @@
#include "pch.h"
#include <filesystem>
#include <sstream>
#include "Transcoder.h"
//ffmpeg imports
extern "C"
{
#include <libavformat/avformat.h>
#include <libavutil/dict.h>
#include <libavutil/timestamp.h>
}
constexpr enum AVRounding operator |(const enum AVRounding a, const enum AVRounding b)
{
return (enum AVRounding)(uint32_t(a) | uint32_t(b));
}
#include "helper.h"
int Init()
{
return sizeof(Stream);
}
#pragma region InternalProcess
int open_input_context(AVFormatContext **inputContext, const char *path)
int transmux(const char *path, const char *out_path)
{
if (avformat_open_input(inputContext, path, NULL, NULL))
{
std::cout << "Error: Can't open the file at " << path << std::endl;
return 1;
}
if (avformat_find_stream_info(*inputContext, NULL) < 0)
{
std::cout << "Error: Could't find streams informations for the file at " << path << std::endl;
return 1;
}
av_dump_format(*inputContext, 0, path, false);
return 0;
}
AVStream* copy_stream_to_output(AVFormatContext *outputContext, AVStream *inputStream)
{
AVStream *outputStream = avformat_new_stream(outputContext, NULL);
if (outputStream == NULL)
{
std::cout << "Error: Couldn't create stream." << std::endl;
return NULL;
}
if (avcodec_parameters_copy(outputStream->codecpar, inputStream->codecpar) < 0)
{
std::cout << "Error: Couldn't copy parameters to the output file." << std::endl;
return NULL;
}
outputStream->codecpar->codec_tag = 0;
avformat_transfer_internal_stream_timing_info(outputContext->oformat, outputStream, inputStream, AVTimebaseSource::AVFMT_TBCF_AUTO);
outputStream->time_base = av_add_q(av_stream_get_codec_timebase(outputStream), AVRational{ 0, 1 });
outputStream->duration = av_rescale_q(inputStream->duration, inputStream->time_base, outputStream->time_base);
outputStream->disposition = inputStream->disposition;
outputStream->avg_frame_rate = inputStream->avg_frame_rate;
outputStream->r_frame_rate = inputStream->r_frame_rate;
//av_dict_copy(&outputStream->metadata, inputStream->metadata, NULL);
//if (inputStream->nb_side_data)
//{
// for (int i = 0; i < inputStream->nb_side_data; i++)
// {
// std::cout << "Copying side packet #" << i << std::endl;
// AVPacketSideData *sidePkt = &inputStream->side_data[i];
// uint8_t *newPkt = av_stream_new_side_data(outputStream, sidePkt->type, sidePkt->size);
// if (newPkt == NULL)
// {
// std::cout << "Error copying side package." << std::endl;
// //Should handle return here
// return;
// }
// memcpy(newPkt, sidePkt->data, sidePkt->size);
// }
//}
return outputStream;
}
int open_output_file_for_write(AVFormatContext *outputContext, const char* outputPath)
{
if (!(outputContext->flags & AVFMT_NOFILE))
{
if (avio_open(&outputContext->pb, outputPath, AVIO_FLAG_WRITE) < 0)
{
std::cout << "Error: Couldn't open file at " << outputPath << std::endl;
return 1;
}
}
else
std::cout << "Output flag set to AVFMT_NOFILE." << std::endl;
if (avformat_write_header(outputContext, NULL) < 0)
{
std::cout << "Error: Couldn't write headers to file at " << outputPath << std::endl;
return 1;
}
return 0;
}
void process_packet(AVPacket &pkt, AVStream* inputStream, AVStream* outputStream)
{
pkt.pts = av_rescale_q_rnd(pkt.pts, inputStream->time_base, outputStream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd(pkt.dts, inputStream->time_base, outputStream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
pkt.duration = av_rescale_q(pkt.duration, inputStream->time_base, outputStream->time_base);
pkt.pos = -1;
}
#pragma endregion
int Transmux(const char *path, const char *outPath)
{
AVFormatContext *inputContext = NULL;
AVFormatContext *in_ctx = NULL;
AVFormatContext* out_ctx = NULL;
AVStream* stream;
AVPacket pkt;
int* stream_map;
int stream_count;
int ret = 0;
if (open_input_context(&inputContext, path) != 0)
if (open_input_context(&in_ctx, path) != 0)
return 1;
AVFormatContext *outputContext = NULL;
if (avformat_alloc_output_context2(&outputContext, NULL, NULL, outPath) < 0)
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;
}
int *streamsMap = new int[inputContext->nb_streams];
int streamCount = 0;
stream_map = new int[in_ctx->nb_streams];
stream_count = 0;
for (unsigned int i = 0; i < inputContext->nb_streams; i++)
for (unsigned int i = 0; i < in_ctx->nb_streams; i++)
{
AVStream *stream = inputContext->streams[i];
stream = in_ctx->streams[i];
if (stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
streamsMap[i] = streamCount;
streamCount++;
if (copy_stream_to_output(outputContext, stream) == NULL)
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.
{
streamsMap[i] = streamCount;
streamCount++;
if (copy_stream_to_output(outputContext, stream) == NULL)
stream_map[i] = stream_count;
stream_count++;
if (copy_stream_to_output(out_ctx, stream) == NULL)
return 1;
}
else
streamsMap[i] = -1;
stream_map[i] = -1;
}
av_dump_format(outputContext, 0, outPath, true);
if (open_output_file_for_write(outputContext, outPath) != 0)
av_dump_format(out_ctx, 0, out_path, true);
if (open_output_file_for_write(out_ctx, out_path) != 0)
return 1;
AVPacket pkt;
while (av_read_frame(inputContext, &pkt) == 0)
while (av_read_frame(in_ctx, &pkt) == 0)
{
if (pkt.stream_index >= inputContext->nb_streams || streamsMap[pkt.stream_index] < 0)
if ((unsigned int)pkt.stream_index >= in_ctx->nb_streams || stream_map[pkt.stream_index] < 0)
{
av_packet_unref(&pkt);
continue;
}
AVStream *inputStream = inputContext->streams[pkt.stream_index];
pkt.stream_index = streamsMap[pkt.stream_index];
AVStream *outputStream = outputContext->streams[pkt.stream_index];
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]);
process_packet(pkt, inputStream, outputStream);
if (av_interleaved_write_frame(outputContext, &pkt) < 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);
}
av_write_trailer(outputContext);
avformat_close_input(&inputContext);
av_write_trailer(out_ctx);
avformat_close_input(&in_ctx);
if (outputContext && !(outputContext->oformat->flags & AVFMT_NOFILE))
avio_closep(&outputContext->pb);
avformat_free_context(outputContext);
delete[] streamsMap;
if (out_ctx && !(out_ctx->oformat->flags & AVFMT_NOFILE))
avio_closep(&out_ctx->pb);
avformat_free_context(out_ctx);
delete[] stream_map;
if (ret < 0 && ret != AVERROR_EOF)
return 1;
@ -200,31 +88,35 @@ int Transmux(const char *path, const char *outPath)
}
Stream *ExtractSubtitles(const char *path, const char *outPath, int *streamCount, int *subtitleCount)
Stream *extract_subtitles(const char *path, const char *out_path, int *stream_count, int *subtitle_count)
{
AVFormatContext *inputContext = NULL;
AVFormatContext *int_ctx = NULL;
AVFormatContext** output_list;
Stream* streams;
AVPacket pkt;
unsigned int out_count;
if (open_input_context(&inputContext, path) != 0)
if (open_input_context(&int_ctx, path) != 0)
return nullptr;
*streamCount = inputContext->nb_streams;
*subtitleCount = 0;
Stream *streams = new Stream[*streamCount];
*stream_count = int_ctx->nb_streams;
*subtitle_count = 0;
streams = new Stream[*stream_count];
const unsigned int outputCount = inputContext->nb_streams;
AVFormatContext **outputList = new AVFormatContext*[outputCount];
out_count = int_ctx->nb_streams;
output_list = new AVFormatContext*[out_count];
//Initialize output and set headers.
for (unsigned int i = 0; i < inputContext->nb_streams; i++)
for (unsigned int i = 0; i < int_ctx->nb_streams; i++)
{
AVStream *inputStream = inputContext->streams[i];
AVStream *inputStream = int_ctx->streams[i];
const AVCodecParameters *inputCodecpar = inputStream->codecpar;
if (inputCodecpar->codec_type != AVMEDIA_TYPE_SUBTITLE)
outputList[i] = NULL;
output_list[i] = NULL;
else
{
*subtitleCount += 1;
*subtitle_count += 1;
AVDictionaryEntry *languagePtr = av_dict_get(inputStream->metadata, "language", NULL, 0);
@ -237,7 +129,7 @@ Stream *ExtractSubtitles(const char *path, const char *outPath, int *streamCount
//Create the language subfolder
std::stringstream outStream;
outStream << outPath << (char)std::filesystem::path::preferred_separator << streams[i].language;
outStream << out_path << (char)std::filesystem::path::preferred_separator << streams[i].language;
std::filesystem::create_directory(outStream.str());
//Get file name
@ -260,11 +152,11 @@ Stream *ExtractSubtitles(const char *path, const char *outPath, int *streamCount
else
{
std::cout << "Unsupported subtitle codec: " << streams[i].codec << std::endl;
outputList[i] = NULL;
output_list[i] = NULL;
continue;
}
streams[i].path = _strdup(outStream.str().c_str());
streams[i].path = strdup(outStream.str().c_str());
std::cout << "Stream #" << i << "(" << streams[i].language << "), stream type: " << inputCodecpar->codec_type << " codec: " << streams[i].codec << std::endl;
@ -275,7 +167,7 @@ Stream *ExtractSubtitles(const char *path, const char *outPath, int *streamCount
continue;
}
av_dict_copy(&outputContext->metadata, inputContext->metadata, NULL);
av_dict_copy(&outputContext->metadata, int_ctx->metadata, NULL);
AVStream *outputStream = copy_stream_to_output(outputContext, inputStream);
if (outputStream == NULL)
@ -286,7 +178,7 @@ Stream *ExtractSubtitles(const char *path, const char *outPath, int *streamCount
if (open_output_file_for_write(outputContext, streams[i].path) != 0)
goto end;
outputList[i] = outputContext;
output_list[i] = outputContext;
if (false)
{
@ -295,30 +187,27 @@ Stream *ExtractSubtitles(const char *path, const char *outPath, int *streamCount
avio_closep(&outputContext->pb);
avformat_free_context(outputContext);
outputList[i] = nullptr;
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.
AVPacket pkt;
while (av_read_frame(inputContext, &pkt) == 0)
while (av_read_frame(int_ctx, &pkt) == 0)
{
if (pkt.stream_index >= outputCount)
if ((unsigned int)pkt.stream_index >= out_count)
continue;
AVFormatContext *outputContext = outputList[pkt.stream_index];
AVFormatContext *outputContext = output_list[pkt.stream_index];
if (outputContext == nullptr)
{
av_packet_unref(&pkt);
continue;
}
AVStream *inputStream = inputContext->streams[pkt.stream_index];
AVStream *outputStream = outputContext->streams[0];
process_packet(pkt, int_ctx->streams[pkt.stream_index], outputContext->streams[0]);
pkt.stream_index = 0;
process_packet(pkt, inputStream, outputStream);
if (av_interleaved_write_frame(outputContext, &pkt) < 0)
std::cout << "Error while writing a packet to the output file." << std::endl;
@ -326,11 +215,11 @@ Stream *ExtractSubtitles(const char *path, const char *outPath, int *streamCount
av_packet_unref(&pkt);
}
avformat_close_input(&inputContext);
avformat_close_input(&int_ctx);
for (unsigned int i = 0; i < outputCount; i++)
for (unsigned int i = 0; i < out_count; i++)
{
AVFormatContext *outputContext = outputList[i];
AVFormatContext *outputContext = output_list[i];
if (outputContext == NULL)
continue;
@ -342,11 +231,11 @@ Stream *ExtractSubtitles(const char *path, const char *outPath, int *streamCount
avformat_free_context(outputContext);
}
delete[] outputList;
delete[] output_list;
return streams;
}
void FreeMemory(Stream *streamsPtr)
void free_memory(Stream *stream_ptr)
{
delete[] streamsPtr;
delete[] stream_ptr;
}

View File

@ -11,11 +11,9 @@
extern "C" API int Init();
extern "C" API int Transmux(const char *path, const char *outPath);
extern "C" API int transmux(const char *path, const char *outPath);
//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* ExtractSubtitles(const char *path, const char *outPath, int *streamCount, int *subtitleCount);
extern "C" API Stream* extract_subtitles(const char *path, const char *outPath, int *streamCount, int *subtitleCount);
extern "C" API void FreeMemory(Stream *streamsPtr);
extern "C" API Stream* TestMemory(const char *path, const char *outPath, int *streamCount, int *subtitleCount);
extern "C" API void free_memory(Stream *streamsPtr);

View File

@ -1,19 +0,0 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

View File

@ -1,5 +0,0 @@
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>

View File

@ -0,0 +1,75 @@
#include "helper.h"
#include <iostream>
int open_input_context(AVFormatContext** inputContext, const char* path)
{
if (avformat_open_input(inputContext, path, NULL, NULL))
{
std::cout << "Error: Can't open the file at " << path << std::endl;
return 1;
}
if (avformat_find_stream_info(*inputContext, NULL) < 0)
{
std::cout << "Error: Could't find streams informations for the file at " << path << std::endl;
return 1;
}
av_dump_format(*inputContext, 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* outputContext, const char* outputPath)
{
if (!(outputContext->flags & AVFMT_NOFILE))
{
if (avio_open(&outputContext->pb, outputPath, AVIO_FLAG_WRITE) < 0)
{
std::cout << "Error: Couldn't open file at " << outputPath << std::endl;
return 1;
}
}
else
std::cout << "Output flag set to AVFMT_NOFILE." << std::endl;
if (avformat_write_header(outputContext, NULL) < 0)
{
std::cout << "Error: Couldn't write headers to file at " << outputPath << 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,11 @@
#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);
void process_packet(AVPacket& pkt, AVStream* in_stream, AVStream* out_stream);

View File

@ -1,5 +0,0 @@
// pch.cpp: source file corresponding to the pre-compiled header
#include "pch.h"
// When you are using pre-compiled headers, this source file is necessary for compilation to succeed.

View File

@ -1,14 +0,0 @@
// pch.h: This is a precompiled header file.
// Files listed below are compiled only once, improving build performance for future builds.
// This also affects IntelliSense performance, including code completion and many code browsing features.
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
// Do not add files here that you will be updating frequently as this negates the performance advantage.
#ifndef PCH_H
#define PCH_H
// add headers that you want to pre-compile here
#include "framework.h"
#include <iostream>
#endif //PCH_H

8
Kyoo/.gitignore vendored
View File

@ -1,3 +1,11 @@
## PROJECT CUSTOM IGNORES
# Transcoder build (auto generated from Kyoo.Transcoder sub-project)
Transcoder/Kyoo.Transcoder.dll
Transcoder/Kyoo.Transcoder.ilk
Transcoder/Kyoo.Transcoder.lib
Transcoder/Kyoo.Transcoder.pdb
/Properties/launchSettings.json
## Ignore Visual Studio temporary files, build results, and

View File

@ -32,8 +32,8 @@
"scripts": [
"./node_modules/jquery/dist/jquery.min.js",
"./node_modules/bootstrap/dist/js/bootstrap.bundle.min.js",
"./src/libraries/subtitles.js",
"./node_modules/video.js/dist/video.min.js"
"./node_modules/dashjs/dist/dash.all.min.js",
"./src/libraries/subtitles.js"
]
},
"configurations": {

View File

@ -1345,6 +1345,12 @@
"integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==",
"dev": true
},
"@types/video.js": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.2.tgz",
"integrity": "sha512-r6oXapONKV4QRJ4K5/rOKlD9VdMQHY4JGVafJpf5ynDnJtV3PIxglS1i1VWNYQGZYGGL3aFDWedWNODLO4bxhw==",
"dev": true
},
"@types/webpack-sources": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-0.1.5.tgz",
@ -2787,6 +2793,11 @@
}
}
},
"codem-isoboxer": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/codem-isoboxer/-/codem-isoboxer-0.3.6.tgz",
"integrity": "sha512-LuO8/7LW6XuR5ERn1yavXAfodGRhuY2yP60JTZIw5yNYMCE5lUVbk3NFUCJxjnphQH+Xemp5hOGb1LgUXm00Xw=="
},
"collection-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@ -3173,6 +3184,16 @@
"assert-plus": "^1.0.0"
}
},
"dashjs": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/dashjs/-/dashjs-3.0.0.tgz",
"integrity": "sha512-XyVkjeHB4mqzf/Y7ARQ1IqbRBaee0osAulwCFV5ZNZ734wea8LbSC/23zKI32BulW7Kk6f7I6onW1WDMHRgz7Q==",
"requires": {
"codem-isoboxer": "0.3.6",
"fast-deep-equal": "2.0.1",
"imsc": "^1.0.2"
}
},
"date-format": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
@ -4013,8 +4034,7 @@
"fast-deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
"dev": true
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
},
"fast-json-stable-stringify": {
"version": "2.0.0",
@ -4433,9 +4453,9 @@
"dev": true
},
"handlebars": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz",
"integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==",
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
"integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
"dev": true,
"requires": {
"neo-async": "^2.6.0",
@ -4730,9 +4750,9 @@
"dev": true
},
"https-proxy-agent": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz",
"integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==",
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz",
"integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==",
"dev": true,
"requires": {
"agent-base": "^4.3.0",
@ -4852,6 +4872,21 @@
"resolve-cwd": "^2.0.0"
}
},
"imsc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/imsc/-/imsc-1.1.0.tgz",
"integrity": "sha512-z2aUE3X00O39fCgkLHEVbfKG/D9cBrmsbf4NjP7K1gQb06YBjgljq5nZD72HYMFG2lRJI7QY7v+JVxg6o6I/Jg==",
"requires": {
"sax": "1.2.1"
},
"dependencies": {
"sax": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
}
}
},
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@ -10129,16 +10164,23 @@
"dev": true
},
"uglify-js": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
"integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==",
"version": "3.6.8",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz",
"integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==",
"dev": true,
"optional": true,
"requires": {
"commander": "~2.20.0",
"commander": "~2.20.3",
"source-map": "~0.6.1"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true,
"optional": true
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -10447,6 +10489,28 @@
"xhr": "2.4.0"
}
},
"videojs-contrib-dash": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/videojs-contrib-dash/-/videojs-contrib-dash-2.11.0.tgz",
"integrity": "sha512-kS+KKlpVfIWwTOYniV3KaKNo8qFoHAv9xpH+M/1g65P97BrmYT7LxMqdI0W9iNnjU/Ot6KLrIj204iDEPjPb/A==",
"requires": {
"dashjs": "2.9.3",
"global": "^4.3.2",
"video.js": "^5.18.0 || ^6 || ^7"
},
"dependencies": {
"dashjs": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/dashjs/-/dashjs-2.9.3.tgz",
"integrity": "sha512-Bhbh+tapes3f6ZUprOR6Yoh61AbRBK/96hC71tog5Rw5eSkBGeDD3l53hRVR9Ex6O1W4iEYxDAhE8c8UVUYEaQ==",
"requires": {
"codem-isoboxer": "0.3.6",
"fast-deep-equal": "2.0.1",
"imsc": "^1.0.2"
}
}
}
},
"videojs-font": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz",

View File

@ -22,12 +22,12 @@
"@angular/platform-browser-dynamic": "~8.2.0",
"@angular/router": "~8.2.0",
"bootstrap": "^4.3.1",
"dashjs": "^3.0.0",
"hammerjs": "^2.0.8",
"jquery": "^3.4.1",
"popper.js": "^1.15.0",
"rxjs": "~6.4.0",
"tslib": "^1.10.0",
"video.js": "^7.6.5",
"zone.js": "~0.9.1"
},
"devDependencies": {
@ -40,6 +40,7 @@
"@types/jasminewd2": "~2.0.3",
"@types/jquery": "^3.3.31",
"@types/node": "~8.9.4",
"@types/video.js": "^7.3.2",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",

View File

@ -14,22 +14,22 @@ import { StreamResolverService } from "./services/stream-resolver.service";
import { ShowDetailsComponent } from './show-details/show-details.component';
const routes: Routes = [
{ path: "browse", component: BrowseComponent, pathMatch: "full", resolve: { shows: LibraryResolverService } },
{ path: "browse/:library-slug", component: BrowseComponent, resolve: { shows: LibraryResolverService } },
{ path: "show/:show-slug", component: ShowDetailsComponent, resolve: { show: ShowResolverService } },
{ path: "browse", component: BrowseComponent, pathMatch: "full", resolve: { shows: LibraryResolverService } },
{ path: "browse/:library-slug", component: BrowseComponent, resolve: { shows: LibraryResolverService } },
{ path: "show/:show-slug", component: ShowDetailsComponent, resolve: { show: ShowResolverService } },
{ path: "collection/:collection-slug", component: CollectionComponent, resolve: { collection: CollectionResolverService } },
{ path: "people/:people-slug", component: CollectionComponent, resolve: { collection: PeopleResolverService } },
{ path: "watch/:item", component: PlayerComponent, resolve: { item: StreamResolverService } },
{ path: "search/:query", component: SearchComponent, resolve: { items: SearchResolverService } },
{ path: "**", component: NotFoundComponent }
{ path: "**", component: NotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes,
{
scrollPositionRestoration: "enabled"
})],
exports: [RouterModule],
imports: [RouterModule.forRoot(routes,
{
scrollPositionRestoration: "enabled"
})],
exports: [RouterModule],
providers: [
LibraryResolverService,
ShowResolverService,

View File

@ -1,8 +1,7 @@
<div id="root">
<div class="player">
<video id="player" poster="backdrop/{{this.item.showSlug}}" autoplay muted (click)="videoClicked()" class="video-js">
<source src="/video/{{this.item.link}}" />
<source src="/video/transmux/{{this.item.link}}" type="video/mp4" />
<div class="player data-vjs-player">
<video id="player" poster="backdrop/{{this.item.showSlug}}" autoplay muted (click)="videoClicked()">
<source src="https://dash.akamaized.net/envivio/EnvivioDash3/manifest.mpd" />
</video>
</div>

View File

@ -4,6 +4,7 @@ import { DomSanitizer, Title } from "@angular/platform-browser";
import { ActivatedRoute, Event, NavigationCancel, NavigationEnd, NavigationStart, Router } from "@angular/router";
import { Track, WatchItem } from "../../models/watch-item";
import { Location } from "@angular/common";
import { MediaPlayer } from "dashjs";
declare var SubtitleManager: any;
@ -337,7 +338,9 @@ export class PlayerComponent implements OnInit
init()
{
//Load sub selected from the url.
var dashPlayer = MediaPlayer().create();
dashPlayer.initialize(this.player, "/video/transmux/" + this.item.link, true);
let sub: string = this.route.snapshot.queryParams["sub"];
if (sub != null)
{

View File

@ -1,21 +1,21 @@
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Show } from "../../models/show";
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
import { EMPTY, Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { WatchItem } from "../../models/watch-item";
@Injectable()
export class StreamResolverService implements Resolve<Show>
export class StreamResolverService implements Resolve<WatchItem>
{
constructor(private http: HttpClient, private snackBar: MatSnackBar) { }
resolve(route: ActivatedRouteSnapshot): Show | Observable<Show> | Promise<Show>
resolve(route: ActivatedRouteSnapshot): WatchItem | Observable<WatchItem> | Promise<WatchItem>
{
let item: string = route.paramMap.get("item");
return this.http.get<Show>("api/watch/" + item).pipe(catchError((error: HttpErrorResponse) =>
return this.http.get<WatchItem>("api/watch/" + item).pipe(catchError((error: HttpErrorResponse) =>
{
console.log(error.status + " - " + error.message);
if (error.status == 404)

View File

@ -8,6 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#000000" />
<link rel="icon" type="image/x-icon" href="favicon.ico">
<script>window.VIDEOJS_NO_DYNAMIC_STYLE = true</script>
</head>
<body>
<app-root></app-root>

View File

@ -6,7 +6,6 @@ export interface WatchItem
showSlug: string;
seasonNumber: number;
episodeNumber: number;
video: string;
title: string;
link: string;
duration: number;

View File

@ -37,7 +37,7 @@ namespace Kyoo.Controllers
{
string path = transcoder.Transmux(episode);
if (path != null)
return PhysicalFile(path, "video/mp4", true);
return PhysicalFile(path, "application/dash+xml", true);
else
return StatusCode(500);
}

View File

@ -0,0 +1,8 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "C++ bindings", Scope = "member", Target = "~M:Kyoo.InternalAPI.TranscoderLink.TranscoderAPI.transmux(System.String,System.String)~System.Int32")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "C++ bindings", Scope = "member", Target = "~M:Kyoo.InternalAPI.TranscoderLink.TranscoderAPI.free_memory(System.IntPtr)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "C++ bindings", Scope = "member", Target = "~M:Kyoo.InternalAPI.TranscoderLink.TranscoderAPI.extract_subtitles(System.String,System.String,System.Int32@,System.Int32@)~System.IntPtr")]

View File

@ -38,9 +38,9 @@ namespace Kyoo.InternalAPI
public string Transmux(WatchItem episode)
{
string temp = Path.Combine(tempPath, episode.Link + ".mp4");
string temp = Path.Combine(tempPath, episode.Link + "/" + episode.Link + ".mpd");
Debug.WriteLine("&Transmuxing " + episode.Link + " at " + episode.Path + ", outputPath: " + temp);
if (File.Exists(temp) || TranscoderAPI.Transmux(episode.Path, temp) == 0)
if (File.Exists(temp) || TranscoderAPI.transmux(episode.Path, temp) == 0)
return temp;
else
return null;

View File

@ -14,19 +14,19 @@ namespace Kyoo.InternalAPI.TranscoderLink
public extern static int Init();
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
public extern static int Transmux(string path, string outPath);
public extern static int transmux(string path, string out_path);
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
private extern static IntPtr ExtractSubtitles(string path, string outPath, out int arrayLength, out int trackCount);
private extern static IntPtr extract_subtitles(string path, string out_path, out int array_length, out int track_count);
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
private extern static void FreeMemory(IntPtr streamsPtr);
private extern static void free_memory(IntPtr stream_ptr);
public static void ExtractSubtitles(string path, string outPath, out Track[] tracks)
{
int size = Marshal.SizeOf<Stream>();
IntPtr ptr = ExtractSubtitles(path, outPath, out int arrayLength, out int trackCount);
IntPtr ptr = extract_subtitles(path, outPath, out int arrayLength, out int trackCount);
IntPtr streamsPtr = ptr;
if (trackCount > 0 && ptr != IntPtr.Zero)
{
@ -47,12 +47,8 @@ namespace Kyoo.InternalAPI.TranscoderLink
else
tracks = null;
FreeMemory(ptr);
free_memory(ptr);
Debug.WriteLine("&" + tracks?.Length + " tracks got at: " + path);
}
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
private extern static IntPtr TestMemory(string path, string outPath, out int arrayLength, out int trackCount);
}
}

View File

@ -41,6 +41,7 @@
<None Remove="ClientApp\src\models\show.ts" />
<None Remove="ClientApp\src\models\studio.ts" />
<None Remove="ClientApp\src\models\watch-item.ts" />
<None Remove="Transcoder\Kyoo.Transcoder.exp" />
</ItemGroup>
<ItemGroup>
@ -108,9 +109,6 @@
<None Update="Transcoder\Kyoo.Transcoder.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Transcoder\Kyoo.Transcoder.exp">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Transcoder\Kyoo.Transcoder.ilk">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>

Binary file not shown.

Binary file not shown.

Binary file not shown.