From 3c47c88767ef135f132ab957a0a613ccb444f976 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 25 Sep 2019 01:06:37 +0200 Subject: [PATCH] Creating a Transmux function and cleaning up the Transcoder code. --- Kyoo.Transcoder/src/Transcoder.cpp | 240 ++++++++++++------ Kyoo.Transcoder/src/Transcoder.h | 2 + Kyoo/ClientApp/angular.json | 3 +- Kyoo/ClientApp/package-lock.json | 224 ++++++++++++++-- Kyoo/ClientApp/package.json | 1 + .../src/app/player/player.component.html | 6 +- .../src/app/player/player.component.ts | 10 +- Kyoo/Controllers/VideoController.cs | 21 +- Kyoo/InternalAPI/Transcoder/ITranscoder.cs | 2 +- Kyoo/InternalAPI/Transcoder/Transcoder.cs | 13 +- Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs | 3 + Kyoo/config.json | 1 + 12 files changed, 409 insertions(+), 117 deletions(-) diff --git a/Kyoo.Transcoder/src/Transcoder.cpp b/Kyoo.Transcoder/src/Transcoder.cpp index 05a4c777..4c322b8e 100644 --- a/Kyoo.Transcoder/src/Transcoder.cpp +++ b/Kyoo.Transcoder/src/Transcoder.cpp @@ -21,33 +21,178 @@ int Init() return 42; } -Stream* ExtractSubtitles(const char* path, const char* outPath, int* streamCount) -{ - AVFormatContext* inputContext = NULL; - if (avformat_open_input(&inputContext, path, NULL, NULL)) +#pragma region InternalProcess +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 0; + return 1; } - if (avformat_find_stream_info(inputContext, NULL) < 0) + if (avformat_find_stream_info(*inputContext, NULL) < 0) { std::cout << "Error: Could't find streams informations for the file at " << path << std::endl; - return 0; + return 1; } - av_dump_format(inputContext, 0, path, false); + av_dump_format(*inputContext, 0, path, false); + return 0; +} - std::vector* subtitleStreams = new std::vector(); +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; + + 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 + + + + + + + +//Should add goto end; +int Transmux(const char *path, const char *outPath) +{ + AVFormatContext *inputContext = NULL; + int ret = 0; + + if (open_input_context(&inputContext, path) != 0) + return 1; + + AVFormatContext *outputContext = NULL; + if (avformat_alloc_output_context2(&outputContext, NULL, NULL, outPath) < 0) + { + std::cout << "Error: Couldn't create an output file." << std::endl; + return 1; + } + + for (unsigned int i = 0; i < inputContext->nb_streams; i++) + { + AVStream *stream = inputContext->streams[i]; + if(stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO || stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) //Should support multi-audio on a good format. + if (copy_stream_to_output(outputContext, stream) == NULL) + return 1; + } + + av_dump_format(outputContext, 0, outPath, true); + if (open_output_file_for_write(outputContext, outPath) != 0) + return 1; + + AVPacket pkt; + while (av_read_frame(inputContext, &pkt) == 0) + { + if (pkt.stream_index >= outputContext->nb_streams) + continue; + + AVStream *inputStream = inputContext->streams[pkt.stream_index]; + AVStream *outputStream = outputContext->streams[pkt.stream_index]; + + 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; + + av_packet_unref(&pkt); + } + + + avformat_close_input(&inputContext); + av_write_trailer(outputContext); + + if (outputContext && !(outputContext->flags & AVFMT_NOFILE)) + avio_closep(&outputContext->pb); + avformat_free_context(outputContext); + return ret; +} + + +Stream *ExtractSubtitles(const char *path, const char *outPath, int *streamCount) +{ + AVFormatContext *inputContext = NULL; + + if (open_input_context(&inputContext, path) != 0) + return 0; + + std::vector *subtitleStreams = new std::vector(); const unsigned int outputCount = inputContext->nb_streams; - AVFormatContext** outputList = new AVFormatContext*[outputCount]; + AVFormatContext **outputList = new AVFormatContext*[outputCount]; //Initialize output and set headers. for (unsigned int i = 0; i < inputContext->nb_streams; i++) { - AVStream* inputStream = inputContext->streams[i]; - const AVCodecParameters* inputCodecpar = inputStream->codecpar; + AVStream *inputStream = inputContext->streams[i]; + const AVCodecParameters *inputCodecpar = inputStream->codecpar; if (inputCodecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) outputList[i] = NULL; @@ -62,7 +207,7 @@ Stream* ExtractSubtitles(const char* path, const char* outPath, int* streamCount NULL); //Path builder references //Create the language subfolder - std::stringstream outStream /*= new std::stringstream()*/; + std::stringstream outStream; outStream << outPath << (char)std::filesystem::path::preferred_separator << stream.language; std::filesystem::create_directory(outStream.str()); @@ -90,7 +235,7 @@ Stream* ExtractSubtitles(const char* path, const char* outPath, int* streamCount subtitleStreams->push_back(stream); std::cout << "Stream #" << i << "(" << stream.language << "), stream type: " << inputCodecpar->codec_type << " codec: " << stream.codec << std::endl; - AVFormatContext* outputContext = NULL; + AVFormatContext *outputContext = NULL; if (avformat_alloc_output_context2(&outputContext, NULL, NULL, stream.path) < 0) { std::cout << "Error: Couldn't create an output file." << std::endl; @@ -99,63 +244,14 @@ Stream* ExtractSubtitles(const char* path, const char* outPath, int* streamCount av_dict_copy(&outputContext->metadata, inputContext->metadata, NULL); - AVStream* outputStream = avformat_new_stream(outputContext, NULL); + AVStream* outputStream = copy_stream_to_output(outputContext, inputStream); if (outputStream == NULL) - { - std::cout << "Error: Couldn't create stream." << std::endl; goto end; - } - - if (avcodec_parameters_copy(outputStream->codecpar, inputCodecpar) < 0) - { - std::cout << "Error: Couldn't copy parameters to the output file." << std::endl; - goto end; - } - 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; - - 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); - // } - //} av_dump_format(outputContext, 0, stream.path, true); - if (!(outputContext->flags & AVFMT_NOFILE)) - { - if (avio_open(&outputContext->pb, stream.path, AVIO_FLAG_WRITE) < 0) - { - std::cout << "Error: Couldn't open file at " << stream.path << std::endl; - goto end; - } - } - 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 " << stream.path << std::endl; + if (open_output_file_for_write(outputContext, stream.path) != 0) goto end; - } outputList[i] = outputContext; @@ -179,22 +275,18 @@ Stream* ExtractSubtitles(const char* path, const char* outPath, int* streamCount if (pkt.stream_index >= outputCount) continue; - AVFormatContext* outputContext = outputList[pkt.stream_index]; + AVFormatContext *outputContext = outputList[pkt.stream_index]; if(outputContext == nullptr) { av_packet_unref(&pkt); continue; } - AVStream* inputStream = inputContext->streams[pkt.stream_index]; - AVStream* outputStream = outputContext->streams[0]; + AVStream *inputStream = inputContext->streams[pkt.stream_index]; + AVStream *outputStream = outputContext->streams[0]; pkt.stream_index = 0; - - 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; + process_packet(pkt, inputStream, outputStream); if (av_interleaved_write_frame(outputContext, &pkt) < 0) { @@ -208,7 +300,7 @@ Stream* ExtractSubtitles(const char* path, const char* outPath, int* streamCount for (unsigned int i = 0; i < outputCount; i++) { - AVFormatContext* outputContext = outputList[i]; + AVFormatContext *outputContext = outputList[i]; if (outputContext == NULL) continue; diff --git a/Kyoo.Transcoder/src/Transcoder.h b/Kyoo.Transcoder/src/Transcoder.h index 2f3c9982..903f0af1 100644 --- a/Kyoo.Transcoder/src/Transcoder.h +++ b/Kyoo.Transcoder/src/Transcoder.h @@ -13,3 +13,5 @@ extern "C" API int Init(); //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* streamsCount); + +extern "C" API int Transmux(const char* path, const char* outPath); diff --git a/Kyoo/ClientApp/angular.json b/Kyoo/ClientApp/angular.json index 3169896d..0f2046fc 100644 --- a/Kyoo/ClientApp/angular.json +++ b/Kyoo/ClientApp/angular.json @@ -32,7 +32,8 @@ "scripts": [ "./node_modules/jquery/dist/jquery.min.js", "./node_modules/bootstrap/dist/js/bootstrap.bundle.min.js", - "./src/libraries/subtitles.js" + "./src/libraries/subtitles.js", + "./node_modules/video.js/dist/video.min.js" ] }, "configurations": { diff --git a/Kyoo/ClientApp/package-lock.json b/Kyoo/ClientApp/package-lock.json index 606ee9b9..39ca2711 100644 --- a/Kyoo/ClientApp/package-lock.json +++ b/Kyoo/ClientApp/package-lock.json @@ -1134,6 +1134,21 @@ "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", "dev": true }, + "@babel/runtime": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.2.tgz", + "integrity": "sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg==", + "requires": { + "regenerator-runtime": "^0.13.2" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + } + } + }, "@babel/template": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", @@ -1349,6 +1364,20 @@ } } }, + "@videojs/http-streaming": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-1.10.6.tgz", + "integrity": "sha512-uPBuunHnxWeFRYxRX0j6h1IIWv3+QKvSkZGmW9TvqxWBqeNGSrQymR6tm1nVjQ2HhMVxVphQTUhUTTPDVWqmQg==", + "requires": { + "aes-decrypter": "3.0.0", + "global": "^4.3.0", + "m3u8-parser": "4.4.0", + "mpd-parser": "0.8.1", + "mux.js": "5.2.1", + "url-toolkit": "^2.1.3", + "video.js": "^6.8.0 || ^7.0.0" + } + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -1575,6 +1604,16 @@ "integrity": "sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw==", "dev": true }, + "aes-decrypter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.0.0.tgz", + "integrity": "sha1-eEihwUW5/b9Xrj4rWxvHzwZEqPs=", + "requires": { + "commander": "^2.9.0", + "global": "^4.3.2", + "pkcs7": "^1.0.2" + } + }, "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", @@ -2791,8 +2830,7 @@ "commander": { "version": "2.20.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", - "dev": true + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" }, "commondir": { "version": "1.0.1", @@ -3203,7 +3241,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -3415,6 +3452,11 @@ "void-elements": "^2.0.0" } }, + "dom-walk": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" + }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -3631,7 +3673,6 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", - "dev": true, "requires": { "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", @@ -3645,7 +3686,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -4130,6 +4170,14 @@ } } }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -4250,8 +4298,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "genfun": { "version": "5.0.0", @@ -4324,6 +4371,22 @@ } } }, + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "requires": { + "min-document": "^2.19.0", + "process": "~0.5.1" + }, + "dependencies": { + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" + } + } + }, "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", @@ -4409,7 +4472,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -4455,8 +4517,7 @@ "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" }, "has-value": { "version": "1.0.0", @@ -4803,6 +4864,11 @@ "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", "dev": true }, + "individual": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz", + "integrity": "sha1-gzsJfa0jKU52EXqY+zjg2a1hu5c=" + }, "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -4956,8 +5022,7 @@ "is-callable": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" }, "is-data-descriptor": { "version": "0.1.4", @@ -4982,8 +5047,7 @@ "is-date-object": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" }, "is-descriptor": { "version": "0.1.6", @@ -5037,6 +5101,11 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "is-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", + "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" + }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", @@ -5101,7 +5170,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, "requires": { "has": "^1.0.1" } @@ -5116,7 +5184,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, "requires": { "has-symbols": "^1.0.0" } @@ -6312,6 +6379,11 @@ "source-map-support": "^0.5.5" } }, + "keycode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", + "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" + }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -6488,6 +6560,14 @@ "yallist": "^3.0.2" } }, + "m3u8-parser": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.4.0.tgz", + "integrity": "sha512-iH2AygTFILtato+XAgnoPYzLHM4R3DjATj7Ozbk7EHdB2XoLF2oyOUguM7Kc4UVHbQHHL/QPaw98r7PbWzG0gg==", + "requires": { + "global": "^4.3.2" + } + }, "magic-string": { "version": "0.25.3", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.3.tgz", @@ -6787,6 +6867,14 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "^0.1.0" + } + }, "mini-css-extract-plugin": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz", @@ -6933,6 +7021,15 @@ "run-queue": "^1.0.3" } }, + "mpd-parser": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.8.1.tgz", + "integrity": "sha512-WBTJ1bKk8OLUIxBh6s1ju1e2yz/5CzhPbgi6P3F3kJHKhGy1Z+ElvEnuzEbtC/dnbRcJtMXazE3f93N5LLdp9Q==", + "requires": { + "global": "^4.3.2", + "url-toolkit": "^2.1.1" + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -6961,6 +7058,11 @@ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, + "mux.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-5.2.1.tgz", + "integrity": "sha512-1t2payD3Y8izfZRq7tfUQlhL2fKzjeLr9v1/2qNCTkEQnd9Abtn1JgzsBgGZubEXh6lM5L8B0iLGoWQiukjtbQ==" + }, "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", @@ -7270,8 +7372,7 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-visit": { "version": "1.0.1", @@ -7586,6 +7687,15 @@ "safe-buffer": "^5.1.1" } }, + "parse-headers": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.2.tgz", + "integrity": "sha512-/LypJhzFmyBIDYP9aDVgeyEb5sQfbfY5mnDq4hVhlQ69js87wXfmEI5V3xI6vvXasqebp0oCytYFLxsBVfCzSg==", + "requires": { + "for-each": "^0.3.3", + "string.prototype.trim": "^1.1.2" + } + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -7743,6 +7853,11 @@ "pinkie": "^2.0.0" } }, + "pkcs7": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.2.tgz", + "integrity": "sha1-ttulJ1KMKUK/wSLOLa/NteWQdOc=" + }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", @@ -8557,6 +8672,14 @@ "aproba": "^1.1.1" } }, + "rust-result": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz", + "integrity": "sha1-NMdbLm3Dn+WHXlveyFteD5FTb3I=", + "requires": { + "individual": "^2.0.0" + } + }, "rxjs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz", @@ -8571,6 +8694,14 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "safe-json-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz", + "integrity": "sha1-fA9XjPzNEtM6ccDgVBPi7KFx6qw=", + "requires": { + "rust-result": "^1.0.0" + } + }, "safe-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", @@ -9579,6 +9710,16 @@ } } }, + "string.prototype.trim": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.0.tgz", + "integrity": "sha512-9EIjYD/WdlvLpn987+ctkLf0FfvBefOCuiEr2henD8X+7jfwPnyvTdmW8OJhj5p+M0/96mBdynLWkxUr+rHlpg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.13.0", + "function-bind": "^1.1.1" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -10172,6 +10313,11 @@ "requires-port": "^1.0.0" } }, + "url-toolkit": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.1.6.tgz", + "integrity": "sha512-UaZ2+50am4HwrV2crR/JAf63Q4VvPYphe63WGeoJxeu8gmOm0qxPt+KsukfakPNrX9aymGNEkkaoICwn+OuvBw==" + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -10286,6 +10432,34 @@ "extsprintf": "^1.2.0" } }, + "video.js": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.6.5.tgz", + "integrity": "sha512-r0kC9SNJhXz9th/wwbRaLVOIZTvXkF+rhFq9/FWU+e+EJClwClRCgP8STGmfrPHDXrfWiJwH9YY21JZK61vGGQ==", + "requires": { + "@babel/runtime": "^7.4.5", + "@videojs/http-streaming": "1.10.6", + "global": "4.3.2", + "keycode": "^2.2.0", + "safe-json-parse": "4.0.0", + "videojs-font": "3.2.0", + "videojs-vtt.js": "^0.14.1", + "xhr": "2.4.0" + } + }, + "videojs-font": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz", + "integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA==" + }, + "videojs-vtt.js": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.14.1.tgz", + "integrity": "sha512-YxOiywx6N9t3J5nqsE5WN2Sw4CSqVe3zV+AZm2T4syOc2buNJaD6ZoexSdeszx2sHLU/RRo2r4BJAXFDQ7Qo2Q==", + "requires": { + "global": "^4.3.1" + } + }, "vm-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", @@ -11994,6 +12168,17 @@ "ultron": "~1.1.0" } }, + "xhr": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.4.0.tgz", + "integrity": "sha1-4W5mpF+GmGHu76tBbV7/ci3ECZM=", + "requires": { + "global": "~4.3.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, "xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", @@ -12027,8 +12212,7 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { "version": "4.0.0", diff --git a/Kyoo/ClientApp/package.json b/Kyoo/ClientApp/package.json index 1da988cb..62f978c0 100644 --- a/Kyoo/ClientApp/package.json +++ b/Kyoo/ClientApp/package.json @@ -27,6 +27,7 @@ "popper.js": "^1.15.0", "rxjs": "~6.4.0", "tslib": "^1.10.0", + "video.js": "^7.6.5", "zone.js": "~0.9.1" }, "devDependencies": { diff --git a/Kyoo/ClientApp/src/app/player/player.component.html b/Kyoo/ClientApp/src/app/player/player.component.html index d3657398..63372442 100644 --- a/Kyoo/ClientApp/src/app/player/player.component.html +++ b/Kyoo/ClientApp/src/app/player/player.component.html @@ -1,10 +1,8 @@
-
diff --git a/Kyoo/ClientApp/src/app/player/player.component.ts b/Kyoo/ClientApp/src/app/player/player.component.ts index 688b579d..08122f09 100644 --- a/Kyoo/ClientApp/src/app/player/player.component.ts +++ b/Kyoo/ClientApp/src/app/player/player.component.ts @@ -1,10 +1,8 @@ -import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; -import { WatchItem, Track } from "../../models/watch-item"; -import { ActivatedRoute, Router } from "@angular/router"; -import { DomSanitizer, Title } from "@angular/platform-browser"; -import { Location } from "@angular/common"; -import { MatSliderChange } from "@angular/material/slider"; +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { MatSnackBar } from "@angular/material/snack-bar"; +import { DomSanitizer, Title } from "@angular/platform-browser"; +import { ActivatedRoute, Router } from "@angular/router"; +import { Track, WatchItem } from "../../models/watch-item"; declare var SubtitleManager: any; diff --git a/Kyoo/Controllers/VideoController.cs b/Kyoo/Controllers/VideoController.cs index 0d379422..e374282b 100644 --- a/Kyoo/Controllers/VideoController.cs +++ b/Kyoo/Controllers/VideoController.cs @@ -22,35 +22,38 @@ namespace Kyoo.Controllers { WatchItem episode = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber); - if (System.IO.File.Exists(episode.Path)) + if (episode != null && System.IO.File.Exists(episode.Path)) return PhysicalFile(episode.Path, "video/x-matroska", true); else return NotFound(); } - [HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}/stream")] - public IActionResult Stream(string showSlug, long seasonNumber, long episodeNumber) + [HttpGet("transmux/{showSlug}-s{seasonNumber}e{episodeNumber}")] + public IActionResult Transmux(string showSlug, long seasonNumber, long episodeNumber) { WatchItem episode = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber); - if (System.IO.File.Exists(episode.Path)) + if (episode != null && System.IO.File.Exists(episode.Path)) { - string path = transcoder.Stream(episode.Path); - return PhysicalFile(path, "video/mp4", true); + string path = transcoder.Transmux(episode); + if (path != null) + return PhysicalFile(path, "video/mp4", true); + else + return StatusCode(500); } else return NotFound(); } - [HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}/transcode")] + [HttpGet("transcode/{showSlug}-s{seasonNumber}e{episodeNumber}")] public IActionResult Transcode(string showSlug, long seasonNumber, long episodeNumber) { WatchItem episode = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber); - if (System.IO.File.Exists(episode.Path)) + if (episode != null && System.IO.File.Exists(episode.Path)) { string path = transcoder.Transcode(episode.Path); - return PhysicalFile(path, "video/mp4", true); + return PhysicalFile(path, "video/mp4", true); //Should use mpeg dash } else return NotFound(); diff --git a/Kyoo/InternalAPI/Transcoder/ITranscoder.cs b/Kyoo/InternalAPI/Transcoder/ITranscoder.cs index 53bc83ec..80cf84f3 100644 --- a/Kyoo/InternalAPI/Transcoder/ITranscoder.cs +++ b/Kyoo/InternalAPI/Transcoder/ITranscoder.cs @@ -6,7 +6,7 @@ namespace Kyoo.InternalAPI public interface ITranscoder { //Should transcode to a mp4 container (same video/audio format if possible, no subtitles). - string Stream(string path); + string Transmux(WatchItem episode); //Should transcode to a mp4 container with a h264 video format and a AAC audio format, no subtitles. string Transcode(string path); diff --git a/Kyoo/InternalAPI/Transcoder/Transcoder.cs b/Kyoo/InternalAPI/Transcoder/Transcoder.cs index 0a599601..76b6ad06 100644 --- a/Kyoo/InternalAPI/Transcoder/Transcoder.cs +++ b/Kyoo/InternalAPI/Transcoder/Transcoder.cs @@ -10,8 +10,12 @@ namespace Kyoo.InternalAPI { public class Transcoder : ITranscoder { + private readonly string tempPath; + public Transcoder(IConfiguration config) { + tempPath = config.GetValue("tempPath"); + Debug.WriteLine("&Api INIT: " + TranscoderAPI.Init()); } @@ -29,9 +33,14 @@ namespace Kyoo.InternalAPI Debug.WriteLine("&Getting video..."); } - public string Stream(string path) + public string Transmux(WatchItem episode) { - return @"D:\Videos\Anohana\AnoHana S01E01.mp4"; + string temp = Path.Combine(tempPath, episode.Link + ".mp4"); + Debug.WriteLine("&Transmuxing " + episode.Link + " at " + episode.Path + ", outputPath: " + temp); + if (File.Exists(temp) || TranscoderAPI.Transmux(episode.Path, temp) == 0) + return temp; + else + return null; } public string Transcode(string path) diff --git a/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs b/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs index 3c9ab86a..b222b609 100644 --- a/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs +++ b/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs @@ -12,6 +12,9 @@ namespace Kyoo.InternalAPI.TranscoderLink [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] public extern static int Init(); + [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] + public extern static int Transmux(string path, string outPath); + [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] private extern static IntPtr ExtractSubtitles(string path, string outPath, out int streams); diff --git a/Kyoo/config.json b/Kyoo/config.json index edff394a..0d31b949 100644 --- a/Kyoo/config.json +++ b/Kyoo/config.json @@ -1,5 +1,6 @@ { "databasePath": "C://Projects/database.db", + "tempPath": "C:\\Projects", "peoplePath": "D://Videos/People", "plugins": "C:\\Projects\\Kyoo\\Debug", "providerPlugins": "C://Projects/Plugins/Providers",