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
192 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
6416dd642d | ||
![]() |
967f0582a6 | ||
![]() |
a5aeffc6ec | ||
![]() |
745cbd92ad | ||
![]() |
4a4412a262 | ||
![]() |
084989b3ce | ||
![]() |
640208d95c | ||
![]() |
ad148ba4c7 | ||
![]() |
7c961dbbbf | ||
![]() |
83df7b4301 | ||
![]() |
860282a463 | ||
![]() |
70582029dd | ||
![]() |
1f3002ef66 | ||
![]() |
07c758dd00 | ||
![]() |
38f2f5589f | ||
![]() |
c7b9414613 | ||
![]() |
84953fa16a | ||
![]() |
4cf88711d6 | ||
![]() |
b59940f719 | ||
![]() |
6a6a338868 | ||
![]() |
0d74380e1c | ||
![]() |
5a398bdb79 | ||
![]() |
c2953a3d43 | ||
![]() |
6f2ebfba2d | ||
![]() |
3c1b893919 | ||
![]() |
8b6f411af9 | ||
![]() |
6cfd74f3a0 | ||
![]() |
044068016b | ||
![]() |
6d21ef0a10 | ||
![]() |
6d1834ca9f | ||
![]() |
3e215359b4 | ||
![]() |
3d9610d3bb | ||
![]() |
377c575c72 | ||
![]() |
c02d737125 | ||
![]() |
c1fb395ee0 | ||
![]() |
3920ae6178 | ||
![]() |
f96ea497b7 | ||
![]() |
0013ec4bb5 | ||
![]() |
604c25a2be | ||
![]() |
a83afd8908 | ||
![]() |
206342c4cf | ||
![]() |
296739910e | ||
![]() |
5a62342145 | ||
![]() |
64df11fefb | ||
![]() |
5d82bc9135 | ||
![]() |
2a61b61205 | ||
![]() |
15568f917c | ||
![]() |
b196990972 | ||
![]() |
d4a94d185a | ||
![]() |
d865c05d9a |
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\
|
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>
|
80
N_m3u8DL-CLI/CommandLineArgument.cs
Normal file
80
N_m3u8DL-CLI/CommandLineArgument.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
/**
|
||||
* https://www.cnblogs.com/linxuanchen/p/c-sharp-command-line-argument-parser.html
|
||||
*/
|
||||
public class CommandLineArgument
|
||||
{
|
||||
List<CommandLineArgument> _arguments;
|
||||
|
||||
int _index;
|
||||
|
||||
string _argumentText;
|
||||
|
||||
public CommandLineArgument Next
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index < _arguments.Count - 1)
|
||||
{
|
||||
return _arguments[_index + 1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public CommandLineArgument Previous
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index > 0)
|
||||
{
|
||||
return _arguments[_index - 1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
internal CommandLineArgument(List<CommandLineArgument> args, int index, string argument)
|
||||
{
|
||||
_arguments = args;
|
||||
_index = index;
|
||||
_argumentText = argument;
|
||||
}
|
||||
|
||||
public CommandLineArgument Take()
|
||||
{
|
||||
return Next;
|
||||
}
|
||||
|
||||
public IEnumerable<CommandLineArgument> Take(int count)
|
||||
{
|
||||
var list = new List<CommandLineArgument>();
|
||||
var parent = this;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var next = parent.Next;
|
||||
if (next == null)
|
||||
break;
|
||||
|
||||
list.Add(next);
|
||||
|
||||
parent = next;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static implicit operator string(CommandLineArgument argument)
|
||||
{
|
||||
return argument._argumentText;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return _argumentText;
|
||||
}
|
||||
}
|
||||
}
|
39
N_m3u8DL-CLI/CommandLineArgumentParser.cs
Normal file
39
N_m3u8DL-CLI/CommandLineArgumentParser.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
|
||||
{
|
||||
public class CommandLineArgumentParser
|
||||
{
|
||||
|
||||
List<CommandLineArgument> _arguments;
|
||||
public static CommandLineArgumentParser Parse(string[] args)
|
||||
{
|
||||
return new CommandLineArgumentParser(args);
|
||||
}
|
||||
|
||||
public CommandLineArgumentParser(string[] args)
|
||||
{
|
||||
_arguments = new List<CommandLineArgument>();
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
_arguments.Add(new CommandLineArgument(_arguments, i, args[i]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public CommandLineArgument Get(string argumentName)
|
||||
{
|
||||
return _arguments.FirstOrDefault(p => p == argumentName);
|
||||
}
|
||||
|
||||
public bool Has(string argumentName)
|
||||
{
|
||||
return _arguments.Count(p => p == argumentName) > 0;
|
||||
}
|
||||
}
|
||||
}
|
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
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);
|
||||
}
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
||||
}
|
82
N_m3u8DL-CLI/Decrypter.cs
Normal file
82
N_m3u8DL-CLI/Decrypter.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_CLI
|
||||
{
|
||||
class Decrypter
|
||||
{
|
||||
public static byte[] AES128Decrypt(string filePath, byte[] keyByte, byte[] ivByte, CipherMode mode = CipherMode.CBC)
|
||||
{
|
||||
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 = PaddingMode.PKCS7;
|
||||
|
||||
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)
|
||||
{
|
||||
byte[] inBuff = encryptedBuff;
|
||||
|
||||
Aes dcpt = Aes.Create();
|
||||
dcpt.BlockSize = 128;
|
||||
dcpt.KeySize = 128;
|
||||
dcpt.Key = keyByte;
|
||||
dcpt.IV = ivByte;
|
||||
dcpt.Mode = mode;
|
||||
dcpt.Padding = PaddingMode.PKCS7;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
696
N_m3u8DL-CLI/DownloadManager.cs
Normal file
696
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;
|
||||
}
|
||||
}
|
||||
}
|
1141
N_m3u8DL-CLI/Global.cs
Normal file
1141
N_m3u8DL-CLI/Global.cs
Normal file
File diff suppressed because it is too large
Load Diff
153
N_m3u8DL-CLI/HLSLiveDownloader.cs
Normal file
153
N_m3u8DL-CLI/HLSLiveDownloader.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
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;//设置定时器运行间隔
|
||||
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";
|
||||
}
|
||||
}
|
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 static int CursorIndex = 5;
|
||||
public static int FFmpegCorsorIndex = 5;
|
||||
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)); //新建文件夹
|
||||
if (File.Exists(LOGFILE))//若文件存在则删除
|
||||
File.Delete(LOGFILE);
|
||||
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 cursorIndex = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CursorIndex > 1000)
|
||||
{
|
||||
Console.Clear();
|
||||
CursorIndex = 0;
|
||||
}
|
||||
if (cursorIndex == 0)
|
||||
Console.SetCursorPosition(0, CursorIndex++);
|
||||
else
|
||||
Console.SetCursorPosition(0, cursorIndex);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
;
|
||||
}
|
||||
switch (printLevel)
|
||||
{
|
||||
case 0:
|
||||
Console.WriteLine(" ".PadRight(12) + " " + text);
|
||||
break;
|
||||
case 1:
|
||||
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff") + " ");
|
||||
Console.WriteLine(text);
|
||||
break;
|
||||
case 2:
|
||||
Console.Write(DateTime.Now.ToString("HH:mm:ss.fff") + " ");
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(text);
|
||||
Console.ResetColor();
|
||||
break;
|
||||
case 3:
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
617
N_m3u8DL-CLI/MPDParser.cs
Normal file
617
N_m3u8DL-CLI/MPDParser.cs
Normal file
File diff suppressed because it is too large
Load Diff
133
N_m3u8DL-CLI/N_m3u8DL-CLI.csproj
Normal file
133
N_m3u8DL-CLI/N_m3u8DL-CLI.csproj
Normal file
@@ -0,0 +1,133 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<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 />
|
||||
</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="Microsoft.JScript" />
|
||||
<Reference Include="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
|
||||
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.12.0.3\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.IO" />
|
||||
<Reference Include="System.Web" />
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CommandLineArgument.cs" />
|
||||
<Compile Include="CommandLineArgumentParser.cs" />
|
||||
<Compile Include="Decode51CtoKey.cs" />
|
||||
<Compile Include="DecodeDdyun.cs" />
|
||||
<Compile Include="DecodeImooc.cs" />
|
||||
<Compile Include="DecodeNfmovies.cs" />
|
||||
<Compile Include="Decrypter.cs" />
|
||||
<Compile Include="FFmpeg.cs" />
|
||||
<Compile Include="Global.cs" />
|
||||
<Compile Include="HLSLiveDownloader.cs" />
|
||||
<Compile Include="HLSTags.cs" />
|
||||
<Compile Include="LOGGER.cs" />
|
||||
<Compile Include="DownloadManager.cs" />
|
||||
<Compile Include="MPDParser.cs" />
|
||||
<Compile Include="Parser.cs" />
|
||||
<Compile Include="Program.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>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>strings.en-US.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="strings.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>strings.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="strings.zh-TW.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>strings.zh-TW.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
876
N_m3u8DL-CLI/Parser.cs
Normal file
876
N_m3u8DL-CLI/Parser.cs
Normal file
File diff suppressed because it is too large
Load Diff
801
N_m3u8DL-CLI/Program.cs
Normal file
801
N_m3u8DL-CLI/Program.cs
Normal file
File diff suppressed because it is too large
Load Diff
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 © 2020")]
|
||||
[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("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("2.7.0.0")]
|
104
N_m3u8DL-CLI/Watcher.cs
Normal file
104
N_m3u8DL-CLI/Watcher.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
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);
|
||||
string percent = (Convert.ToDouble(now) / Convert.ToDouble(total) * 100).ToString("0.00") + "%";
|
||||
Console.SetCursorPosition(0, 2);
|
||||
Console.Write(("Progress: " + Now + " of " + Total
|
||||
+ $" ({percent}/{downloadedSize}/{estimatedSize}/{Global.FormatTime(Convert.ToInt32(TotalDuration))})").PadRight(62));
|
||||
}
|
||||
|
||||
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);
|
||||
string percent = (Convert.ToDouble(now) / Convert.ToDouble(total) * 100).ToString("0.00") + "%";
|
||||
Console.SetCursorPosition(0, 2);
|
||||
Console.Write(("Progress: " + Now + " of " + Total
|
||||
+ $" ({percent}/{downloadedSize}/{estimatedSize}/{Global.FormatTime(Convert.ToInt32(TotalDuration))})").PadRight(62));
|
||||
}
|
||||
|
||||
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);
|
||||
string percent = (Convert.ToDouble(now) / Convert.ToDouble(total) * 100).ToString("0.00") + "%";
|
||||
Console.SetCursorPosition(0, 2);
|
||||
Console.Write(("Progress: " + Now + " of " + Total
|
||||
+ $" ({percent}/{downloadedSize}/{estimatedSize}/{Global.FormatTime(Convert.ToInt32(TotalDuration))})").PadRight(62));
|
||||
}
|
||||
}
|
||||
}
|
287
N_m3u8DL-CLI/changelog.txt
Normal file
287
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 |
5
N_m3u8DL-CLI/packages.config
Normal file
5
N_m3u8DL-CLI/packages.config
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net46" />
|
||||
<package id="NiL.JS" version="2.5.1428" targetFramework="net46" />
|
||||
</packages>
|
541
N_m3u8DL-CLI/strings.Designer.cs
generated
Normal file
541
N_m3u8DL-CLI/strings.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
0
N_m3u8DL-CLI/strings.en-US.Designer.cs
generated
Normal file
0
N_m3u8DL-CLI/strings.en-US.Designer.cs
generated
Normal file
301
N_m3u8DL-CLI/strings.en-US.resx
Normal file
301
N_m3u8DL-CLI/strings.en-US.resx
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user