You've already forked N_m3u8DL-CLI
mirror of
https://github.com/nilaoda/N_m3u8DL-CLI
synced 2025-09-05 23:29:33 +02:00
Compare commits
289 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7dd2b6cee2 | ||
![]() |
f744aa990e | ||
![]() |
97c9f444b8 | ||
![]() |
8aac37bbe2 | ||
![]() |
b7e19ec699 | ||
![]() |
7e7c5c6ba9 | ||
![]() |
50722079ba | ||
![]() |
d529ece032 | ||
![]() |
9ed8b359a1 | ||
![]() |
a7591e3978 | ||
![]() |
094a89bc79 | ||
![]() |
9e7b603d3b | ||
![]() |
3801090928 | ||
![]() |
42c1d628d4 | ||
![]() |
ecefecd827 | ||
![]() |
4661818c1f | ||
![]() |
fa61cd136d | ||
![]() |
472fb0cf02 | ||
![]() |
28e39a94ac | ||
![]() |
a42ade64f3 | ||
![]() |
539f773e37 | ||
![]() |
d6f27cf90f | ||
![]() |
8912ee5e4b | ||
![]() |
409437b2c7 | ||
![]() |
350d3b508d | ||
![]() |
1e3f06eaae | ||
![]() |
2d4bc42d55 | ||
![]() |
68e06d3fa7 | ||
![]() |
6c3340e94e | ||
![]() |
051faa9f2b | ||
![]() |
115f9c9424 | ||
![]() |
faf67cd527 | ||
![]() |
38d1a1a2dc | ||
![]() |
12eb68d592 | ||
![]() |
0804e295e5 | ||
![]() |
847c4683cb | ||
![]() |
8c72947860 | ||
![]() |
f0b240a6ee | ||
![]() |
793cf53042 | ||
![]() |
612fc29197 | ||
![]() |
307e2389de | ||
![]() |
1c932abdc3 | ||
![]() |
314f0065c7 | ||
![]() |
59060bb74d | ||
![]() |
cab882c3a3 | ||
![]() |
9955532ce5 | ||
![]() |
7e127be8c2 | ||
![]() |
b46571a57f | ||
![]() |
da5861d907 | ||
![]() |
92bc91a1fb | ||
![]() |
439f50103e | ||
![]() |
8a95e31b2f | ||
![]() |
115b8a156a | ||
![]() |
120bcaebb5 | ||
![]() |
455d56707c | ||
![]() |
048adcf118 | ||
![]() |
fe5aa27b1c | ||
![]() |
039aa489b1 | ||
![]() |
14e80f0b06 | ||
![]() |
2256fff549 | ||
![]() |
84cfd4e138 | ||
![]() |
e70c229135 | ||
![]() |
8b520d0c19 | ||
![]() |
71d69de51a | ||
![]() |
bc89ead00d | ||
![]() |
ae79d6eb3a | ||
![]() |
96bd8af883 | ||
![]() |
89b1e30e0f | ||
![]() |
7a741359ab | ||
![]() |
564b6ad291 | ||
![]() |
a0fc9404f7 | ||
![]() |
5267be1699 | ||
![]() |
20bfda39e7 | ||
![]() |
ba4c0eeda7 | ||
![]() |
5d72e24002 | ||
![]() |
a87c051d23 | ||
![]() |
44e1b68d6b | ||
![]() |
e65dfa52cd | ||
![]() |
965c173899 | ||
![]() |
880af02cc2 | ||
![]() |
2742de43c4 | ||
![]() |
9d8cb57390 | ||
![]() |
9e2a192dab | ||
![]() |
33cf9e2256 | ||
![]() |
2959cbbb5c | ||
![]() |
c2eb8a6adc | ||
![]() |
e1b591b81c | ||
![]() |
334b1939b5 | ||
![]() |
7e916b65fd | ||
![]() |
4ead563fa2 | ||
![]() |
1b387a06e5 | ||
![]() |
6e7b4ac7ea | ||
![]() |
e98c5205d1 | ||
![]() |
d7890dd124 | ||
![]() |
82f2111522 | ||
![]() |
4c3207586f | ||
![]() |
69b411e37c | ||
![]() |
1e8525041f | ||
![]() |
65ae72d4a4 | ||
![]() |
4a4bfae5ab | ||
![]() |
d586dddfcd | ||
![]() |
fca6b3ff6c | ||
![]() |
5d75626a36 | ||
![]() |
a94271c244 | ||
![]() |
c51118dce7 | ||
![]() |
81b2e87bf7 | ||
![]() |
71a9878aaa | ||
![]() |
769fe4e926 | ||
![]() |
1f57ba7c09 | ||
![]() |
71282bda30 | ||
![]() |
41ee8aebdf | ||
![]() |
a4537bc093 | ||
![]() |
b8a60b3917 | ||
![]() |
8091dd290f | ||
![]() |
d48e84e611 | ||
![]() |
9f5423a437 | ||
![]() |
ce7e38770a | ||
![]() |
8fdb2e918e | ||
![]() |
d4b7d240c1 | ||
![]() |
484d2941ed | ||
![]() |
a0f2b66575 | ||
![]() |
65cc0681e2 | ||
![]() |
7d980ec9a2 | ||
![]() |
ec5892c05a | ||
![]() |
9aed50fbf9 | ||
![]() |
d657b455cd | ||
![]() |
cda6575605 | ||
![]() |
0d6377d41b | ||
![]() |
9c76bdcbce | ||
![]() |
83915ff606 | ||
![]() |
9cd4746f33 | ||
![]() |
33a5b917ac | ||
![]() |
f69978bd82 | ||
![]() |
d9fd526886 | ||
![]() |
1cc8ecfaaf | ||
![]() |
2bc2dde2ad | ||
![]() |
c3ddcf9e0e | ||
![]() |
ba5d20dd02 | ||
![]() |
16f705fe66 | ||
![]() |
d141cabc4a | ||
![]() |
adcf884a93 | ||
![]() |
9d903a025f | ||
![]() |
0bd23ab641 | ||
![]() |
a38f27ccd7 | ||
![]() |
3acec5efd3 | ||
![]() |
90874e4bfe | ||
![]() |
b94768e3e8 | ||
![]() |
311f3b882e | ||
![]() |
4b4f537984 | ||
![]() |
8032d50b42 | ||
![]() |
f615764e55 | ||
![]() |
ccaa200ef8 | ||
![]() |
6058d878eb | ||
![]() |
c712c6dee0 | ||
![]() |
bb24bb998f | ||
![]() |
e9d951efa5 | ||
![]() |
6f88a805ef | ||
![]() |
6aa6d63a8d | ||
![]() |
147246caba | ||
![]() |
35c1ee4777 | ||
![]() |
8b32081b85 | ||
![]() |
c00de328d1 | ||
![]() |
d5193c1645 | ||
![]() |
c06fbf5820 | ||
![]() |
e700edba56 | ||
![]() |
aad948da7c | ||
![]() |
14f7b20176 | ||
![]() |
a66a9a4096 | ||
![]() |
c862f23a9c | ||
![]() |
1a722e80de | ||
![]() |
eff43e8ac3 | ||
![]() |
7648f8f8dc | ||
![]() |
1edc1a43fe | ||
![]() |
01e7735018 | ||
![]() |
a0c41d6116 | ||
![]() |
c3c25774de | ||
![]() |
ba40641a21 | ||
![]() |
5d76418780 | ||
![]() |
fd48b766b5 | ||
![]() |
0f25cc0ae8 | ||
![]() |
1c6bd688e3 | ||
![]() |
480857cc3b | ||
![]() |
52af9a44a8 | ||
![]() |
84d137b504 | ||
![]() |
a4f1064c81 | ||
![]() |
eac08b12f8 | ||
![]() |
c81c911888 | ||
![]() |
361e901e5b | ||
![]() |
3567f4c4cc | ||
![]() |
2f7b0f7304 | ||
![]() |
000d0db4ae | ||
![]() |
d551ad52c1 | ||
![]() |
c064c83c21 | ||
![]() |
f6ed0f9e4d | ||
![]() |
f65fee94ca | ||
![]() |
ce64a92b0d | ||
![]() |
42790ce540 | ||
![]() |
83d8ca1c8c | ||
![]() |
4b5a64eb98 | ||
![]() |
2b5af09c3b | ||
![]() |
6368adc2ab | ||
![]() |
fcd7840091 | ||
![]() |
d77cb62dff | ||
![]() |
34394c6a2b | ||
![]() |
091cba8555 | ||
![]() |
b02b6b7168 | ||
![]() |
b7408b0599 | ||
![]() |
b83cb35170 | ||
![]() |
ed0a7b71a7 | ||
![]() |
50eae19bf3 | ||
![]() |
cba8f3ea52 | ||
![]() |
7fd93e1232 | ||
![]() |
74a7e3c3ec | ||
![]() |
849d712e11 | ||
![]() |
7544f3a02c | ||
![]() |
0120736c53 | ||
![]() |
e4bde4926c | ||
![]() |
d42cd6a60d | ||
![]() |
175f13adc9 | ||
![]() |
72f1c043b1 | ||
![]() |
a2e2070f5d | ||
![]() |
6c96deb366 | ||
![]() |
2bd900ee5d | ||
![]() |
1261810510 | ||
![]() |
26823dbd7e | ||
![]() |
698699d9fc | ||
![]() |
ebed7fa1e3 | ||
![]() |
73a8348155 | ||
![]() |
5ace0b3a4f | ||
![]() |
5abe889da0 | ||
![]() |
f1070fd1b4 | ||
![]() |
4af82cc7f9 | ||
![]() |
809380b7ab | ||
![]() |
ab57420507 | ||
![]() |
80230f12fe | ||
![]() |
a6c7c0fd8c | ||
![]() |
bd6df6b58c | ||
![]() |
e0a9071d62 | ||
![]() |
5c9bcf72d2 | ||
![]() |
7cf2c12d0c | ||
![]() |
1b35fe2d2c | ||
![]() |
ed3aae1cb9 | ||
![]() |
01c2ecbeb5 | ||
![]() |
9993ec8177 | ||
![]() |
464300c860 | ||
![]() |
45fa58a46f | ||
![]() |
f93ddc7107 | ||
![]() |
05f450fa6d | ||
![]() |
580c374d7b | ||
![]() |
b58dc5d8e0 | ||
![]() |
f50cf7862a | ||
![]() |
fdad68d483 | ||
![]() |
d7aaa5323b | ||
![]() |
6c2e13b800 | ||
![]() |
086fc57958 | ||
![]() |
bc349b8977 | ||
![]() |
cc4efed3c6 | ||
![]() |
cf958e833b | ||
![]() |
fb09add0cd | ||
![]() |
5a3c5baefd | ||
![]() |
839afd8e61 | ||
![]() |
338c7a25d0 | ||
![]() |
f57ce8c2da | ||
![]() |
a5009e1683 | ||
![]() |
66933da9de | ||
![]() |
136389e248 | ||
![]() |
7971104bd4 | ||
![]() |
cc4941554d | ||
![]() |
c50371080a | ||
![]() |
154c5f84a1 | ||
![]() |
1556ee42df | ||
![]() |
121ff9f226 | ||
![]() |
e5b84c2f04 | ||
![]() |
7e562a06bb | ||
![]() |
1eae19c3ea | ||
![]() |
ba33049b99 | ||
![]() |
9773087bf9 | ||
![]() |
b60d39ad0d | ||
![]() |
e89705a370 | ||
![]() |
4aea16d672 | ||
![]() |
afc69eae1a | ||
![]() |
2446a25b35 | ||
![]() |
d37524180d | ||
![]() |
b02a29fbb7 | ||
![]() |
e87d02230d | ||
![]() |
63609a0970 | ||
![]() |
1fe798ab19 | ||
![]() |
21b9977501 | ||
![]() |
fbe37be499 |
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
34
.github/workflows/build_latest.yml
vendored
Normal file
34
.github/workflows/build_latest.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Build_Latest
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
name: Checkout Code
|
||||
|
||||
- name: Setup MSBuild Path
|
||||
uses: warrenbuckley/Setup-MSBuild@v1
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
|
||||
- name: Setup NuGet
|
||||
uses: NuGet/setup-nuget@v1.0.2
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
|
||||
|
||||
- name: Restore NuGet Packages
|
||||
run: nuget restore N_m3u8DL-CLI.sln
|
||||
|
||||
- name: Build
|
||||
run: msbuild N_m3u8DL-CLI.sln /p:Configuration=Release /p:DebugSymbols=false /p:DebugType=None
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v1.0.0
|
||||
with:
|
||||
name: N_m3u8DL-CLI_latest
|
||||
path: N_m3u8DL-CLI\bin\Release\N_m3u8DL-CLI.exe
|
261
.gitignore
vendored
Normal file
261
.gitignore
vendored
Normal file
File diff suppressed because it is too large
Load Diff
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 nilaoda
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
25
N_m3u8DL-CLI.sln
Normal file
25
N_m3u8DL-CLI.sln
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29215.179
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "N_m3u8DL-CLI", "N_m3u8DL-CLI\N_m3u8DL-CLI.csproj", "{4FB61439-B738-46AC-B3AF-2BF72150D057}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4FB61439-B738-46AC-B3AF-2BF72150D057}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4FB61439-B738-46AC-B3AF-2BF72150D057}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4FB61439-B738-46AC-B3AF-2BF72150D057}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4FB61439-B738-46AC-B3AF-2BF72150D057}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {82B9270D-B7B2-4591-BF8A-5B4EBCD0EA8A}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
6
N_m3u8DL-CLI/App.config
Normal file
6
N_m3u8DL-CLI/App.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/></startup>
|
||||
</configuration>
|
318
N_m3u8DL-CLI/Decode51CtoKey.cs
Normal file
318
N_m3u8DL-CLI/Decode51CtoKey.cs
Normal file
File diff suppressed because one or more lines are too long
83
N_m3u8DL-CLI/DecodeCdeledu.cs
Normal file
83
N_m3u8DL-CLI/DecodeCdeledu.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using NiL.JS.BaseLibrary;
|
||||
using NiL.JS.Core;
|
||||
using NiL.JS.Extensions;
|
||||
using System;
|
||||
using Array = System.Array;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
internal class DecodeCdeledu
|
||||
{
|
||||
private static string JS = @"
|
||||
var _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
|
||||
var removePaddingChars = function(input) {
|
||||
var lkey = _keyStr.indexOf(input.charAt(input.length - 1));
|
||||
if (lkey == 64) {
|
||||
return input.substring(0, input.length - 1);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
var base64Decode = function(input, arrayBuffer) {
|
||||
input = removePaddingChars(input);
|
||||
input = removePaddingChars(input);
|
||||
var bytes = parseInt((input.length / 4) * 3, 10);
|
||||
var uarray;
|
||||
var chr1, chr2, chr3;
|
||||
var enc1, enc2, enc3, enc4;
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
if (arrayBuffer) {
|
||||
uarray = new Uint8Array(arrayBuffer);
|
||||
} else {
|
||||
uarray = new Uint8Array(bytes);
|
||||
}
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
|
||||
for (i = 0; i < bytes; i += 3) {
|
||||
enc1 = _keyStr.indexOf(input.charAt(j++));
|
||||
enc2 = _keyStr.indexOf(input.charAt(j++));
|
||||
enc3 = _keyStr.indexOf(input.charAt(j++));
|
||||
enc4 = _keyStr.indexOf(input.charAt(j++));
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
uarray[i] = chr1;
|
||||
if (enc3 != 64)
|
||||
uarray[i + 1] = chr2;
|
||||
if (enc4 != 64)
|
||||
uarray[i + 2] = chr3;
|
||||
}
|
||||
return uarray;
|
||||
}
|
||||
|
||||
var uint8ArrayToString = function(uDataArr) {
|
||||
var arrStr = '';
|
||||
for (var i = 0; i < uDataArr.length; i++) {
|
||||
arrStr += String.fromCharCode(uDataArr[i]);
|
||||
}
|
||||
return arrStr;
|
||||
}
|
||||
|
||||
var decodeKey = function(dataKeyString) {
|
||||
var decodeArr = base64Decode(dataKeyString);
|
||||
var decodeArrString = uint8ArrayToString(decodeArr);
|
||||
return decodeArrString;
|
||||
if (decodeArrString.indexOf('|&|') > 0) {
|
||||
return decodeArrString;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
";
|
||||
//https://video.cdeledu.com/js/lib/cdel.hls.min-1.0.js?v=1.3
|
||||
public static string DecodeKey(string txt)
|
||||
{
|
||||
var context = new Context();
|
||||
context.Eval(JS);
|
||||
var concatFunction = context.GetVariable("decodeKey").As<Function>();
|
||||
string key = concatFunction.Call(new Arguments { txt }).ToString();
|
||||
string realKey = key.Split(new string[] { "|&|" }, StringSplitOptions.None)[1];
|
||||
return realKey;
|
||||
}
|
||||
}
|
||||
}
|
41
N_m3u8DL-CLI/DecodeDdyun.cs
Normal file
41
N_m3u8DL-CLI/DecodeDdyun.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class DecodeDdyun
|
||||
{
|
||||
public static string DecryptM3u8(byte[] byteArray)
|
||||
{
|
||||
string tmp = DecodeNfmovies.DecryptM3u8(byteArray);
|
||||
if (tmp.StartsWith("duoduo.key"))
|
||||
{
|
||||
tmp = Regex.Replace(tmp, @"#EXT-X-BYTERANGE:.*\s", "");
|
||||
tmp = tmp.Replace("https:", "jump/https:")
|
||||
.Replace("inews.gtimg.com", "puui.qpic.cn");
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
//https://player.ddyunp.com/jQuery.min.js?v1.5
|
||||
public static string GetVaildM3u8Url(string url)
|
||||
{
|
||||
//url: https://hls.ddyunp.com/ddyun/id/1/key/playlist.m3u8
|
||||
string id = Regex.Match(url, @"\w{20,}").Value;
|
||||
string tm = Global.GetTimeStamp(false);
|
||||
string t = ((long.Parse(tm) / 0x186a0) * 0x64).ToString();
|
||||
string tmp = id + "duoduo" + "1" + t;
|
||||
MD5 md5 = MD5.Create();
|
||||
byte[] bs = Encoding.UTF8.GetBytes(tmp);
|
||||
byte[] hs = md5.ComputeHash(bs);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
foreach (byte b in hs)
|
||||
{
|
||||
sb.Append(b.ToString("x2"));
|
||||
}
|
||||
string key = sb.ToString();
|
||||
return Regex.Replace(url, @"1/\w{20,}", "1/" + key);
|
||||
}
|
||||
}
|
||||
}
|
53
N_m3u8DL-CLI/DecodeHuke88Key.cs
Normal file
53
N_m3u8DL-CLI/DecodeHuke88Key.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
//https://js.huke88.com/assets/revision/js/plugins/tcplayer/tcplayer.v4.1.min.js?v=930
|
||||
//https://js.huke88.com/assets/revision/js/plugins/tcplayer/libs/hls.min.0.13.2m.js?v=930
|
||||
class DecodeHuke88Key
|
||||
{
|
||||
private static string[] GetOverlayInfo(string url)
|
||||
{
|
||||
var enc = new Regex("eyJ\\w{100,}").Match(url).Value;
|
||||
var json = Encoding.UTF8.GetString(Convert.FromBase64String(enc));
|
||||
JObject jObject = JObject.Parse(json);
|
||||
var key = jObject["overlayKey"].ToString();
|
||||
var iv = jObject["overlayIv"].ToString();
|
||||
return new string[] { key, iv };
|
||||
}
|
||||
|
||||
public static string DecodeKey(string url, byte[] data)
|
||||
{
|
||||
var info = GetOverlayInfo(url);
|
||||
var overlayKey = info[0];
|
||||
var overlayIv = info[1];
|
||||
var l = new List<byte>();
|
||||
var c = new List<byte>();
|
||||
for (int h = 0; h < 16; h++)
|
||||
{
|
||||
var f = overlayKey.Substring(2 * h, 2);
|
||||
var g = overlayIv.Substring(2 * h, 2);
|
||||
l.Add(Convert.ToByte(f, 16));
|
||||
c.Add(Convert.ToByte(g, 16));
|
||||
}
|
||||
|
||||
var _lastCipherblock = c.ToArray();
|
||||
|
||||
var t = new byte[data.Length];
|
||||
var r = data;
|
||||
r = Decrypter.AES128Decrypt(data, l.ToArray(), Decrypter.HexStringToBytes("00000000000000000000000000000000"), CipherMode.CBC, PaddingMode.Zeros);
|
||||
|
||||
for (var o = 0; o < 16; o++)
|
||||
t[o] = (byte)(r[o] ^ _lastCipherblock[o]);
|
||||
|
||||
var key = Convert.ToBase64String(t);
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
203
N_m3u8DL-CLI/DecodeImooc.cs
Normal file
203
N_m3u8DL-CLI/DecodeImooc.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
using NiL.JS.BaseLibrary;
|
||||
using NiL.JS.Core;
|
||||
using NiL.JS.Extensions;
|
||||
using System;
|
||||
using Array = System.Array;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
/*
|
||||
* js代码来自:https://www.imooc.com/static/moco/player/3.0.6.3/mocoplayer.js?v=202006122046
|
||||
*
|
||||
*/
|
||||
class DecodeImooc
|
||||
{
|
||||
private static string JS = @"
|
||||
function n(t, e) {
|
||||
function r(t, e) {
|
||||
var r = '';
|
||||
if ('object' == typeof t)
|
||||
for (var n = 0; n < t.length; n++)
|
||||
r += String.fromCharCode(t[n]);
|
||||
t = r || t;
|
||||
for (var i, o, a = new Uint8Array(t.length), s = e.length, n = 0; n < t.length; n++)
|
||||
o = n % s,
|
||||
i = t[n],
|
||||
i = i.toString().charCodeAt(0),
|
||||
a[n] = i ^ e.charCodeAt(o);
|
||||
return a
|
||||
}
|
||||
function n(t) {
|
||||
var e = '';
|
||||
if ('object' == typeof t)
|
||||
for (var r = 0; r < t.length; r++)
|
||||
e += String.fromCharCode(t[r]);
|
||||
t = e || t;
|
||||
var n = new Uint8Array(t.length);
|
||||
for (r = 0; r < t.length; r++)
|
||||
n[r] = t[r].toString().charCodeAt(0);
|
||||
var i, o, r = 0;
|
||||
for (r = 0; r < n.length; r++)
|
||||
0 != (i = n[r] % 3) && r + i < n.length && (o = n[r + 1],
|
||||
n[r + 1] = n[r + i],
|
||||
n[r + i] = o,
|
||||
r = r + i + 1);
|
||||
return n
|
||||
}
|
||||
function i(t) {
|
||||
var e = '';
|
||||
if ('object' == typeof t)
|
||||
for (var r = 0; r < t.length; r++)
|
||||
e += String.fromCharCode(t[r]);
|
||||
t = e || t;
|
||||
var n = new Uint8Array(t.length);
|
||||
for (r = 0; r < t.length; r++)
|
||||
n[r] = t[r].toString().charCodeAt(0);
|
||||
var r = 0
|
||||
, i = 0
|
||||
, o = 0
|
||||
, a = 0;
|
||||
for (r = 0; r < n.length; r++)
|
||||
o = n[r] % 2,
|
||||
o && r++,
|
||||
a++;
|
||||
var s = new Uint8Array(a);
|
||||
for (r = 0; r < n.length; r++)
|
||||
o = n[r] % 2,
|
||||
s[i++] = o ? n[r++] : n[r];
|
||||
return s
|
||||
}
|
||||
function o(t, e) {
|
||||
var r = 0
|
||||
, n = 0
|
||||
, i = 0
|
||||
, o = 0
|
||||
, a = '';
|
||||
if ('object' == typeof t)
|
||||
for (var r = 0; r < t.length; r++)
|
||||
a += String.fromCharCode(t[r]);
|
||||
t = a || t;
|
||||
var s = new Uint8Array(t.length);
|
||||
for (r = 0; r < t.length; r++)
|
||||
s[r] = t[r].toString().charCodeAt(0);
|
||||
for (r = 0; r < t.length; r++)
|
||||
if (0 != (o = s[r] % 5) && 1 != o && r + o < s.length && (i = s[r + 1],
|
||||
n = r + 2,
|
||||
s[r + 1] = s[r + o],
|
||||
s[o + r] = i,
|
||||
(r = r + o + 1) - 2 > n))
|
||||
for (; n < r - 2; n++)
|
||||
s[n] = s[n] ^ e.charCodeAt(n % e.length);
|
||||
for (r = 0; r < t.length; r++)
|
||||
s[r] = s[r] ^ e.charCodeAt(r % e.length);
|
||||
return s
|
||||
}
|
||||
for (var a = {
|
||||
data: {
|
||||
info: t
|
||||
}
|
||||
}, s = {
|
||||
q: r,
|
||||
h: n,
|
||||
m: i,
|
||||
k: o
|
||||
}, l = a.data.info, u = l.substring(l.length - 4).split(''), c = 0; c < u.length; c++)
|
||||
u[c] = u[c].toString().charCodeAt(0) % 4;
|
||||
u.reverse();
|
||||
for (var d = [], c = 0; c < u.length; c++)
|
||||
d.push(l.substring(u[c] + 1, u[c] + 2)),
|
||||
l = l.substring(0, u[c] + 1) + l.substring(u[c] + 2);
|
||||
a.data.encrypt_table = d,
|
||||
a.data.key_table = [];
|
||||
for (var c in a.data.encrypt_table)
|
||||
'q' != a.data.encrypt_table[c] && 'k' != a.data.encrypt_table[c] || (a.data.key_table.push(l.substring(l.length - 12)),
|
||||
l = l.substring(0, l.length - 12));
|
||||
a.data.key_table.reverse(),
|
||||
a.data.info = l;
|
||||
var f = new Array(-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1);
|
||||
a.data.info = function(t) {
|
||||
var e, r, n, i, o, a, s;
|
||||
for (a = t.length,
|
||||
o = 0,
|
||||
s = ''; o < a; ) {
|
||||
do {
|
||||
e = f[255 & t.charCodeAt(o++)]
|
||||
} while (o < a && -1 == e);if (-1 == e)
|
||||
break;
|
||||
do {
|
||||
r = f[255 & t.charCodeAt(o++)]
|
||||
} while (o < a && -1 == r);if (-1 == r)
|
||||
break;
|
||||
s += String.fromCharCode(e << 2 | (48 & r) >> 4);
|
||||
do {
|
||||
if (61 == (n = 255 & t.charCodeAt(o++)))
|
||||
return s;
|
||||
n = f[n]
|
||||
} while (o < a && -1 == n);if (-1 == n)
|
||||
break;
|
||||
s += String.fromCharCode((15 & r) << 4 | (60 & n) >> 2);
|
||||
do {
|
||||
if (61 == (i = 255 & t.charCodeAt(o++)))
|
||||
return s;
|
||||
i = f[i]
|
||||
} while (o < a && -1 == i);if (-1 == i)
|
||||
break;
|
||||
s += String.fromCharCode((3 & n) << 6 | i)
|
||||
}
|
||||
return s
|
||||
}(a.data.info);
|
||||
for (var c in a.data.encrypt_table) {
|
||||
var h = a.data.encrypt_table[c];
|
||||
if ('q' == h || 'k' == h) {
|
||||
var p = a.data.key_table.pop();
|
||||
a.data.info = s[a.data.encrypt_table[c]](a.data.info, p)
|
||||
} else
|
||||
a.data.info = s[a.data.encrypt_table[c]](a.data.info)
|
||||
}
|
||||
if (e)
|
||||
return a.data.info;
|
||||
var g = '';
|
||||
for (c = 0; c < a.data.info.length; c++)
|
||||
g += String.fromCharCode(a.data.info[c]);
|
||||
return g
|
||||
}
|
||||
function Uint8ArrayToString(fileData){
|
||||
var dataString = '';
|
||||
for (var i = 0; i < fileData.length; i++) {
|
||||
dataString += Number(fileData[i]) + ',';
|
||||
}
|
||||
return dataString;
|
||||
}
|
||||
function decodeKey(resp){
|
||||
var string = eval('('+resp+')');
|
||||
//return btoa(String.fromCharCode.apply(null, new Uint8Array(n(string.data.info, 1))));
|
||||
return Uint8ArrayToString(new Uint8Array(n(string.data.info, 1)));
|
||||
}
|
||||
function decodeM3u8(resp){
|
||||
var string = eval('('+resp+')');
|
||||
return n(string.data.info);
|
||||
}
|
||||
";
|
||||
|
||||
|
||||
public static string DecodeM3u8(string resp)
|
||||
{
|
||||
var context = new Context();
|
||||
context.Eval(JS);
|
||||
var concatFunction = context.GetVariable("decodeM3u8").As<Function>();
|
||||
string m3u8 = concatFunction.Call(new Arguments { resp }).ToString();
|
||||
return m3u8;
|
||||
}
|
||||
|
||||
public static string DecodeKey(string resp)
|
||||
{
|
||||
var context = new Context();
|
||||
context.Eval(JS);
|
||||
var concatFunction = context.GetVariable("decodeKey").As<Function>();
|
||||
string key = concatFunction.Call(new Arguments { resp }).ToString();
|
||||
byte[] v = Array.ConvertAll(key.Trim(',').Split(','), s => (byte)int.Parse(s));
|
||||
string realKey = Convert.ToBase64String(v);
|
||||
return realKey;
|
||||
}
|
||||
}
|
||||
}
|
36
N_m3u8DL-CLI/DecodeNfmovies.cs
Normal file
36
N_m3u8DL-CLI/DecodeNfmovies.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class DecodeNfmovies
|
||||
{
|
||||
//https://jx.nfmovies.com/hls.min.js
|
||||
public static string DecryptM3u8(byte[] byteArray)
|
||||
{
|
||||
var t = byteArray;
|
||||
var decrypt = "";
|
||||
if (137 == t[0] && 80 == t[1] && 130 == t[354] && 96 == t[353]) t = t.Skip(355).ToArray();
|
||||
else
|
||||
{
|
||||
if (137 != t[0] || 80 != t[1] || 130 != t[394] || 96 != t[393])
|
||||
{
|
||||
for (var i = 0; i < t.Length; i++) decrypt += Convert.ToChar(t[i]);
|
||||
return decrypt;
|
||||
}
|
||||
t = t.Skip(395).ToArray();
|
||||
}
|
||||
using (var zipStream =
|
||||
new System.IO.Compression.GZipStream(new MemoryStream(t), System.IO.Compression.CompressionMode.Decompress))
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(zipStream, Encoding.UTF8))
|
||||
{
|
||||
decrypt = sr.ReadToEnd();
|
||||
}
|
||||
}
|
||||
return decrypt;
|
||||
}
|
||||
}
|
||||
}
|
78
N_m3u8DL-CLI/Decrypter.cs
Normal file
78
N_m3u8DL-CLI/Decrypter.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class Decrypter
|
||||
{
|
||||
public static byte[] AES128Decrypt(string filePath, byte[] keyByte, byte[] ivByte, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
|
||||
{
|
||||
FileStream fs = new FileStream(filePath, FileMode.Open);
|
||||
//获取文件大小
|
||||
long size = fs.Length;
|
||||
byte[] inBuff = new byte[size];
|
||||
fs.Read(inBuff, 0, inBuff.Length);
|
||||
fs.Close();
|
||||
|
||||
Aes dcpt = Aes.Create();
|
||||
dcpt.BlockSize = 128;
|
||||
dcpt.KeySize = 128;
|
||||
dcpt.Key = keyByte;
|
||||
dcpt.IV = ivByte;
|
||||
dcpt.Mode = mode;
|
||||
dcpt.Padding = padding;
|
||||
|
||||
ICryptoTransform cTransform = dcpt.CreateDecryptor();
|
||||
Byte[] resultArray = cTransform.TransformFinalBlock(inBuff, 0, inBuff.Length);
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
public static byte[] AES128Decrypt(byte[] encryptedBuff, byte[] keyByte, byte[] ivByte, CipherMode mode = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7)
|
||||
{
|
||||
byte[] inBuff = encryptedBuff;
|
||||
|
||||
Aes dcpt = Aes.Create();
|
||||
dcpt.BlockSize = 128;
|
||||
dcpt.KeySize = 128;
|
||||
dcpt.Key = keyByte;
|
||||
dcpt.IV = ivByte;
|
||||
dcpt.Mode = mode;
|
||||
dcpt.Padding = padding;
|
||||
|
||||
ICryptoTransform cTransform = dcpt.CreateDecryptor();
|
||||
Byte[] resultArray = cTransform.TransformFinalBlock(inBuff, 0, inBuff.Length);
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
public static byte[] HexStringToBytes(string hexStr)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hexStr))
|
||||
{
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
if (hexStr.StartsWith("0x") || hexStr.StartsWith("0X"))
|
||||
{
|
||||
hexStr = hexStr.Remove(0, 2);
|
||||
}
|
||||
|
||||
int count = hexStr.Length;
|
||||
|
||||
if (count % 2 == 1)
|
||||
{
|
||||
throw new ArgumentException("Invalid length of bytes:" + count);
|
||||
}
|
||||
|
||||
int byteCount = count / 2;
|
||||
byte[] result = new byte[byteCount];
|
||||
for (int ii = 0; ii < byteCount; ++ii)
|
||||
{
|
||||
var tempBytes = Byte.Parse(hexStr.Substring(2 * ii, 2), System.Globalization.NumberStyles.HexNumber);
|
||||
result[ii] = tempBytes;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
668
N_m3u8DL-CLI/DownloadManager.cs
Normal file
668
N_m3u8DL-CLI/DownloadManager.cs
Normal file
File diff suppressed because it is too large
Load Diff
252
N_m3u8DL-CLI/Downloader.cs
Normal file
252
N_m3u8DL-CLI/Downloader.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class Downloader
|
||||
{
|
||||
private int timeOut = 0;
|
||||
private int retry = 5;
|
||||
private int count = 0;
|
||||
private int segIndex = 0;
|
||||
private double segDur = 0;
|
||||
private string fileUrl = string.Empty;
|
||||
private string savePath = string.Empty;
|
||||
private string headers = string.Empty;
|
||||
private string method = string.Empty;
|
||||
private string key = string.Empty;
|
||||
private string iv = string.Empty;
|
||||
private string liveFile = string.Empty;
|
||||
private long expectByte = -1;
|
||||
private long startByte = 0;
|
||||
private bool isLive = false;
|
||||
private bool isDone = false;
|
||||
private bool firstSeg = true;
|
||||
private FileStream liveStream = null;
|
||||
|
||||
public string FileUrl { get => fileUrl; set => fileUrl = value; }
|
||||
public string SavePath { get => savePath; set => savePath = value; }
|
||||
public string Headers { get => headers; set => headers = value; }
|
||||
public string Method { get => method; set => method = value; }
|
||||
public string Key { get => key; set => key = value; }
|
||||
public string Iv { get => iv; set => iv = value; }
|
||||
public bool IsLive { get => isLive; set => isLive = value; }
|
||||
public int Retry { get => retry; set => retry = value; }
|
||||
public bool IsDone { get => isDone; set => isDone = value; }
|
||||
public int SegIndex { get => segIndex; set => segIndex = value; }
|
||||
public int TimeOut { get => timeOut; set => timeOut = value; }
|
||||
public FileStream LiveStream { get => liveStream; set => liveStream = value; }
|
||||
public string LiveFile { get => liveFile; set => liveFile = value; }
|
||||
public long ExpectByte { get => expectByte; set => expectByte = value; }
|
||||
public long StartByte { get => startByte; set => startByte = value; }
|
||||
public double SegDur { get => segDur; set => segDur = value; }
|
||||
|
||||
//重写WebClinet
|
||||
//private class WebClient : System.Net.WebClient
|
||||
//{
|
||||
// protected override WebRequest GetWebRequest(Uri uri)
|
||||
// {
|
||||
// WebRequest lWebRequest = base.GetWebRequest(uri);
|
||||
// lWebRequest.Timeout = TimeOut;
|
||||
// ((HttpWebRequest)lWebRequest).ReadWriteTimeout = TimeOut;
|
||||
// return lWebRequest;
|
||||
// }
|
||||
//}
|
||||
|
||||
//WebClient client = new WebClient();
|
||||
|
||||
|
||||
public void Down()
|
||||
{
|
||||
try
|
||||
{
|
||||
//直播下载
|
||||
if (IsLive)
|
||||
{
|
||||
IsDone = false; //设置为未完成下载
|
||||
|
||||
if (Method == "NONE" || method.Contains("NOTSUPPORTED"))
|
||||
{
|
||||
LOGGER.PrintLine("<" + SegIndex + " Downloading>");
|
||||
LOGGER.WriteLine("<" + SegIndex + " Downloading>");
|
||||
byte[] segBuff = Global.HttpDownloadFileToBytes(fileUrl, Headers, TimeOut);
|
||||
//byte[] segBuff = Global.WebClientDownloadToBytes(fileUrl, Headers);
|
||||
Global.AppendBytesToFileStreamAndDoNotClose(LiveStream, segBuff);
|
||||
LOGGER.PrintLine("<" + SegIndex + " Complete>\r\n");
|
||||
LOGGER.WriteLine("<" + SegIndex + " Complete>");
|
||||
IsDone = true;
|
||||
}
|
||||
else if (Method == "AES-128")
|
||||
{
|
||||
LOGGER.PrintLine("<" + SegIndex + " Downloading>");
|
||||
LOGGER.WriteLine("<" + SegIndex + " Downloading>");
|
||||
byte[] encryptedBuff = Global.HttpDownloadFileToBytes(fileUrl, Headers, TimeOut);
|
||||
//byte[] encryptedBuff = Global.WebClientDownloadToBytes(fileUrl, Headers);
|
||||
byte[] decryptBuff = null;
|
||||
decryptBuff = Decrypter.AES128Decrypt(
|
||||
encryptedBuff,
|
||||
Convert.FromBase64String(Key),
|
||||
Decrypter.HexStringToBytes(Iv)
|
||||
);
|
||||
Global.AppendBytesToFileStreamAndDoNotClose(LiveStream, decryptBuff);
|
||||
LOGGER.PrintLine("<" + SegIndex + " Complete>\r\n");
|
||||
LOGGER.WriteLine("<" + SegIndex + " Complete>");
|
||||
IsDone = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//LOGGER.PrintLine("不支持这种加密方式!", LOGGER.Error);
|
||||
IsDone = true;
|
||||
}
|
||||
if (firstSeg && Global.FileSize(LiveFile) != 0)
|
||||
{
|
||||
//LOGGER.STOPLOG = false; //记录日志
|
||||
foreach (string ss in (string[])Global.GetVideoInfo(LiveFile).ToArray(typeof(string)))
|
||||
{
|
||||
LOGGER.WriteLine(ss.Trim());
|
||||
}
|
||||
firstSeg = false;
|
||||
//LOGGER.STOPLOG = true; //停止记录日志
|
||||
}
|
||||
HLSLiveDownloader.REC_DUR += SegDur;
|
||||
if (HLSLiveDownloader.REC_DUR_LIMIT != -1 && HLSLiveDownloader.REC_DUR >= HLSLiveDownloader.REC_DUR_LIMIT)
|
||||
{
|
||||
LOGGER.PrintLine(strings.recordLimitReached, LOGGER.Warning);
|
||||
LOGGER.WriteLine(strings.recordLimitReached);
|
||||
Environment.Exit(0); //正常退出
|
||||
}
|
||||
return;
|
||||
}
|
||||
//点播下载
|
||||
else
|
||||
{
|
||||
if (!Directory.Exists(Path.GetDirectoryName(SavePath)))
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(SavePath)); //新建文件夹
|
||||
//是否存在文件,存在则不下载
|
||||
if (File.Exists(Path.GetDirectoryName(savePath) + "\\" + Path.GetFileNameWithoutExtension(savePath) + ".ts"))
|
||||
{
|
||||
Global.BYTEDOWN++; //防止被速度监控程序杀死
|
||||
//Console.WriteLine("Exists " + Path.GetFileNameWithoutExtension(savePath) + ".ts");
|
||||
return;
|
||||
}
|
||||
//Console.WriteLine("开始下载 " + fileUrl);
|
||||
//本地文件
|
||||
if (fileUrl.StartsWith("file:"))
|
||||
{
|
||||
Uri t = new Uri(fileUrl);
|
||||
fileUrl = t.LocalPath;
|
||||
if (File.Exists(fileUrl))
|
||||
{
|
||||
if (ExpectByte == -1) //没有RANGE
|
||||
{
|
||||
FileInfo fi = new FileInfo(fileUrl);
|
||||
fi.CopyTo(savePath);
|
||||
Global.BYTEDOWN += fi.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
FileStream stream = new FileInfo(fileUrl).OpenRead();
|
||||
//seek文件
|
||||
stream.Seek(StartByte, SeekOrigin.Begin);
|
||||
Byte[] buffer = new Byte[ExpectByte];
|
||||
//从流中读取字节块并将该数据写入给定缓冲区buffer中
|
||||
stream.Read(buffer, 0, Convert.ToInt32(buffer.Length));
|
||||
stream.Close();
|
||||
//写出文件
|
||||
MemoryStream m = new MemoryStream(buffer);
|
||||
FileStream fs = new FileStream(savePath, FileMode.OpenOrCreate);
|
||||
m.WriteTo(fs);
|
||||
m.Close();
|
||||
fs.Close();
|
||||
m = null;
|
||||
fs = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//下载
|
||||
Global.HttpDownloadFile(fileUrl, savePath, TimeOut, Headers, StartByte, ExpectByte);
|
||||
}
|
||||
}
|
||||
if (File.Exists(savePath) && Global.ShouldStop == false)
|
||||
{
|
||||
FileInfo fi = new FileInfo(savePath);
|
||||
if (Method == "NONE" || method.Contains("NOTSUPPORTED"))
|
||||
{
|
||||
fi.MoveTo(Path.GetDirectoryName(savePath) + "\\" + Path.GetFileNameWithoutExtension(savePath) + ".ts");
|
||||
DownloadManager.DownloadedSize += fi.Length;
|
||||
//Console.WriteLine(Path.GetFileNameWithoutExtension(savePath) + " Completed.");
|
||||
}
|
||||
else if (File.Exists(fi.FullName)
|
||||
&& Method == "AES-128")
|
||||
{
|
||||
//解密
|
||||
try
|
||||
{
|
||||
byte[] decryptBuff = null;
|
||||
if(fileUrl.Contains(".51cto.com/")) //使用AES-128-ECB模式解密
|
||||
{
|
||||
decryptBuff = Decrypter.AES128Decrypt(
|
||||
fi.FullName,
|
||||
Convert.FromBase64String(Key),
|
||||
Decrypter.HexStringToBytes(Iv),
|
||||
System.Security.Cryptography.CipherMode.ECB
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
decryptBuff = Decrypter.AES128Decrypt(
|
||||
fi.FullName,
|
||||
Convert.FromBase64String(Key),
|
||||
Decrypter.HexStringToBytes(Iv)
|
||||
);
|
||||
}
|
||||
FileStream fs = new FileStream(Path.GetDirectoryName(savePath) + "\\" + Path.GetFileNameWithoutExtension(savePath) + ".ts", FileMode.Create);
|
||||
fs.Write(decryptBuff, 0, decryptBuff.Length);
|
||||
fs.Close();
|
||||
DownloadManager.DownloadedSize += fi.Length;
|
||||
fi.Delete();
|
||||
//Console.WriteLine(Path.GetFileNameWithoutExtension(savePath) + " Completed & Decrypted.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LOGGER.PrintLine(ex.Message, LOGGER.Error);
|
||||
LOGGER.WriteLineError(ex.Message);
|
||||
Thread.Sleep(3000);
|
||||
Environment.Exit(-1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.WriteLineError(strings.SomethingWasWrong);
|
||||
LOGGER.PrintLine(strings.SomethingWasWrong, LOGGER.Error);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LOGGER.WriteLineError(ex.Message);
|
||||
if (ex.Message.Contains("404") || ex.Message.Contains("400"))//(400) 错误的请求,片段过期会提示400错误
|
||||
{
|
||||
IsDone = true;
|
||||
return;
|
||||
}
|
||||
else if (IsLive && count++ < Retry)
|
||||
{
|
||||
Thread.Sleep(2000);//直播一般3-6秒一个片段
|
||||
Down();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
177
N_m3u8DL-CLI/FFmpeg.cs
Normal file
177
N_m3u8DL-CLI/FFmpeg.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class FFmpeg
|
||||
{
|
||||
public static string FFMPEG_PATH = "ffmpeg";
|
||||
public static string REC_TIME = ""; //录制日期
|
||||
|
||||
public static string OutPutPath { get; set; } = string.Empty;
|
||||
public static string ReportFile { get; set; } = string.Empty;
|
||||
public static bool UseAACFilter { get; set; } = false; //是否启用滤镜
|
||||
public static bool WriteDate { get; set; } = true; //是否写入录制日期
|
||||
|
||||
public static void Merge(string[] files, string muxFormat, bool fastStart,
|
||||
string poster = "", string audioName = "", string title = "",
|
||||
string copyright = "", string comment = "", string encodingTool = "")
|
||||
{
|
||||
string dateString = string.IsNullOrEmpty(REC_TIME) ? DateTime.Now.ToString("o") : REC_TIME;
|
||||
|
||||
//同名文件已存在的共存策略
|
||||
if (File.Exists($"{OutPutPath}.{muxFormat.ToLower()}"))
|
||||
{
|
||||
OutPutPath = Path.Combine(Path.GetDirectoryName(OutPutPath),
|
||||
Path.GetFileName(OutPutPath) + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
|
||||
}
|
||||
|
||||
string command = "-loglevel warning -i concat:\"";
|
||||
string data = string.Empty;
|
||||
string ddpAudio = string.Empty;
|
||||
string addPoster = "-map 1 -c:v:1 copy -disposition:v:1 attached_pic";
|
||||
ddpAudio = (File.Exists($"{Path.GetFileNameWithoutExtension(OutPutPath + ".mp4")}.txt") ? File.ReadAllText($"{Path.GetFileNameWithoutExtension(OutPutPath + ".mp4")}.txt") : "") ;
|
||||
if (!string.IsNullOrEmpty(ddpAudio)) UseAACFilter = false;
|
||||
|
||||
|
||||
foreach (string t in files)
|
||||
{
|
||||
command += Path.GetFileName(t) + "|";
|
||||
}
|
||||
|
||||
switch (muxFormat.ToUpper())
|
||||
{
|
||||
case ("MP4"):
|
||||
command += "\" " + (string.IsNullOrEmpty(poster) ? "" : "-i \"" + poster + "\"");
|
||||
command += " " + (string.IsNullOrEmpty(ddpAudio) ? "" : "-i \"" + ddpAudio + "\"");
|
||||
command +=
|
||||
$" -map 0:v? {(string.IsNullOrEmpty(ddpAudio) ? "-map 0:a?" : $"-map {(string.IsNullOrEmpty(poster) ? "1" : "2")}:a -map 0:a?")} -map 0:s? " + (string.IsNullOrEmpty(poster) ? "" : addPoster)
|
||||
+ (WriteDate ? " -metadata date=\"" + dateString + "\"" : "") +
|
||||
" -metadata encoding_tool=\"" + encodingTool + "\" -metadata title=\"" + title +
|
||||
"\" -metadata copyright=\"" + copyright + "\" -metadata comment=\"" + comment +
|
||||
$"\" -metadata:s:a:{(string.IsNullOrEmpty(ddpAudio) ? "0" : "1")} handler_name=\"" + audioName + $"\" -metadata:s:a:{(string.IsNullOrEmpty(ddpAudio) ? "0" : "1")} handler=\"" + audioName + "\" ";
|
||||
command += (string.IsNullOrEmpty(ddpAudio) ? "" : " -metadata:s:a:0 handler_name=\"DD+\" -metadata:s:a:0 handler=\"DD+\" ");
|
||||
if (fastStart)
|
||||
command += "-movflags +faststart";
|
||||
command += " -c copy -y " + (UseAACFilter ? "-bsf:a aac_adtstoasc" : "") + " \"" + OutPutPath + ".mp4\"";
|
||||
break;
|
||||
case ("MKV"):
|
||||
command += "\" -map 0 -c copy -y " + (UseAACFilter ? "-bsf:a aac_adtstoasc" : "") + " \"" + OutPutPath + ".mkv\"";
|
||||
break;
|
||||
case ("FLV"):
|
||||
command += "\" -map 0 -c copy -y " + (UseAACFilter ? "-bsf:a aac_adtstoasc" : "") + " \"" + OutPutPath + ".flv\"";
|
||||
break;
|
||||
case ("TS"):
|
||||
command += "\" -map 0 -c copy -y -f mpegts -bsf:v h264_mp4toannexb \"" + OutPutPath + ".ts\"";
|
||||
break;
|
||||
case ("VTT"):
|
||||
command += "\" -map 0 -y \"" + OutPutPath + ".srt\""; //Convert To Srt
|
||||
break;
|
||||
case ("EAC3"):
|
||||
command += "\" -map 0:a -c copy -y \"" + OutPutPath + ".eac3\"";
|
||||
break;
|
||||
case ("AAC"):
|
||||
command += "\" -map 0:a -c copy -y \"" + OutPutPath + ".m4a\"";
|
||||
break;
|
||||
case ("AC3"):
|
||||
command += "\" -map 0:a -c copy -y \"" + OutPutPath + ".ac3\"";
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
Run(FFMPEG_PATH, command, Path.GetDirectoryName(files[0]));
|
||||
LOGGER.WriteLine(strings.ffmpegDone);
|
||||
//Console.WriteLine(command);
|
||||
}
|
||||
|
||||
public static void ConvertToMPEGTS(string file)
|
||||
{
|
||||
if (Global.VIDEO_TYPE == "H264")
|
||||
{
|
||||
Run(FFMPEG_PATH,
|
||||
"-loglevel quiet -i \"" + file + "\" -map 0 -c copy -copy_unknown -f mpegts -bsf:v h264_mp4toannexb \""
|
||||
+ Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts\"",
|
||||
Path.GetDirectoryName(file));
|
||||
if (File.Exists(Path.GetDirectoryName(file) + "\\" + Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts"))
|
||||
{
|
||||
File.Delete(file);
|
||||
File.Move(Path.GetDirectoryName(file) + "\\" + Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts", file);
|
||||
}
|
||||
}
|
||||
else if (Global.VIDEO_TYPE == "H265")
|
||||
{
|
||||
Run(FFMPEG_PATH,
|
||||
"-loglevel quiet -i \"" + file + "\" -map 0 -c copy -copy_unknown -f mpegts -bsf:v hevc_mp4toannexb \""
|
||||
+ Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts\"",
|
||||
Path.GetDirectoryName(file));
|
||||
if (File.Exists(Path.GetDirectoryName(file) + "\\" + Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts"))
|
||||
{
|
||||
File.Delete(file);
|
||||
File.Move(Path.GetDirectoryName(file) + "\\" + Path.GetFileNameWithoutExtension(file) + "[MPEGTS].ts", file);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGGER.WriteLineError("Unkown Video Type");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Run(string path, string args, string workDir)
|
||||
{
|
||||
string nowDir = Directory.GetCurrentDirectory(); //当前工作路径
|
||||
Directory.SetCurrentDirectory(workDir);
|
||||
Process p = new Process();//建立外部调用线程
|
||||
p.StartInfo.FileName = path;//要调用外部程序的绝对路径
|
||||
Environment.SetEnvironmentVariable("FFREPORT", "file=" + ReportFile + ":level=32"); //兼容XP系统
|
||||
//p.StartInfo.Environment.Add("FFREPORT", "file=" + ReportFile + ":level=32");
|
||||
p.StartInfo.Arguments = args;//参数(这里就是FFMPEG的参数了)
|
||||
p.StartInfo.UseShellExecute = false;//不使用操作系统外壳程序启动线程(一定为FALSE,详细的请看MSDN)
|
||||
p.StartInfo.RedirectStandardError = true;//把外部程序错误输出写到StandardError流中(这个一定要注意,FFMPEG的所有输出信息,都为错误输出流,用StandardOutput是捕获不到任何消息的...这是我耗费了2个多月得出来的经验...mencoder就是用standardOutput来捕获的)
|
||||
p.StartInfo.CreateNoWindow = false;//不创建进程窗口
|
||||
p.ErrorDataReceived += new DataReceivedEventHandler(Output);//外部程序(这里是FFMPEG)输出流时候产生的事件,这里是把流的处理过程转移到下面的方法中,详细请查阅MSDN
|
||||
p.StartInfo.StandardErrorEncoding = Encoding.UTF8;
|
||||
p.Start();//启动线程
|
||||
p.BeginErrorReadLine();//开始异步读取
|
||||
p.WaitForExit();//阻塞等待进程结束
|
||||
p.Close();//关闭进程
|
||||
p.Dispose();//释放资源
|
||||
Environment.SetEnvironmentVariable("FFREPORT", null); //兼容XP系统
|
||||
Directory.SetCurrentDirectory(nowDir);
|
||||
}
|
||||
|
||||
private static void Output(object sendProcess, DataReceivedEventArgs output)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(output.Data))
|
||||
{
|
||||
LOGGER.PrintLine(output.Data, LOGGER.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckMPEGTS(string file)
|
||||
{
|
||||
//放行杜比视界或纯音频文件
|
||||
if (Global.VIDEO_TYPE == "DV" || Global.AUDIO_TYPE != "")
|
||||
return true;
|
||||
//如果是多分片,也认为不是MPEGTS
|
||||
if (DownloadManager.PartsCount > 1)
|
||||
return false;
|
||||
|
||||
using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
byte[] firstByte = new byte[1];
|
||||
fs.Read(firstByte, 0, 1);
|
||||
//第一字节的16进制字符串
|
||||
string _1_byte_str = Convert.ToString(firstByte[0], 16);
|
||||
//syncword不为47就不处理
|
||||
if (_1_byte_str != "47")
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
1284
N_m3u8DL-CLI/Global.cs
Normal file
1284
N_m3u8DL-CLI/Global.cs
Normal file
File diff suppressed because it is too large
Load Diff
154
N_m3u8DL-CLI/HLSLiveDownloader.cs
Normal file
154
N_m3u8DL-CLI/HLSLiveDownloader.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Timers;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class HLSLiveDownloader
|
||||
{
|
||||
public static int REC_DUR_LIMIT = -1; //默认不限制录制时长
|
||||
public static double REC_DUR = 0; //已录制时长
|
||||
private string liveFile = string.Empty;
|
||||
private string jsonFile = string.Empty;
|
||||
private string headers = string.Empty;
|
||||
private string downDir = string.Empty;
|
||||
private FileStream liveStream = null;
|
||||
private double targetduration = 10;
|
||||
private bool isFirstJson = true;
|
||||
|
||||
public double TotalDuration { get; set; }
|
||||
public string Headers { get => headers; set => headers = value; }
|
||||
public string DownDir { get => downDir; set => downDir = value; }
|
||||
public FileStream LiveStream { get => liveStream; set => liveStream = value; }
|
||||
public string LiveFile { get => liveFile; set => liveFile = value; }
|
||||
|
||||
ArrayList toDownList = new ArrayList(); //所有待下载的列表
|
||||
System.Timers.Timer timer = new System.Timers.Timer();
|
||||
Downloader sd = new Downloader(); //只有一个实例
|
||||
|
||||
public void TimerStart()
|
||||
{
|
||||
timer.Enabled = true;
|
||||
//timer.Interval = (targetduration - 2) * 1000; //执行间隔时间,单位为毫秒
|
||||
timer.Start();
|
||||
timer.Elapsed += new ElapsedEventHandler(UpdateList);
|
||||
UpdateList(timer, new EventArgs()); //立即执行一次
|
||||
Record();
|
||||
}
|
||||
|
||||
public void TimerStop()
|
||||
{
|
||||
timer.Stop();
|
||||
}
|
||||
|
||||
//更新列表
|
||||
private void UpdateList(object source, EventArgs e)
|
||||
{
|
||||
jsonFile = Path.Combine(DownDir, "meta.json");
|
||||
if (!File.Exists(jsonFile))
|
||||
{
|
||||
TimerStop();
|
||||
return;
|
||||
}
|
||||
string jsonContent = File.ReadAllText(jsonFile);
|
||||
JObject initJson = JObject.Parse(jsonContent);
|
||||
string m3u8Url = initJson["m3u8"].Value<string>();
|
||||
targetduration = initJson["m3u8Info"]["targetDuration"].Value<double>();
|
||||
TotalDuration = initJson["m3u8Info"]["totalDuration"].Value<double>();
|
||||
timer.Interval = (TotalDuration - targetduration) * 1000;//设置定时器运行间隔
|
||||
if (timer.Interval <= 1000) timer.Interval = 10000;
|
||||
JArray lastSegments = JArray.Parse(initJson["m3u8Info"]["segments"][0].ToString().Trim()); //上次的分段,用于比对新分段
|
||||
ArrayList tempList = new ArrayList(); //所有待下载的列表
|
||||
tempList.Clear();
|
||||
foreach (JObject seg in lastSegments)
|
||||
{
|
||||
tempList.Add(seg.ToString());
|
||||
}
|
||||
|
||||
if(isFirstJson)
|
||||
{
|
||||
toDownList = tempList;
|
||||
isFirstJson = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Parser parser = new Parser();
|
||||
parser.DownDir = Path.GetDirectoryName(jsonFile);
|
||||
parser.M3u8Url = m3u8Url;
|
||||
parser.LiveStream = true;
|
||||
parser.Parse(); //产生新的json文件
|
||||
|
||||
jsonContent = File.ReadAllText(jsonFile);
|
||||
initJson = JObject.Parse(jsonContent);
|
||||
JArray segments = JArray.Parse(initJson["m3u8Info"]["segments"][0].ToString()); //大分组
|
||||
foreach (JObject seg in segments)
|
||||
{
|
||||
if (!tempList.Contains(seg.ToString()))
|
||||
{
|
||||
toDownList.Add(seg.ToString()); //加入真正的待下载队列
|
||||
//Console.WriteLine(seg.ToString());
|
||||
}
|
||||
}
|
||||
if (toDownList.Count > 0)
|
||||
Record();
|
||||
}
|
||||
|
||||
//public void TryDownload()
|
||||
//{
|
||||
// Thread t = new Thread(Download);
|
||||
// while (toDownList.Count != 0)
|
||||
// {
|
||||
// t = new Thread(Download);
|
||||
// t.Start();
|
||||
// t.Join();
|
||||
// while (sd.IsDone != true) ; //忙等待
|
||||
// if (toDownList.Count > 0)
|
||||
// toDownList.RemoveAt(0); //下完删除一项
|
||||
// }
|
||||
// Console.WriteLine("Waiting...");
|
||||
//}
|
||||
|
||||
private void Record()
|
||||
{
|
||||
while (toDownList.Count > 0 && (sd.FileUrl != "" ? sd.IsDone : true))
|
||||
{
|
||||
JObject info = JObject.Parse(toDownList[0].ToString());
|
||||
int index = info["index"].Value<int>();
|
||||
sd.FileUrl = info["segUri"].Value<string>();
|
||||
sd.Method = info["method"].Value<string>();
|
||||
if (sd.Method != "NONE")
|
||||
{
|
||||
sd.Key = info["key"].Value<string>();
|
||||
sd.Iv = info["iv"].Value<string>();
|
||||
}
|
||||
sd.TimeOut = (int)timer.Interval - 1000;//超时时间不超过下次执行时间
|
||||
sd.SegIndex = index;
|
||||
sd.Headers = Headers;
|
||||
sd.SegDur = info["duration"].Value<double>();
|
||||
sd.IsLive = true; //标记为直播
|
||||
sd.LiveFile = LiveFile;
|
||||
sd.LiveStream = LiveStream;
|
||||
sd.Down(); //开始下载
|
||||
while (sd.IsDone != true) { Thread.Sleep(1); }; //忙等待 Thread.Sleep(1) 可防止cpu 100% 防止电脑风扇狂转
|
||||
if (toDownList.Count > 0)
|
||||
toDownList.RemoveAt(0); //下完删除一项
|
||||
}
|
||||
LOGGER.PrintLine("Waiting...", LOGGER.Warning);
|
||||
LOGGER.WriteLine("Waiting...");
|
||||
}
|
||||
|
||||
//检测是否有新分片
|
||||
private bool isNewSeg()
|
||||
{
|
||||
if (toDownList.Count > 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
38
N_m3u8DL-CLI/HLSTags.cs
Normal file
38
N_m3u8DL-CLI/HLSTags.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class HLSTags
|
||||
{
|
||||
public static string ext_m3u = "#EXTM3U";
|
||||
public static string ext_x_targetduration = "#EXT-X-TARGETDURATION";
|
||||
public static string ext_x_media_sequence = "#EXT-X-MEDIA-SEQUENCE";
|
||||
public static string ext_x_discontinuity_sequence = "#EXT-X-DISCONTINUITY-SEQUENCE";
|
||||
public static string ext_x_program_date_time = "#EXT-X-PROGRAM-DATE-TIME";
|
||||
public static string ext_x_media = "#EXT-X-MEDIA";
|
||||
public static string ext_x_playlist_type = "#EXT-X-PLAYLIST-TYPE";
|
||||
public static string ext_x_key = "#EXT-X-KEY";
|
||||
public static string ext_x_stream_inf = "#EXT-X-STREAM-INF";
|
||||
public static string ext_x_version = "#EXT-X-VERSION";
|
||||
public static string ext_x_allow_cache = "#EXT-X-ALLOW-CACHE";
|
||||
public static string ext_x_endlist = "#EXT-X-ENDLIST";
|
||||
public static string extinf = "#EXTINF";
|
||||
public static string ext_i_frames_only = "#EXT-X-I-FRAMES-ONLY";
|
||||
public static string ext_x_byterange = "#EXT-X-BYTERANGE";
|
||||
public static string ext_x_i_frame_stream_inf = "#EXT-X-I-FRAME-STREAM-INF";
|
||||
public static string ext_x_discontinuity = "#EXT-X-DISCONTINUITY";
|
||||
public static string ext_x_cue_out_start = "#EXT-X-CUE-OUT";
|
||||
public static string ext_x_cue_out = "#EXT-X-CUE-OUT-CONT";
|
||||
public static string ext_is_independent_segments = "#EXT-X-INDEPENDENT-SEGMENTS";
|
||||
public static string ext_x_scte35 = "#EXT-OATCLS-SCTE35";
|
||||
public static string ext_x_cue_start = "#EXT-X-CUE-OUT";
|
||||
public static string ext_x_cue_end = "#EXT-X-CUE-IN";
|
||||
public static string ext_x_cue_span = "#EXT-X-CUE-SPAN";
|
||||
public static string ext_x_map = "#EXT-X-MAP";
|
||||
public static string ext_x_start = "#EXT-X-START";
|
||||
}
|
||||
}
|
116
N_m3u8DL-CLI/IqJsonParser.cs
Normal file
116
N_m3u8DL-CLI/IqJsonParser.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class IqJsonParser
|
||||
{
|
||||
public static string Parse(string downDir, string json)
|
||||
{
|
||||
JObject jObject = JObject.Parse(json);
|
||||
var aClips = jObject["payload"]["wm_a"]["audio_track1"]["files"].Value<JArray>();
|
||||
var vClips = jObject["payload"]["wm_a"]["video_track1"]["files"].Value<JArray>();
|
||||
|
||||
var codecsList = new List<string>();
|
||||
|
||||
var audioPath = "";
|
||||
var videoPath = "";
|
||||
var audioInitPath = "";
|
||||
var videoInitPath = "";
|
||||
|
||||
if (aClips.Count > 0)
|
||||
{
|
||||
var init = jObject["payload"]["wm_a"]["audio_track1"]["codec_init"].Value<string>();
|
||||
byte[] bytes = Convert.FromBase64String(init);
|
||||
//输出init文件
|
||||
audioInitPath = Path.Combine(downDir, "iqAudioInit.mp4");
|
||||
File.WriteAllBytes(audioInitPath, bytes);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("#EXTM3U");
|
||||
sb.AppendLine("#EXT-X-VERSION:3");
|
||||
sb.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
|
||||
sb.AppendLine("#CREATED-BY:N_m3u8DL-CLI");
|
||||
sb.AppendLine($"#EXT-CODEC:{jObject["payload"]["wm_a"]["audio_track1"]["codec"].Value<string>()}");
|
||||
sb.AppendLine($"#EXT-KID:{jObject["payload"]["wm_a"]["audio_track1"]["key_id"].Value<string>()}");
|
||||
sb.AppendLine($"#EXT-X-MAP:URI=\"{new Uri(Path.Combine(downDir + "(Audio)", "iqAudioInit.mp4")).ToString()}\"");
|
||||
sb.AppendLine("#EXT-X-KEY:METHOD=PLZ-KEEP-RAW,URI=\"None\"");
|
||||
foreach (var a in aClips)
|
||||
{
|
||||
sb.AppendLine($"#EXTINF:{a["duration_second"].ToString()}");
|
||||
sb.AppendLine(a["file_name"].Value<string>());
|
||||
}
|
||||
sb.AppendLine("#EXT-X-ENDLIST");
|
||||
//输出m3u8文件
|
||||
var _path = Path.Combine(downDir, "iqAudio.m3u8");
|
||||
File.WriteAllText(_path, sb.ToString());
|
||||
audioPath = new Uri(_path).ToString();
|
||||
codecsList.Add(jObject["payload"]["wm_a"]["audio_track1"]["codec"].Value<string>());
|
||||
}
|
||||
|
||||
if (vClips.Count > 0)
|
||||
{
|
||||
var init = jObject["payload"]["wm_a"]["video_track1"]["codec_init"].Value<string>();
|
||||
byte[] bytes = Convert.FromBase64String(init);
|
||||
//输出init文件
|
||||
videoInitPath = Path.Combine(downDir, "iqVideoInit.mp4");
|
||||
File.WriteAllBytes(videoInitPath, bytes);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.AppendLine("#EXTM3U");
|
||||
sb.AppendLine("#EXT-X-VERSION:3");
|
||||
sb.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
|
||||
sb.AppendLine("#CREATED-BY:N_m3u8DL-CLI");
|
||||
sb.AppendLine($"#EXT-CODEC:{jObject["payload"]["wm_a"]["video_track1"]["codec"].Value<string>()}");
|
||||
sb.AppendLine($"#EXT-KID:{jObject["payload"]["wm_a"]["video_track1"]["key_id"].Value<string>()}");
|
||||
sb.AppendLine($"#EXT-X-MAP:URI=\"{new Uri(videoInitPath).ToString()}\"");
|
||||
sb.AppendLine("#EXT-X-KEY:METHOD=PLZ-KEEP-RAW,URI=\"None\"");
|
||||
foreach (var a in vClips)
|
||||
{
|
||||
var start = a["seekable"]["pos_start"].Value<long>();
|
||||
var size = a["size"].Value<long>();
|
||||
sb.AppendLine($"#EXTINF:{a["duration_second"].ToString()}");
|
||||
sb.AppendLine($"#EXT-X-BYTERANGE:{size}@{start}");
|
||||
sb.AppendLine(a["file_name"].Value<string>());
|
||||
}
|
||||
sb.AppendLine("#EXT-X-ENDLIST");
|
||||
//输出m3u8文件
|
||||
var _path = Path.Combine(downDir, "iqVideo.m3u8");
|
||||
File.WriteAllText(_path, sb.ToString());
|
||||
videoPath = new Uri(_path).ToString();
|
||||
codecsList.Add(jObject["payload"]["wm_a"]["video_track1"]["codec"].Value<string>());
|
||||
}
|
||||
|
||||
var content = "";
|
||||
if ((videoPath == "" && audioPath != "") || Global.VIDEO_TYPE == "IGNORE")
|
||||
{
|
||||
return audioPath;
|
||||
}
|
||||
else if (audioPath == "" && videoPath != "")
|
||||
{
|
||||
return videoPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Directory.Exists(downDir + "(Audio)"))
|
||||
Directory.CreateDirectory(downDir + "(Audio)");
|
||||
var _path = Path.Combine(downDir + "(Audio)", "iqAudio.m3u8");
|
||||
var _pathInit = Path.Combine(downDir + "(Audio)", "iqAudioInit.mp4");
|
||||
File.Copy(new Uri(audioPath).LocalPath, _path, true);
|
||||
File.Copy(new Uri(audioInitPath).LocalPath, _pathInit, true);
|
||||
audioPath = new Uri(_path).ToString();
|
||||
content = $"#EXTM3U\r\n" +
|
||||
$"#EXT-X-MEDIA:TYPE=AUDIO,URI=\"{audioPath}\",GROUP-ID=\"default-audio-group\",NAME=\"stream_0\",AUTOSELECT=YES,CHANNELS=\"0\"\r\n" +
|
||||
$"#EXT-X-STREAM-INF:BANDWIDTH=99999,CODECS=\"{string.Join(",", codecsList)}\",RESOLUTION=0x0,AUDIO=\"default-audio-group\"\r\n" +
|
||||
$"{videoPath}";
|
||||
}
|
||||
|
||||
var _masterPath = Path.Combine(downDir, "master.m3u8");
|
||||
File.WriteAllText(_masterPath, content);
|
||||
return new Uri(_masterPath).ToString();
|
||||
}
|
||||
}
|
||||
}
|
163
N_m3u8DL-CLI/LOGGER.cs
Normal file
163
N_m3u8DL-CLI/LOGGER.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class LOGGER
|
||||
{
|
||||
public const int Default = 1;
|
||||
public const int Error = 2;
|
||||
public const int Warning = 3;
|
||||
|
||||
public static string LOGFILE;
|
||||
public static bool STOPLOG = false;
|
||||
public static string FindLog(string dir)
|
||||
{
|
||||
DirectoryInfo d = new DirectoryInfo(dir);
|
||||
foreach (FileInfo fi in d.GetFiles())
|
||||
{
|
||||
if (fi.Extension.ToUpper() == ".LOG")
|
||||
{
|
||||
return fi.FullName;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static void InitLog()
|
||||
{
|
||||
if (!Directory.Exists(Path.GetDirectoryName(LOGFILE)))//若文件夹不存在则新建文件夹
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(LOGFILE)); //新建文件夹
|
||||
//若文件存在则加序号
|
||||
int index = 1;
|
||||
var fileName = Path.GetFileNameWithoutExtension(LOGFILE);
|
||||
while (File.Exists(LOGFILE))
|
||||
{
|
||||
LOGFILE = Path.Combine(Path.GetDirectoryName(LOGFILE), $"{fileName}-{index++}.log");
|
||||
}
|
||||
string file = LOGFILE;
|
||||
string now = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
string init = "LOG " + DateTime.Now.ToString("yyyy/MM/dd") + "\r\n"
|
||||
+ "Save Path: " + Path.GetDirectoryName(LOGFILE) + "\r\n"
|
||||
+ "Task Start: " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + "\r\n"
|
||||
+ "Task CommandLine: " + Environment.CommandLine;
|
||||
|
||||
if (File.Exists(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "N_m3u8DL-CLI.args.txt")))
|
||||
{
|
||||
init += "\r\nAdditional Args: " + File.ReadAllText(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "N_m3u8DL-CLI.args.txt")); //解析命令行
|
||||
}
|
||||
|
||||
init += "\r\n\r\n";
|
||||
File.WriteAllText(file, init, Encoding.UTF8);
|
||||
}
|
||||
|
||||
//读写锁机制,当资源被占用,其他线程等待
|
||||
static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
|
||||
|
||||
public static void PrintLine(string text, int printLevel = 1)
|
||||
{
|
||||
int windowWith = 63;
|
||||
try
|
||||
{
|
||||
windowWith = Console.WindowWidth;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// empty
|
||||
}
|
||||
switch (printLevel)
|
||||
{
|
||||
case 0:
|
||||
Console.Write("\r" + new string(' ', windowWith - 1) + "\r");
|
||||
Console.WriteLine(" ".PadRight(12) + " " + text);
|
||||
break;
|
||||
case 1:
|
||||
Console.Write("\r" + new string(' ', windowWith - 1) + "\r");
|
||||
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff") + " ");
|
||||
Console.WriteLine(text);
|
||||
break;
|
||||
case 2:
|
||||
Console.Write("\r" + new string(' ', windowWith - 1) + "\r");
|
||||
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff") + " ");
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(text);
|
||||
Console.ResetColor();
|
||||
break;
|
||||
case 3:
|
||||
Console.Write("\r" + new string(' ', windowWith - 1) + "\r");
|
||||
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff") + " ");
|
||||
Console.ForegroundColor = ConsoleColor.DarkYellow;
|
||||
Console.WriteLine(text);
|
||||
Console.ResetColor();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteLine(string text)
|
||||
{
|
||||
if (STOPLOG)
|
||||
return;
|
||||
if (!File.Exists(LOGFILE))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
string file = LOGFILE;
|
||||
//进入写入
|
||||
LogWriteLock.EnterWriteLock();
|
||||
using (StreamWriter sw = File.AppendText(file))
|
||||
{
|
||||
sw.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " / (NORMAL) " + text, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
//释放占用
|
||||
LogWriteLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteLineError(string text)
|
||||
{
|
||||
if (!File.Exists(LOGFILE))
|
||||
return;
|
||||
try
|
||||
{
|
||||
string file = LOGFILE;
|
||||
//进入写入
|
||||
LogWriteLock.EnterWriteLock();
|
||||
using (StreamWriter sw = File.AppendText(file))
|
||||
{
|
||||
sw.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " / (ERROR) " + text, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
//释放占用
|
||||
LogWriteLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Show(string text)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(DateTime.Now.ToString("o") + " " + text);
|
||||
while (Console.ForegroundColor == ConsoleColor.Red)
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
}
|
867
N_m3u8DL-CLI/MPDParser.cs
Normal file
867
N_m3u8DL-CLI/MPDParser.cs
Normal file
File diff suppressed because it is too large
Load Diff
101
N_m3u8DL-CLI/MyOptions.cs
Normal file
101
N_m3u8DL-CLI/MyOptions.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using CommandLine;
|
||||
using CommandLine.Text;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
internal class MyOptions
|
||||
{
|
||||
[Value(0, Hidden = true, MetaName = "Input Source", HelpText = "Help_input", ResourceType = typeof(strings))]
|
||||
public string Input { get; set; }
|
||||
|
||||
[Option("workDir", HelpText = "Help_workDir", ResourceType = typeof(strings))]
|
||||
public string WorkDir { get; set; }
|
||||
|
||||
[Option("saveName", HelpText = "Help_saveName", ResourceType = typeof(strings))]
|
||||
public string SaveName { get; set; } = "";
|
||||
|
||||
[Option("baseUrl", HelpText = "Help_baseUrl", ResourceType = typeof(strings))]
|
||||
public string BaseUrl { get; set; }
|
||||
|
||||
[Option("headers", HelpText = "Help_headers", ResourceType = typeof(strings))]
|
||||
public string Headers { get; set; } = "";
|
||||
|
||||
[Option("maxThreads", Default = 32U, HelpText = "Help_maxThreads", ResourceType = typeof(strings))]
|
||||
public uint MaxThreads { get; set; }
|
||||
|
||||
[Option("minThreads", Default = 16U, HelpText = "Help_minThreads", ResourceType = typeof(strings))]
|
||||
public uint MinThreads { get; set; }
|
||||
|
||||
[Option("retryCount", Default = 15U, HelpText = "Help_retryCount", ResourceType = typeof(strings))]
|
||||
public uint RetryCount { get; set; }
|
||||
|
||||
[Option("timeOut", Default = 10U, HelpText = "Help_timeOut", ResourceType = typeof(strings))]
|
||||
public uint TimeOut { get; set; }
|
||||
|
||||
[Option("muxSetJson", HelpText = "Help_muxSetJson", ResourceType = typeof(strings))]
|
||||
public string MuxSetJson { get; set; }
|
||||
|
||||
[Option("useKeyFile", HelpText = "Help_useKeyFile", ResourceType = typeof(strings))]
|
||||
public string UseKeyFile { get; set; }
|
||||
|
||||
[Option("useKeyBase64", HelpText = "Help_useKeyBase64", ResourceType = typeof(strings))]
|
||||
public string UseKeyBase64 { get; set; }
|
||||
|
||||
[Option("useKeyIV", HelpText = "Help_useKeyIV", ResourceType = typeof(strings))]
|
||||
public string UseKeyIV { get; set; }
|
||||
|
||||
[Option("downloadRange", HelpText = "Help_downloadRange", ResourceType = typeof(strings))]
|
||||
public string DownloadRange { get; set; }
|
||||
|
||||
[Option("liveRecDur", HelpText = "Help_liveRecDur", ResourceType = typeof(strings))]
|
||||
public string LiveRecDur { get; set; }
|
||||
|
||||
[Option("stopSpeed", HelpText = "Help_stopSpeed", ResourceType = typeof(strings))]
|
||||
public long StopSpeed { get; set; } = 0L;
|
||||
|
||||
[Option("maxSpeed", HelpText = "Help_maxSpeed", ResourceType = typeof(strings))]
|
||||
public long MaxSpeed { get; set; } = 0L;
|
||||
|
||||
[Option("proxyAddress", HelpText = "Help_proxyAddress", ResourceType = typeof(strings))]
|
||||
public string ProxyAddress { get; set; }
|
||||
|
||||
[Option("enableDelAfterDone", HelpText = "Help_enableDelAfterDone", ResourceType = typeof(strings))]
|
||||
public bool EnableDelAfterDone { get; set; }
|
||||
|
||||
[Option("enableMuxFastStart", HelpText = "Help_enableMuxFastStart", ResourceType = typeof(strings))]
|
||||
public bool EnableMuxFastStart { get; set; }
|
||||
|
||||
[Option("enableBinaryMerge", HelpText = "Help_enableBinaryMerge", ResourceType = typeof(strings))]
|
||||
public bool EnableBinaryMerge { get; set; }
|
||||
|
||||
[Option("enableParseOnly", HelpText = "Help_enableParseOnly", ResourceType = typeof(strings))]
|
||||
public bool EnableParseOnly { get; set; }
|
||||
|
||||
[Option("enableAudioOnly", HelpText = "Help_enableAudioOnly", ResourceType = typeof(strings))]
|
||||
public bool EnableAudioOnly { get; set; }
|
||||
|
||||
[Option("disableDateInfo", HelpText = "Help_disableDateInfo", ResourceType = typeof(strings))]
|
||||
public bool DisableDateInfo { get; set; }
|
||||
|
||||
[Option("disableIntegrityCheck", HelpText = "Help_disableIntegrityCheck", ResourceType = typeof(strings))]
|
||||
public bool DisableIntegrityCheck { get; set; }
|
||||
|
||||
[Option("noMerge", HelpText = "Help_noMerge", ResourceType = typeof(strings))]
|
||||
public bool NoMerge { get; set; }
|
||||
|
||||
[Option("noProxy", HelpText = "Help_noProxy", ResourceType = typeof(strings))]
|
||||
public bool NoProxy { get; set; }
|
||||
|
||||
[Option("registerUrlProtocol", HelpText = "Help_registerUrlProtocol", ResourceType = typeof(strings))]
|
||||
public bool RegisterUrlProtocol { get; set; }
|
||||
|
||||
[Option("unregisterUrlProtocol", HelpText = "Help_unregisterUrlProtocol", ResourceType = typeof(strings))]
|
||||
public bool UnregisterUrlProtocol { get; set; }
|
||||
|
||||
}
|
||||
}
|
178
N_m3u8DL-CLI/N_m3u8DL-CLI.csproj
Normal file
178
N_m3u8DL-CLI/N_m3u8DL-CLI.csproj
Normal file
@@ -0,0 +1,178 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props" Condition="Exists('..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props')" />
|
||||
<Import Project="..\packages\Resource.Embedder.2.1.1\build\Resource.Embedder.props" Condition="Exists('..\packages\Resource.Embedder.2.1.1\build\Resource.Embedder.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{4FB61439-B738-46AC-B3AF-2BF72150D057}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>N_m3u8DL_CLI</RootNamespace>
|
||||
<AssemblyName>N_m3u8DL-CLI</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>logo_3Iv_icon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="BrotliSharpLib, Version=0.3.2.0, Culture=neutral, PublicKeyToken=3f4e2a1cd615fcb7, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\BrotliSharpLib.0.3.3\lib\net451\BrotliSharpLib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="CommandLine, Version=2.8.0.0, Culture=neutral, PublicKeyToken=5a870481e358d379, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\CommandLineParser.2.8.0\lib\net45\CommandLine.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Costura, Version=4.1.0.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Costura.Fody.4.1.0\lib\net40\Costura.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Build.Framework" />
|
||||
<Reference Include="Microsoft.Build.Utilities.v4.0" />
|
||||
<Reference Include="Microsoft.JScript" />
|
||||
<Reference Include="Microsoft.Win32.TaskScheduler, Version=2.8.7.0, Culture=neutral, PublicKeyToken=c416bc1b32d97233, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\TaskScheduler.2.8.7\lib\net452\Microsoft.Win32.TaskScheduler.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="MihaZupan.HttpToSocks5Proxy, Version=1.4.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\HttpToSocks5Proxy.1.4.0\lib\net45\MihaZupan.HttpToSocks5Proxy.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NiL.JS, Version=2.5.1428.0, Culture=neutral, PublicKeyToken=fa941a7c2a4de689, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NiL.JS.2.5.1428\lib\net45\NiL.JS.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Collections" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.IO" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="UACHelper, Version=1.3.0.4, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\UACHelper.1.3.0.5\lib\net40\UACHelper.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Decode51CtoKey.cs" />
|
||||
<Compile Include="DecodeCdeledu.cs" />
|
||||
<Compile Include="DecodeDdyun.cs" />
|
||||
<Compile Include="DecodeHuke88Key.cs" />
|
||||
<Compile Include="DecodeImooc.cs" />
|
||||
<Compile Include="Decrypter.cs" />
|
||||
<Compile Include="DecodeNfmovies.cs" />
|
||||
<Compile Include="FFmpeg.cs" />
|
||||
<Compile Include="Global.cs" />
|
||||
<Compile Include="HLSLiveDownloader.cs" />
|
||||
<Compile Include="HLSTags.cs" />
|
||||
<Compile Include="IqJsonParser.cs" />
|
||||
<Compile Include="LOGGER.cs" />
|
||||
<Compile Include="DownloadManager.cs" />
|
||||
<Compile Include="MPDParser.cs" />
|
||||
<Compile Include="MyOptions.cs" />
|
||||
<Compile Include="Parser.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="ProgressReporter.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Downloader.cs" />
|
||||
<Compile Include="strings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>strings.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="strings.en-US.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>strings.en-US.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="strings.zh-TW.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>strings.zh-TW.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Watcher.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<COMReference Include="Scripting">
|
||||
<Guid>{420B2830-E718-11CF-893D-00A0C9054228}</Guid>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="logo_3Iv_icon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="strings.en-US.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>strings.en-US.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="strings.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>strings.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="strings.zh-TW.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>strings.zh-TW.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\Resource.Embedder.2.1.1\build\Resource.Embedder.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Resource.Embedder.2.1.1\build\Resource.Embedder.props'))" />
|
||||
<Error Condition="!Exists('..\packages\Fody.6.0.0\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.6.0.0\build\Fody.targets'))" />
|
||||
<Error Condition="!Exists('..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.4.1.0\build\Costura.Fody.props'))" />
|
||||
</Target>
|
||||
<Import Project="..\packages\Fody.6.0.0\build\Fody.targets" Condition="Exists('..\packages\Fody.6.0.0\build\Fody.targets')" />
|
||||
</Project>
|
1000
N_m3u8DL-CLI/Parser.cs
Normal file
1000
N_m3u8DL-CLI/Parser.cs
Normal file
File diff suppressed because it is too large
Load Diff
512
N_m3u8DL-CLI/Program.cs
Normal file
512
N_m3u8DL-CLI/Program.cs
Normal file
File diff suppressed because it is too large
Load Diff
39
N_m3u8DL-CLI/ProgressReporter.cs
Normal file
39
N_m3u8DL-CLI/ProgressReporter.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class ProgressReporter
|
||||
{
|
||||
private static string speed = "";
|
||||
private static string progress = "";
|
||||
|
||||
static object lockThis = new object();
|
||||
public static void Report(string progress, string speed)
|
||||
{
|
||||
lock (lockThis)
|
||||
{
|
||||
int windowWith = 63;
|
||||
try
|
||||
{
|
||||
windowWith = Console.WindowWidth;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// empty
|
||||
}
|
||||
if (!string.IsNullOrEmpty(progress)) ProgressReporter.progress = progress;
|
||||
if (!string.IsNullOrEmpty(speed)) ProgressReporter.speed = speed;
|
||||
string now = DateTime.Now.ToString("HH:mm:ss.000");
|
||||
var sub = windowWith - 4 - ProgressReporter.progress.Length - ProgressReporter.speed.Length - now.Length;
|
||||
if (sub <= 0) sub = 0;
|
||||
string print = now + " " + ProgressReporter.progress + " " + ProgressReporter.speed + new string(' ', sub);
|
||||
Console.Write("\r" + print + "\r");
|
||||
//Console.Write(print);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
N_m3u8DL-CLI/Properties/AssemblyInfo.cs
Normal file
36
N_m3u8DL-CLI/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// 有关程序集的一般信息由以下
|
||||
// 控制。更改这些特性值可修改
|
||||
// 与程序集关联的信息。
|
||||
[assembly: AssemblyTitle("N_m3u8DL-CLI")]
|
||||
[assembly: AssemblyDescription("一款命令行m3u8下载器")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("nilaoda")]
|
||||
[assembly: AssemblyProduct("N_m3u8DL-CLI")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2022")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// 将 ComVisible 设置为 false 会使此程序集中的类型
|
||||
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
|
||||
//请将此类型的 ComVisible 特性设置为 true。
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
|
||||
[assembly: Guid("4fb61439-b738-46ac-b3af-2bf72150d057")]
|
||||
|
||||
// 程序集的版本信息由下列四个值组成:
|
||||
//
|
||||
// 主版本
|
||||
// 次版本
|
||||
// 生成号
|
||||
// 修订号
|
||||
//
|
||||
// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
|
||||
// 方法是按如下所示使用“*”: :
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("3.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("3.0.0.0")]
|
110
N_m3u8DL-CLI/Watcher.cs
Normal file
110
N_m3u8DL-CLI/Watcher.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class Watcher
|
||||
{
|
||||
private string dir = string.Empty;
|
||||
private int total = 0;
|
||||
private static double totalDuration = 0; //总时长
|
||||
private int now = 0;
|
||||
private int partsCount = 0;
|
||||
FileSystemWatcher watcher = new FileSystemWatcher();
|
||||
|
||||
public int Total { get => total; set => total = value; }
|
||||
public int Now { get => now; set => now = value; }
|
||||
public int PartsCount { get => partsCount; set => partsCount = value; }
|
||||
public static double TotalDuration { get => totalDuration; set => totalDuration = value; }
|
||||
|
||||
public Watcher(string Dir)
|
||||
{
|
||||
this.dir = Dir;
|
||||
}
|
||||
|
||||
public void WatcherStrat()
|
||||
{
|
||||
for (int i = 0; i < PartsCount; i++)
|
||||
{
|
||||
Now += Global.GetFileCount(dir + "\\Part_" + i.ToString(DownloadManager.partsPadZero), ".ts");
|
||||
}
|
||||
watcher.Path = dir;
|
||||
watcher.Filter = "*.ts";
|
||||
watcher.IncludeSubdirectories = true; //包括子目录
|
||||
watcher.EnableRaisingEvents = true; //开启提交事件
|
||||
watcher.Created += new FileSystemEventHandler(OnCreated);
|
||||
watcher.Renamed += new RenamedEventHandler(OnCreated);
|
||||
watcher.Deleted += new FileSystemEventHandler(OnDeleted);
|
||||
}
|
||||
|
||||
public void WatcherStop()
|
||||
{
|
||||
watcher.Dispose();
|
||||
}
|
||||
|
||||
private void OnCreated(object source, FileSystemEventArgs e)
|
||||
{
|
||||
if (Path.GetFileNameWithoutExtension(e.FullPath).StartsWith("Part"))
|
||||
return;
|
||||
Now++;
|
||||
if (Now > Total)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//Console.Title = Now + " / " + Total;
|
||||
string downloadedSize = Global.FormatFileSize(DownloadManager.DownloadedSize);
|
||||
string estimatedSize = Global.FormatFileSize(DownloadManager.DownloadedSize * total / now);
|
||||
int padding = downloadedSize.Length > estimatedSize.Length ? downloadedSize.Length : estimatedSize.Length;
|
||||
DownloadManager.ToDoSize = (DownloadManager.DownloadedSize * total / now) - DownloadManager.DownloadedSize;
|
||||
string percent = (Convert.ToDouble(now) / Convert.ToDouble(total) * 100).ToString("0.00") + "%";
|
||||
var print = "Progress: " + Now + "/" + Total
|
||||
+ $" ({percent}) -- {downloadedSize.PadLeft(padding)}/{estimatedSize.PadRight(padding)}";
|
||||
ProgressReporter.Report(print, "");
|
||||
}
|
||||
|
||||
private void OnRenamed(object source, RenamedEventArgs e)
|
||||
{
|
||||
if (Path.GetFileNameWithoutExtension(e.FullPath).StartsWith("Part"))
|
||||
return;
|
||||
Now++;
|
||||
if (Now > Total)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//Console.Title = Now + " / " + Total;
|
||||
string downloadedSize = Global.FormatFileSize(DownloadManager.DownloadedSize);
|
||||
string estimatedSize = Global.FormatFileSize(DownloadManager.DownloadedSize * total / now);
|
||||
int padding = downloadedSize.Length > estimatedSize.Length ? downloadedSize.Length : estimatedSize.Length;
|
||||
DownloadManager.ToDoSize = (DownloadManager.DownloadedSize * total / now) - DownloadManager.DownloadedSize;
|
||||
string percent = (Convert.ToDouble(now) / Convert.ToDouble(total) * 100).ToString("0.00") + "%";
|
||||
var print = "Progress: " + Now + "/" + Total
|
||||
+ $" ({percent}) -- {downloadedSize.PadLeft(padding)}/{estimatedSize.PadRight(padding)}";
|
||||
ProgressReporter.Report(print, "");
|
||||
}
|
||||
|
||||
private void OnDeleted(object source, FileSystemEventArgs e)
|
||||
{
|
||||
if (Path.GetFileNameWithoutExtension(e.FullPath).StartsWith("Part"))
|
||||
return;
|
||||
Now--;
|
||||
if (Now > Total)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//Console.Title = Now + " / " + Total;
|
||||
string downloadedSize = Global.FormatFileSize(DownloadManager.DownloadedSize);
|
||||
string estimatedSize = Global.FormatFileSize(DownloadManager.DownloadedSize * total / now);
|
||||
int padding = downloadedSize.Length > estimatedSize.Length ? downloadedSize.Length : estimatedSize.Length;
|
||||
DownloadManager.ToDoSize = (DownloadManager.DownloadedSize * total / now) - DownloadManager.DownloadedSize;
|
||||
string percent = (Convert.ToDouble(now) / Convert.ToDouble(total) * 100).ToString("0.00") + "%";
|
||||
var print = "Progress: " + Now + "/" + Total
|
||||
+ $" ({percent}) -- {downloadedSize.PadLeft(padding)}/{estimatedSize.PadRight(padding)}";
|
||||
ProgressReporter.Report(print, "");
|
||||
}
|
||||
}
|
||||
}
|
362
N_m3u8DL-CLI/changelog.txt
Normal file
362
N_m3u8DL-CLI/changelog.txt
Normal file
File diff suppressed because it is too large
Load Diff
BIN
N_m3u8DL-CLI/logo_3Iv_icon.ico
Normal file
BIN
N_m3u8DL-CLI/logo_3Iv_icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
14
N_m3u8DL-CLI/packages.config
Normal file
14
N_m3u8DL-CLI/packages.config
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="BrotliSharpLib" version="0.3.3" targetFramework="net46" />
|
||||
<package id="CommandLineParser" version="2.8.0" targetFramework="net46" />
|
||||
<package id="Costura.Fody" version="4.1.0" targetFramework="net46" />
|
||||
<package id="Fody" version="6.0.0" targetFramework="net46" developmentDependency="true" />
|
||||
<package id="HttpToSocks5Proxy" version="1.4.0" targetFramework="net46" />
|
||||
<package id="Newtonsoft.Json" version="13.0.1" targetFramework="net46" />
|
||||
<package id="NiL.JS" version="2.5.1428" targetFramework="net46" />
|
||||
<package id="Resource.Embedder" version="2.1.1" targetFramework="net46" />
|
||||
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net46" />
|
||||
<package id="TaskScheduler" version="2.8.7" targetFramework="net46" />
|
||||
<package id="UACHelper" version="1.3.0.5" targetFramework="net46" />
|
||||
</packages>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user