Showing preview only (987K chars total). Download the full file or copy to clipboard to get everything.
Repository: nilaoda/N_m3u8DL-CLI
Branch: master
Commit: 985f6e57c33d
Files: 65
Total size: 953.6 KB
Directory structure:
gitextract_idemaxt1/
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ └── build_latest.yml
├── .gitignore
├── LICENSE
├── N_m3u8DL-CLI/
│ ├── App.config
│ ├── CSChaCha20.cs
│ ├── Decode51CtoKey.cs
│ ├── DecodeCdeledu.cs
│ ├── DecodeDdyun.cs
│ ├── DecodeHuke88Key.cs
│ ├── DecodeImooc.cs
│ ├── DecodeNfmovies.cs
│ ├── Decrypter.cs
│ ├── DownloadManager.cs
│ ├── Downloader.cs
│ ├── FFmpeg.cs
│ ├── Global.cs
│ ├── HLSLiveDownloader.cs
│ ├── HLSTags.cs
│ ├── IqJsonParser.cs
│ ├── LOGGER.cs
│ ├── MPDParser.cs
│ ├── MyOptions.cs
│ ├── N_m3u8DL-CLI.csproj
│ ├── Parser.cs
│ ├── Program.cs
│ ├── ProgressReporter.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── Watcher.cs
│ ├── changelog.txt
│ ├── packages.config
│ ├── strings.Designer.cs
│ ├── strings.en-US.Designer.cs
│ ├── strings.en-US.resx
│ ├── strings.resx
│ ├── strings.zh-TW.Designer.cs
│ └── strings.zh-TW.resx
├── N_m3u8DL-CLI.sln
├── README.md
├── README_ENG.md
└── docs/
├── Advanced.html
├── GetM3u8.html
├── Introductory.html
├── M3U8URL2File.html
├── SimpleGUI.html
├── gitbook/
│ ├── fonts/
│ │ └── fontawesome/
│ │ └── FontAwesome.otf
│ ├── gitbook-plugin-donate/
│ │ ├── plugin.css
│ │ └── plugin.js
│ ├── gitbook-plugin-fontsettings/
│ │ ├── fontsettings.js
│ │ └── website.css
│ ├── gitbook-plugin-github/
│ │ └── plugin.js
│ ├── gitbook-plugin-github-buttons/
│ │ └── plugin.js
│ ├── gitbook-plugin-highlight/
│ │ ├── ebook.css
│ │ └── website.css
│ ├── gitbook-plugin-lunr/
│ │ └── search-lunr.js
│ ├── gitbook-plugin-search/
│ │ ├── search-engine.js
│ │ ├── search.css
│ │ └── search.js
│ ├── gitbook-plugin-sharing-plus/
│ │ └── buttons.js
│ ├── gitbook.js
│ ├── style.css
│ └── theme.js
├── index.html
└── search_index.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
###############################################################################
# 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
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: ['https://nilaoda.github.io/N_m3u8DL-CLI/source/images/alipay.png','https://www.buymeacoffee.com/nilaoda']
================================================
FILE: .github/workflows/build_latest.yml
================================================
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
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
================================================
FILE: LICENSE
================================================
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.
================================================
FILE: N_m3u8DL-CLI/App.config
================================================
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/></startup>
</configuration>
================================================
FILE: N_m3u8DL-CLI/CSChaCha20.cs
================================================
/*
* Copyright (c) 2015, 2018 Scott Bennett
* (c) 2018-2021 Kaarlo Räihä
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices; // For MethodImplOptions.AggressiveInlining
namespace CSChaCha20
{
/// <summary>
/// Class that can be used for ChaCha20 encryption / decryption
/// </summary>
public sealed class ChaCha20 : IDisposable
{
/// <summary>
/// Only allowed key lenght in bytes
/// </summary>
public const int allowedKeyLength = 32;
/// <summary>
/// Only allowed nonce lenght in bytes
/// </summary>
public const int allowedNonceLength = 12;
/// <summary>
/// How many bytes are processed per loop
/// </summary>
public const int processBytesAtTime = 64;
private const int stateLength = 16;
/// <summary>
/// The ChaCha20 state (aka "context")
/// </summary>
private readonly uint[] state = new uint[stateLength];
/// <summary>
/// Determines if the objects in this class have been disposed of. Set to true by the Dispose() method.
/// </summary>
private bool isDisposed = false;
/// <summary>
/// Set up a new ChaCha20 state. The lengths of the given parameters are checked before encryption happens.
/// </summary>
/// <remarks>
/// See <a href="https://tools.ietf.org/html/rfc7539#page-10">ChaCha20 Spec Section 2.4</a> for a detailed description of the inputs.
/// </remarks>
/// <param name="key">
/// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers
/// </param>
/// <param name="nonce">
/// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers
/// </param>
/// <param name="counter">
/// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer
/// </param>
public ChaCha20(byte[] key, byte[] nonce, uint counter)
{
this.KeySetup(key);
this.IvSetup(nonce, counter);
}
#if NET6_0_OR_GREATER
/// <summary>
/// Set up a new ChaCha20 state. The lengths of the given parameters are checked before encryption happens.
/// </summary>
/// <remarks>
/// See <a href="https://tools.ietf.org/html/rfc7539#page-10">ChaCha20 Spec Section 2.4</a> for a detailed description of the inputs.
/// </remarks>
/// <param name="key">A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers</param>
/// <param name="nonce">A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers</param>
/// <param name="counter">A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer</param>
public ChaCha20(ReadOnlySpan<byte> key, ReadOnlySpan<byte> nonce, uint counter)
{
this.KeySetup(key.ToArray());
this.IvSetup(nonce.ToArray(), counter);
}
#endif // NET6_0_OR_GREATER
/// <summary>
/// The ChaCha20 state (aka "context"). Read-Only.
/// </summary>
public uint[] State
{
get
{
return this.state;
}
}
// These are the same constants defined in the reference implementation.
// http://cr.yp.to/streamciphers/timings/estreambench/submissions/salsa20/chacha8/ref/chacha.c
private static readonly byte[] sigma = Encoding.ASCII.GetBytes("expand 32-byte k");
private static readonly byte[] tau = Encoding.ASCII.GetBytes("expand 16-byte k");
/// <summary>
/// Set up the ChaCha state with the given key. A 32-byte key is required and enforced.
/// </summary>
/// <param name="key">
/// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers
/// </param>
private void KeySetup(byte[] key)
{
if (key == null)
{
throw new ArgumentNullException("Key is null");
}
if (key.Length != allowedKeyLength)
{
throw new ArgumentException($"Key length must be {allowedKeyLength}. Actual: {key.Length}");
}
state[4] = Util.U8To32Little(key, 0);
state[5] = Util.U8To32Little(key, 4);
state[6] = Util.U8To32Little(key, 8);
state[7] = Util.U8To32Little(key, 12);
byte[] constants = (key.Length == allowedKeyLength) ? sigma : tau;
int keyIndex = key.Length - 16;
state[8] = Util.U8To32Little(key, keyIndex + 0);
state[9] = Util.U8To32Little(key, keyIndex + 4);
state[10] = Util.U8To32Little(key, keyIndex + 8);
state[11] = Util.U8To32Little(key, keyIndex + 12);
state[0] = Util.U8To32Little(constants, 0);
state[1] = Util.U8To32Little(constants, 4);
state[2] = Util.U8To32Little(constants, 8);
state[3] = Util.U8To32Little(constants, 12);
}
/// <summary>
/// Set up the ChaCha state with the given nonce (aka Initialization Vector or IV) and block counter. A 12-byte nonce and a 4-byte counter are required.
/// </summary>
/// <param name="nonce">
/// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers
/// </param>
/// <param name="counter">
/// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer
/// </param>
private void IvSetup(byte[] nonce, uint counter)
{
if (nonce == null)
{
// There has already been some state set up. Clear it before exiting.
Dispose();
throw new ArgumentNullException("Nonce is null");
}
if (nonce.Length != allowedNonceLength)
{
// There has already been some state set up. Clear it before exiting.
Dispose();
throw new ArgumentException($"Nonce length must be {allowedNonceLength}. Actual: {nonce.Length}");
}
state[12] = counter;
state[13] = Util.U8To32Little(nonce, 0);
state[14] = Util.U8To32Little(nonce, 4);
state[15] = Util.U8To32Little(nonce, 8);
}
#region Encryption methods
/// <summary>
/// Encrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer.
/// </summary>
/// <remarks>Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method</remarks>
/// <param name="output">Output byte array, must have enough bytes</param>
/// <param name="input">Input byte array</param>
/// <param name="numBytes">Number of bytes to encrypt</param>
public void EncryptBytes(byte[] output, byte[] input, int numBytes)
{
this.WorkBytes(output, input, numBytes);
}
/// <summary>
/// Encrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output)
/// </summary>
/// <param name="output">Output stream</param>
/// <param name="input">Input stream</param>
/// <param name="howManyBytesToProcessAtTime">How many bytes to read and write at time, default is 1024</param>
public void EncryptStream(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024)
{
this.WorkStreams(output, input, howManyBytesToProcessAtTime);
}
/// <summary>
/// Async encrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output)
/// </summary>
/// <param name="output">Output stream</param>
/// <param name="input">Input stream</param>
/// <param name="howManyBytesToProcessAtTime">How many bytes to read and write at time, default is 1024</param>
public async Task EncryptStreamAsync(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024)
{
await this.WorkStreamsAsync(output, input, howManyBytesToProcessAtTime);
}
/// <summary>
/// Encrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer.
/// </summary>
/// <remarks>Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method</remarks>
/// <param name="output">Output byte array, must have enough bytes</param>
/// <param name="input">Input byte array</param>
public void EncryptBytes(byte[] output, byte[] input)
{
this.WorkBytes(output, input, input.Length);
}
/// <summary>
/// Encrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method.
/// </summary>
/// <remarks>Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method</remarks>
/// <param name="input">Input byte array</param>
/// <param name="numBytes">Number of bytes to encrypt</param>
/// <returns>Byte array that contains encrypted bytes</returns>
public byte[] EncryptBytes(byte[] input, int numBytes)
{
byte[] returnArray = new byte[numBytes];
this.WorkBytes(returnArray, input, numBytes);
return returnArray;
}
/// <summary>
/// Encrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method.
/// </summary>
/// <remarks>Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method</remarks>
/// <param name="input">Input byte array</param>
/// <returns>Byte array that contains encrypted bytes</returns>
public byte[] EncryptBytes(byte[] input)
{
byte[] returnArray = new byte[input.Length];
this.WorkBytes(returnArray, input, input.Length);
return returnArray;
}
/// <summary>
/// Encrypt string as UTF8 byte array, returns byte array that is allocated by method.
/// </summary>
/// <remarks>Here you can NOT swap encrypt and decrypt methods, because of bytes-string transform</remarks>
/// <param name="input">Input string</param>
/// <returns>Byte array that contains encrypted bytes</returns>
public byte[] EncryptString(string input)
{
byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(input);
byte[] returnArray = new byte[utf8Bytes.Length];
this.WorkBytes(returnArray, utf8Bytes, utf8Bytes.Length);
return returnArray;
}
#endregion // Encryption methods
#region // Decryption methods
/// <summary>
/// Decrypt arbitrary-length byte array (input), writing the resulting byte array to the output buffer.
/// </summary>
/// <remarks>Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method</remarks>
/// <param name="output">Output byte array</param>
/// <param name="input">Input byte array</param>
/// <param name="numBytes">Number of bytes to decrypt</param>
public void DecryptBytes(byte[] output, byte[] input, int numBytes)
{
this.WorkBytes(output, input, numBytes);
}
/// <summary>
/// Decrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output)
/// </summary>
/// <param name="output">Output stream</param>
/// <param name="input">Input stream</param>
/// <param name="howManyBytesToProcessAtTime">How many bytes to read and write at time, default is 1024</param>
public void DecryptStream(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024)
{
this.WorkStreams(output, input, howManyBytesToProcessAtTime);
}
/// <summary>
/// Async decrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output)
/// </summary>
/// <param name="output">Output stream</param>
/// <param name="input">Input stream</param>
/// <param name="howManyBytesToProcessAtTime">How many bytes to read and write at time, default is 1024</param>
public async Task DecryptStreamAsync(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024)
{
await this.WorkStreamsAsync(output, input, howManyBytesToProcessAtTime);
}
/// <summary>
/// Decrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer.
/// </summary>
/// <remarks>Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method</remarks>
/// <param name="output">Output byte array, must have enough bytes</param>
/// <param name="input">Input byte array</param>
public void DecryptBytes(byte[] output, byte[] input)
{
WorkBytes(output, input, input.Length);
}
/// <summary>
/// Decrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method.
/// </summary>
/// <remarks>Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method</remarks>
/// <param name="input">Input byte array</param>
/// <param name="numBytes">Number of bytes to encrypt</param>
/// <returns>Byte array that contains decrypted bytes</returns>
public byte[] DecryptBytes(byte[] input, int numBytes)
{
byte[] returnArray = new byte[numBytes];
WorkBytes(returnArray, input, numBytes);
return returnArray;
}
/// <summary>
/// Decrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method.
/// </summary>
/// <remarks>Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method</remarks>
/// <param name="input">Input byte array</param>
/// <returns>Byte array that contains decrypted bytes</returns>
public byte[] DecryptBytes(byte[] input)
{
byte[] returnArray = new byte[input.Length];
WorkBytes(returnArray, input, input.Length);
return returnArray;
}
/// <summary>
/// Decrypt UTF8 byte array to string.
/// </summary>
/// <remarks>Here you can NOT swap encrypt and decrypt methods, because of bytes-string transform</remarks>
/// <param name="input">Byte array</param>
/// <returns>Byte array that contains encrypted bytes</returns>
public string DecryptUTF8ByteArray(byte[] input)
{
byte[] tempArray = new byte[input.Length];
WorkBytes(tempArray, input, input.Length);
return System.Text.Encoding.UTF8.GetString(tempArray);
}
#endregion // Decryption methods
private void WorkStreams(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024)
{
int readBytes;
byte[] inputBuffer = new byte[howManyBytesToProcessAtTime];
byte[] outputBuffer = new byte[howManyBytesToProcessAtTime];
while ((readBytes = input.Read(inputBuffer, 0, howManyBytesToProcessAtTime)) > 0)
{
// Encrypt or decrypt
WorkBytes(output: outputBuffer, input: inputBuffer, numBytes: readBytes);
// Write buffer
output.Write(outputBuffer, 0, readBytes);
}
}
private async Task WorkStreamsAsync(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024)
{
byte[] readBytesBuffer = new byte[howManyBytesToProcessAtTime];
byte[] writeBytesBuffer = new byte[howManyBytesToProcessAtTime];
int howManyBytesWereRead = await input.ReadAsync(readBytesBuffer, 0, howManyBytesToProcessAtTime);
while (howManyBytesWereRead > 0)
{
// Encrypt or decrypt
WorkBytes(output: writeBytesBuffer, input: readBytesBuffer, numBytes: howManyBytesWereRead);
// Write
await output.WriteAsync(writeBytesBuffer, 0, howManyBytesWereRead);
// Read more
howManyBytesWereRead = await input.ReadAsync(readBytesBuffer, 0, howManyBytesToProcessAtTime);
}
}
/// <summary>
/// Encrypt or decrypt an arbitrary-length byte array (input), writing the resulting byte array to the output buffer. The number of bytes to read from the input buffer is determined by numBytes.
/// </summary>
/// <param name="output">Output byte array</param>
/// <param name="input">Input byte array</param>
/// <param name="numBytes">How many bytes to process</param>
private void WorkBytes(byte[] output, byte[] input, int numBytes)
{
if (isDisposed)
{
throw new ObjectDisposedException("state", "The ChaCha state has been disposed");
}
if (input == null)
{
throw new ArgumentNullException("input", "Input cannot be null");
}
if (output == null)
{
throw new ArgumentNullException("output", "Output cannot be null");
}
if (numBytes < 0 || numBytes > input.Length)
{
throw new ArgumentOutOfRangeException("numBytes", "The number of bytes to read must be between [0..input.Length]");
}
if (output.Length < numBytes)
{
throw new ArgumentOutOfRangeException("output", $"Output byte array should be able to take at least {numBytes}");
}
uint[] x = new uint[stateLength]; // Working buffer
byte[] tmp = new byte[processBytesAtTime]; // Temporary buffer
int offset = 0;
while (numBytes > 0)
{
// Copy state to working buffer
Buffer.BlockCopy(this.state, 0, x, 0, stateLength * sizeof(uint));
for (int i = 0; i < 10; i++)
{
QuarterRound(x, 0, 4, 8, 12);
QuarterRound(x, 1, 5, 9, 13);
QuarterRound(x, 2, 6, 10, 14);
QuarterRound(x, 3, 7, 11, 15);
QuarterRound(x, 0, 5, 10, 15);
QuarterRound(x, 1, 6, 11, 12);
QuarterRound(x, 2, 7, 8, 13);
QuarterRound(x, 3, 4, 9, 14);
}
for (int i = 0; i < stateLength; i++)
{
Util.ToBytes(tmp, Util.Add(x[i], this.state[i]), 4 * i);
}
this.state[12] = Util.AddOne(state[12]);
if (this.state[12] <= 0)
{
/* Stopping at 2^70 bytes per nonce is the user's responsibility */
this.state[13] = Util.AddOne(state[13]);
}
// In case these are last bytes
if (numBytes <= processBytesAtTime)
{
for (int i = 0; i < numBytes; i++)
{
output[i + offset] = (byte)(input[i + offset] ^ tmp[i]);
}
return;
}
for (int i = 0; i < processBytesAtTime; i++)
{
output[i + offset] = (byte)(input[i + offset] ^ tmp[i]);
}
numBytes -= processBytesAtTime;
offset += processBytesAtTime;
}
}
/// <summary>
/// The ChaCha Quarter Round operation. It operates on four 32-bit unsigned integers within the given buffer at indices a, b, c, and d.
/// </summary>
/// <remarks>
/// The ChaCha state does not have four integer numbers: it has 16. So the quarter-round operation works on only four of them -- hence the name. Each quarter round operates on four predetermined numbers in the ChaCha state.
/// See <a href="https://tools.ietf.org/html/rfc7539#page-4">ChaCha20 Spec Sections 2.1 - 2.2</a>.
/// </remarks>
/// <param name="x">A ChaCha state (vector). Must contain 16 elements.</param>
/// <param name="a">Index of the first number</param>
/// <param name="b">Index of the second number</param>
/// <param name="c">Index of the third number</param>
/// <param name="d">Index of the fourth number</param>
private static void QuarterRound(uint[] x, uint a, uint b, uint c, uint d)
{
x[a] = Util.Add(x[a], x[b]);
x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 16);
x[c] = Util.Add(x[c], x[d]);
x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 12);
x[a] = Util.Add(x[a], x[b]);
x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 8);
x[c] = Util.Add(x[c], x[d]);
x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 7);
}
#region Destructor and Disposer
/// <summary>
/// Clear and dispose of the internal state. The finalizer is only called if Dispose() was never called on this cipher.
/// </summary>
~ChaCha20()
{
Dispose(false);
}
/// <summary>
/// Clear and dispose of the internal state. Also request the GC not to call the finalizer, because all cleanup has been taken care of.
/// </summary>
public void Dispose()
{
Dispose(true);
/*
* The Garbage Collector does not need to invoke the finalizer because Dispose(bool) has already done all the cleanup needed.
*/
GC.SuppressFinalize(this);
}
/// <summary>
/// This method should only be invoked from Dispose() or the finalizer. This handles the actual cleanup of the resources.
/// </summary>
/// <param name="disposing">
/// Should be true if called by Dispose(); false if called by the finalizer
/// </param>
private void Dispose(bool disposing)
{
if (!isDisposed)
{
if (disposing)
{
/* Cleanup managed objects by calling their Dispose() methods */
}
/* Cleanup any unmanaged objects here */
Array.Clear(state, 0, stateLength);
}
isDisposed = true;
}
#endregion // Destructor and Disposer
}
/// <summary>
/// Utilities that are used during compression
/// </summary>
public static class Util
{
/// <summary>
/// n-bit left rotation operation (towards the high bits) for 32-bit integers.
/// </summary>
/// <param name="v"></param>
/// <param name="c"></param>
/// <returns>The result of (v LEFTSHIFT c)</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Rotate(uint v, int c)
{
unchecked
{
return (v << c) | (v >> (32 - c));
}
}
/// <summary>
/// Unchecked integer exclusive or (XOR) operation.
/// </summary>
/// <param name="v"></param>
/// <param name="w"></param>
/// <returns>The result of (v XOR w)</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint XOr(uint v, uint w)
{
return unchecked(v ^ w);
}
/// <summary>
/// Unchecked integer addition. The ChaCha spec defines certain operations to use 32-bit unsigned integer addition modulo 2^32.
/// </summary>
/// <remarks>
/// See <a href="https://tools.ietf.org/html/rfc7539#page-4">ChaCha20 Spec Section 2.1</a>.
/// </remarks>
/// <param name="v"></param>
/// <param name="w"></param>
/// <returns>The result of (v + w) modulo 2^32</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Add(uint v, uint w)
{
return unchecked(v + w);
}
/// <summary>
/// Add 1 to the input parameter using unchecked integer addition. The ChaCha spec defines certain operations to use 32-bit unsigned integer addition modulo 2^32.
/// </summary>
/// <remarks>
/// See <a href="https://tools.ietf.org/html/rfc7539#page-4">ChaCha20 Spec Section 2.1</a>.
/// </remarks>
/// <param name="v"></param>
/// <returns>The result of (v + 1) modulo 2^32</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint AddOne(uint v)
{
return unchecked(v + 1);
}
/// <summary>
/// Convert four bytes of the input buffer into an unsigned 32-bit integer, beginning at the inputOffset.
/// </summary>
/// <param name="p"></param>
/// <param name="inputOffset"></param>
/// <returns>An unsigned 32-bit integer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint U8To32Little(byte[] p, int inputOffset)
{
unchecked
{
return ((uint)p[inputOffset]
| ((uint)p[inputOffset + 1] << 8)
| ((uint)p[inputOffset + 2] << 16)
| ((uint)p[inputOffset + 3] << 24));
}
}
/// <summary>
/// Serialize the input integer into the output buffer. The input integer will be split into 4 bytes and put into four sequential places in the output buffer, starting at the outputOffset.
/// </summary>
/// <param name="output"></param>
/// <param name="input"></param>
/// <param name="outputOffset"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ToBytes(byte[] output, uint input, int outputOffset)
{
unchecked
{
output[outputOffset] = (byte)input;
output[outputOffset + 1] = (byte)(input >> 8);
output[outputOffset + 2] = (byte)(input >> 16);
output[outputOffset + 3] = (byte)(input >> 24);
}
}
}
}
================================================
FILE: N_m3u8DL-CLI/Decode51CtoKey.cs
================================================
using NiL.JS.BaseLibrary;
using NiL.JS.Core;
using NiL.JS.Extensions;
using System.Security.Cryptography;
using System.Text;
namespace N_m3u8DL_CLI
{
/*
* js代码来自:https://static1.51ctocdn.cn/edu/player/h5/h5player.js line:9421
*
*/
class Decode51CtoKey
{
private static string JS = @"
var Base64={_keyStr:'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',encode:function(e){var t='';var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t='';var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,'');while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/\r\n/g,'\n');var t='';for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t='';var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}}
var btoa = function(str) {
return Base64.encode(str);
}
var bu = function (e, t) {
for (var r = t - (e += '').length; 0 < r; r--)
e = '0' + e;
return e
}
,
MD5 = function (e) {
var t, a = 0, n = 8;
function o(e, t, r, i, a, n) {
return g((s = g(g(t, e), g(i, n))) << a | s >>> 32 - a, r);
var s
}
function c(e, t, r, i, a, n, s) {
return o(t & r | ~t & i, e, t, a, n, s)
}
function h(e, t, r, i, a, n, s) {
return o(t & i | r & ~i, e, t, a, n, s)
}
function f(e, t, r, i, a, n, s) {
return o(t ^ r ^ i, e, t, a, n, s)
}
function p(e, t, r, i, a, n, s) {
return o(r ^ (t | ~i), e, t, a, n, s)
}
function g(e, t) {
var r = (65535 & e) + (65535 & t);
return (e >> 16) + (t >> 16) + (r >> 16) << 16 | 65535 & r
}
return function (e) {
for (var t = a ? '0123456789ABCDEF' : '0123456789abcdef', r = '', i = 0; i < 4 * e.length; i++)
r += t.charAt(e[i >> 2] >> i % 4 * 8 + 4 & 15) + t.charAt(e[i >> 2] >> i % 4 * 8 & 15);
return r
}(function (e, t) {
e[t >> 5] = e[t >> 5] | (128 << t % 32),
e[14 + (t + 64 >>> 9 << 4)] = t;
for (var r = 1732584193, i = -271733879, a = -1732584194, n = 271733878, s = 0; s < e.length; s += 16) {
var o = r
, l = i
, u = a
, d = n;
i = p(i = p(i = p(i = p(i = f(i = f(i = f(i = f(i = h(i = h(i = h(i = h(i = c(i = c(i = c(i = c(i, a = c(a, n = c(n, r = c(r, i, a, n, e[s + 0], 7, -680876936), i, a, e[s + 1], 12, -389564586), r, i, e[s + 2], 17, 606105819), n, r, e[s + 3], 22, -1044525330), a = c(a, n = c(n, r = c(r, i, a, n, e[s + 4], 7, -176418897), i, a, e[s + 5], 12, 1200080426), r, i, e[s + 6], 17, -1473231341), n, r, e[s + 7], 22, -45705983), a = c(a, n = c(n, r = c(r, i, a, n, e[s + 8], 7, 1770035416), i, a, e[s + 9], 12, -1958414417), r, i, e[s + 10], 17, -42063), n, r, e[s + 11], 22, -1990404162), a = c(a, n = c(n, r = c(r, i, a, n, e[s + 12], 7, 1804603682), i, a, e[s + 13], 12, -40341101), r, i, e[s + 14], 17, -1502002290), n, r, e[s + 15], 22, 1236535329), a = h(a, n = h(n, r = h(r, i, a, n, e[s + 1], 5, -165796510), i, a, e[s + 6], 9, -1069501632), r, i, e[s + 11], 14, 643717713), n, r, e[s + 0], 20, -373897302), a = h(a, n = h(n, r = h(r, i, a, n, e[s + 5], 5, -701558691), i, a, e[s + 10], 9, 38016083), r, i, e[s + 15], 14, -660478335), n, r, e[s + 4], 20, -405537848), a = h(a, n = h(n, r = h(r, i, a, n, e[s + 9], 5, 568446438), i, a, e[s + 14], 9, -1019803690), r, i, e[s + 3], 14, -187363961), n, r, e[s + 8], 20, 1163531501), a = h(a, n = h(n, r = h(r, i, a, n, e[s + 13], 5, -1444681467), i, a, e[s + 2], 9, -51403784), r, i, e[s + 7], 14, 1735328473), n, r, e[s + 12], 20, -1926607734), a = f(a, n = f(n, r = f(r, i, a, n, e[s + 5], 4, -378558), i, a, e[s + 8], 11, -2022574463), r, i, e[s + 11], 16, 1839030562), n, r, e[s + 14], 23, -35309556), a = f(a, n = f(n, r = f(r, i, a, n, e[s + 1], 4, -1530992060), i, a, e[s + 4], 11, 1272893353), r, i, e[s + 7], 16, -155497632), n, r, e[s + 10], 23, -1094730640), a = f(a, n = f(n, r = f(r, i, a, n, e[s + 13], 4, 681279174), i, a, e[s + 0], 11, -358537222), r, i, e[s + 3], 16, -722521979), n, r, e[s + 6], 23, 76029189), a = f(a, n = f(n, r = f(r, i, a, n, e[s + 9], 4, -640364487), i, a, e[s + 12], 11, -421815835), r, i, e[s + 15], 16, 530742520), n, r, e[s + 2], 23, -995338651), a = p(a, n = p(n, r = p(r, i, a, n, e[s + 0], 6, -198630844), i, a, e[s + 7], 10, 1126891415), r, i, e[s + 14], 15, -1416354905), n, r, e[s + 5], 21, -57434055), a = p(a, n = p(n, r = p(r, i, a, n, e[s + 12], 6, 1700485571), i, a, e[s + 3], 10, -1894986606), r, i, e[s + 10], 15, -1051523), n, r, e[s + 1], 21, -2054922799), a = p(a, n = p(n, r = p(r, i, a, n, e[s + 8], 6, 1873313359), i, a, e[s + 15], 10, -30611744), r, i, e[s + 6], 15, -1560198380), n, r, e[s + 13], 21, 1309151649), a = p(a, n = p(n, r = p(r, i, a, n, e[s + 4], 6, -145523070), i, a, e[s + 11], 10, -1120210379), r, i, e[s + 2], 15, 718787259), n, r, e[s + 9], 21, -343485551),
r = g(r, o),
i = g(i, l),
a = g(a, u),
n = g(n, d)
}
return Array(r, i, a, n)
}(function (e) {
for (var t = Array(), r = (1 << n) - 1, i = 0; i < e.length * n; i += n)
t[i >> 5] = t[i >> 5] | ((e.charCodeAt(i / n) & r) << i % 32);
return t
}(t = e), t.length * n))
}
,
eeb64 = function (e) {
for (var t = '', r = '', i = 0; i < e.length; i++)
t += bu('BqrCwxVefD9457mnoHINOPQRSUXLMabFcdghijyzkl6GApstuJKvW0YZ23ET81=_'.indexOf(e[i]).toString(2), 6);
for (t = t.substring(t.length % 8),
i = 0; i < Math.ceil(t.length / 8); i++)
r += String.fromCharCode(parseInt(t.substr(8 * i, 8), 2));
return base64decode(r)
}
,
dec = function (e, t) {
function r(e) {
for (var t = 0; t < s.length; t++)
if (s[t] == e)
return t
}
e[1];
var i = [o[r(e[13])], o[r(e[8])], o[r(e[4])]]
, a = e.substr(0, 1) + e.substr(2, 2) + e.substr(5, 3) + e.substr(9, 4) + e.substr(14);
debugger;var x = [r(e[13]),r(e[8]),r(e[4])]
for (var n in i)
a = i[n](a, t);
return a
}
,
base64decode = function (e) {
var t, r, i, a, n, s, o, l = 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);
for (s = e.length,
n = 0,
o = ''; n < s;) {
for (; t = l[255 & e.charCodeAt(n++)],
n < s && -1 == t;)
;
if (-1 == t)
break;
for (; r = l[255 & e.charCodeAt(n++)],
n < s && -1 == r;)
;
if (-1 == r)
break;
o += String.fromCharCode(t << 2 | (48 & r) >> 4);
do {
if (61 == (i = 255 & e.charCodeAt(n++)))
return o;
i = l[i]
} while (n < s && -1 == i); if (-1 == i)
break;
o += String.fromCharCode((15 & r) << 4 | (60 & i) >> 2);
do {
if (61 == (a = 255 & e.charCodeAt(n++)))
return o;
a = l[a]
} while (n < s && -1 == a); if (-1 == a)
break;
o += String.fromCharCode((3 & i) << 6 | a)
}
return o
}
,
base64ToArrayBuffer = function (e) {
for (var t = atob(e), r = t.length, i = new Uint8Array(r), a = 0; a < r; a++)
i[a] = t.charCodeAt(a);
return i.buffer
}
,
arrayBufferToBase64 = function (e) {
for (var t = '', r = new Uint8Array(e), i = r.byteLength, a = 0; a < i; a++)
t += String.fromCharCode(r[a]);
return btoa(t)
};
var s = ['s', 'i', 'y', 'u', 'a', 'n', 't', 'l', 'w', 'x']
, o = [function (e) {
return e
}
, function (e, t, r) {
r = r || 'eDu_51Cto_siyuanTlw';
for (var i = base64decode(e).split(''), a = MD5(t + r).toString(), n = a.length - 1; 0 <= n; n--) {
var s = a[n].charCodeAt() % (i.length - 1);
i.splice(s, 1)
}
return i.join('')
}
, function (e, t, r) {
for (var i = t % 7, a = e.length, n = '', s = 0; s < a / 2; s++) {
var o = 2 * s;
n += 0 == i || s % i == 0 ? e[o] + e[o + 1] : e[o + 1] ? e[o + 1] + e[o] : e[o]
}
var l = base64decode(n)
, u = (l.length - 1) / 2
, d = '';
for (s = 0; s < u; s++)
o = 2 * s,
i < s && o++ ,
d += s % 3 == 0 ? l[o] : l[o + 1];
return d
}
, function (e) {
return e
}
, function (e) {
return e
}
, function (e, t, r) {
var i, a, n, s, o, l, u, d = e.slice(0, 7) + e.slice(10, 12) + e.slice(15, -3), c = '', h = 0, f = 0, p = '';
d = d.split('').reverse().join(''),
i = eeb64(d),
a = parseInt(i.substr(0, 1)),
s = (n = i.slice(6, -3)).match(/^\d*/),
o = n.match(/\d*$/),
l = s[0],
u = o[0],
n = n.replace(/^\d*/, '').replace(/\d*$/, '');
for (var g = 0; g < u.length; g++)
c += bu(parseInt(u[g]).toString(2), 3);
for (c = c.substr(a),
g = 0; g < c.length; g++)
1 == c[g] ? (p += l[f],
f++) : (p += n[h],
h++);
return p
}
, function (e, t, r) {
for (var i, a = {
B: '0',
q: '1',
r: '2',
C: '3',
w: '4',
x: '5',
V: '6',
e: '7',
f: '8',
D: '9',
9: 'a',
4: 'b',
5: 'c',
7: 'd',
m: 'e',
n: 'f',
o: 'g',
H: 'h',
I: 'i',
N: 'j',
O: 'k',
P: 'l',
Q: 'm',
R: 'n',
S: 'o',
U: 'p',
X: 'q',
L: 'r',
M: 's',
a: 't',
b: 'u',
F: 'v',
c: 'w',
d: 'x',
g: 'y',
h: 'z',
i: 'A',
j: 'B',
y: 'C',
z: 'D',
k: 'E',
l: 'F',
6: 'G',
G: 'H',
A: 'I',
p: 'J',
s: 'K',
t: 'L',
u: 'M',
J: 'N',
K: 'O',
v: 'P',
W: 'Q',
0: 'R',
Y: 'S',
Z: 'T',
2: 'U',
3: 'V',
E: 'W',
T: 'X',
8: 'Y',
1: 'Z'
}, n = 5, s = '', o = 0, l = '', u = 0, d = 0; d < e.length; d++) {
var c = e[d];
s += a[c] ? a[c] : c
}
for (d = 0; d < 8; d++)
i = 7 == d ? 32 - u : Math.abs(8 - n++),
l += s.substr(o++, 1),
o += i,
u += i;
return l += s.substr(40),
eeb64(l.split('').reverse().join(''))
}
, function (e, t, r) {
r = r || 'eDu_51Cto_siyuanTlw';
var i = eeb64(e)
, a = MD5(r + t).toString().slice(0, 16)
, n = i.indexOf(a)
, s = parseInt(i.slice(0, n), 16);
if (!n)
return !1;
var o = i.substr(16 + n);
return o.length == s && o
}
];
function getKey(text, lid) {
return btoa(dec(text, lid));
}";
private static string MD5Encoding(string rawPass)
{
MD5 md5 = MD5.Create();
byte[] bs = Encoding.UTF8.GetBytes(rawPass);
byte[] hs = md5.ComputeHash(bs);
StringBuilder sb = new StringBuilder();
foreach (byte b in hs)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
public static string GetDecodeKey(string encodeKey, string lid)
{
var context = new Context();
context.Eval(JS);
var concatFunction = context.GetVariable("getKey").As<Function>();
string key = concatFunction.Call(new Arguments { encodeKey, lid }).ToString();
return key;
}
public static string GetSign(string lid)
{
var data = lid + "eDu_51Cto_siyuanTlw";
return MD5Encoding(data);
}
}
}
================================================
FILE: N_m3u8DL-CLI/DecodeCdeledu.cs
================================================
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;
}
}
}
================================================
FILE: N_m3u8DL-CLI/DecodeDdyun.cs
================================================
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);
}
}
}
================================================
FILE: N_m3u8DL-CLI/DecodeHuke88Key.cs
================================================
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;
}
}
}
================================================
FILE: N_m3u8DL-CLI/DecodeImooc.cs
================================================
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;
}
}
}
================================================
FILE: N_m3u8DL-CLI/DecodeNfmovies.cs
================================================
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;
}
}
}
================================================
FILE: N_m3u8DL-CLI/Decrypter.cs
================================================
using CSChaCha20;
using System;
using System.IO;
using System.Linq;
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[] CHACHA20Decrypt(byte[] encryptedBuff, byte[] keyBytes, byte[] nonceBytes)
{
if (keyBytes.Length != 32)
throw new Exception("Key must be 32 bytes!");
if (nonceBytes.Length != 12 && nonceBytes.Length != 8)
throw new Exception("Key must be 12 or 8 bytes!");
if (nonceBytes.Length == 8)
nonceBytes = (new byte[4] { 0, 0, 0, 0 }).Concat(nonceBytes).ToArray();
var decStream = new MemoryStream();
using (BinaryReader reader = new BinaryReader(new MemoryStream(encryptedBuff)))
{
using (BinaryWriter writer = new BinaryWriter(decStream))
{
while (true)
{
var buffer = reader.ReadBytes(1024);
byte[] dec = new byte[buffer.Length];
if (buffer.Length > 0)
{
ChaCha20 forDecrypting = new ChaCha20(keyBytes, nonceBytes, 0);
forDecrypting.DecryptBytes(dec, buffer);
writer.Write(dec, 0, dec.Length);
}
else
{
break;
}
}
}
}
return decStream.ToArray();
}
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;
}
}
}
================================================
FILE: N_m3u8DL-CLI/DownloadManager.cs
================================================
using Newtonsoft.Json.Linq;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace N_m3u8DL_CLI
{
class DownloadManager
{
private int stopCount = 0; //速度为零的停止
private string jsonFile = string.Empty;
private int total = 0;
public static string partsPadZero = string.Empty;
string segsPadZero = string.Empty;
private bool isVTT = false;
bool externalAudio = false; //额外的音轨
string externalAudioUrl = "";
bool externalSub = false; //额外的字幕
string externalSubUrl = "";
string fflogName = "_ffreport.log";
public static bool BinaryMerge = false;
public int Threads { get; set; } = 1;
public int RetryCount { get; set; } = 5;
public string Headers { get; set; } = string.Empty;
public string DownDir { get; set; } = string.Empty;
public string DownName { get; set; } = string.Empty;
public bool DelAfterDone { get; set; } = false;
public string MuxFormat { get; set; } = "mp4";
public bool MuxFastStart { get; set; } = true;
public string MuxSetJson { get; set; } = string.Empty;
public int TimeOut { get; set; } = 10000; //超时设置
public static double DownloadedSize { get; set; } = 0; //已下载大小
public static double ToDoSize { get; set; } = 0; //待下载大小
public static bool HasSetDir { get; set; } = false;
public bool NoMerge { get; set; } = false;
public static int CalcTime { get; set; } = 1; //计算速度的间隔
public static int Count { get; set; } = 0;
public static int PartsCount { get; set; } = 0;
public static bool DisableIntegrityCheck { get; set; } = false; //关闭完整性检查
public static bool HasExtMap { get; set; } = false; //是否有MAP
static CancellationTokenSource cts = new CancellationTokenSource();
//计算下载速度
static System.Timers.Timer timer = new System.Timers.Timer(1000 * CalcTime); //实例化Timer类
public DownloadManager()
{
timer.AutoReset = true;
timer.Elapsed += delegate
{
var eta = "";
if (ToDoSize != 0)
{
eta = " @ " + Global.FormatTime(Convert.ToInt32(ToDoSize / (Global.BYTEDOWN / CalcTime)));
}
var print = Global.FormatFileSize((Global.BYTEDOWN) / CalcTime) + "/s" + eta;
ProgressReporter.Report("", "(" + print + ")");
if (Global.HadReadInfo && Global.BYTEDOWN <= Global.STOP_SPEED * 1024 * CalcTime)
{
stopCount++;
eta = "";
if (ToDoSize != 0)
{
eta = " @ " + Global.FormatTime(Convert.ToInt32(ToDoSize / (Global.BYTEDOWN / CalcTime)));
}
print = Global.FormatFileSize((Global.BYTEDOWN) / CalcTime) + "/s [" + stopCount + "]" + eta;
ProgressReporter.Report("", "(" + print + ")");
if (stopCount >= 12)
{
Global.ShouldStop = true;
cts.Cancel();
timer.Enabled = false;
}
}
else
{
stopCount = 0;
Global.BYTEDOWN = 0;
Global.ShouldStop = false;
}
};
}
public void DoDownload()
{
jsonFile = Path.Combine(DownDir, "meta.json");
if (!File.Exists(jsonFile))
return;
string jsonContent = File.ReadAllText(jsonFile);
JObject initJson = JObject.Parse(jsonContent);
JArray parts = JArray.Parse(initJson["m3u8Info"]["segments"].ToString()); //大分组
string segCount = initJson["m3u8Info"]["count"].ToString();
string oriCount = initJson["m3u8Info"]["originalCount"].ToString(); //原始分片数量
string isVOD = initJson["m3u8Info"]["vod"].ToString();
try
{
if (initJson["m3u8Info"]["audio"].ToString() != "")
externalAudio = true;
externalAudioUrl = initJson["m3u8Info"]["audio"].ToString();
LOGGER.WriteLine(strings.hasExternalAudioTrack);
LOGGER.PrintLine(strings.hasExternalAudioTrack, LOGGER.Warning);
}
catch (Exception) {}
try
{
if (initJson["m3u8Info"]["sub"].ToString() != "")
externalSub = true;
externalSubUrl = initJson["m3u8Info"]["sub"].ToString();
LOGGER.WriteLine(strings.hasExternalSubtitleTrack);
LOGGER.PrintLine(strings.hasExternalSubtitleTrack, LOGGER.Warning);
}
catch (Exception) { }
total = Convert.ToInt32(segCount);
PartsCount = parts.Count;
segsPadZero = string.Empty.PadRight(oriCount.Length, '0');
partsPadZero = string.Empty.PadRight(Convert.ToString(parts.Count).Length, '0');
//是直播视频
if (isVOD == "False")
{
return;
}
Global.ShouldStop = false; //是否该停止下载
if (!Directory.Exists(DownDir))
Directory.CreateDirectory(DownDir); //新建文件夹
Watcher watcher = new Watcher(DownDir);
watcher.Total = total;
watcher.PartsCount = PartsCount;
watcher.WatcherStrat();
//开始计算速度
timer.Enabled = true;
cts = new CancellationTokenSource();
//开始调用下载
LOGGER.WriteLine(strings.startDownloading);
LOGGER.PrintLine(strings.startDownloading, LOGGER.Warning);
//下载MAP文件(若有)
downloadMap:
if (HasExtMap)
{
LOGGER.PrintLine(strings.downloadingMapFile);
Downloader sd = new Downloader();
sd.TimeOut = TimeOut;
sd.FileUrl = initJson["m3u8Info"]["extMAP"].Value<string>();
sd.Headers = Headers;
sd.Method = "NONE";
if (sd.FileUrl.Contains("|")) //有range
{
string[] tmp = sd.FileUrl.Split('|');
sd.FileUrl = tmp[0];
sd.StartByte = Convert.ToUInt32(tmp[1].Split('@')[1]);
sd.ExpectByte = Convert.ToUInt32(tmp[1].Split('@')[0]);
}
sd.SavePath = DownDir + "\\!MAP.tsdownloading";
if (File.Exists(sd.SavePath))
File.Delete(sd.SavePath);
if (File.Exists(DownDir + "\\Part_0\\!MAP.ts"))
File.Delete(DownDir + "\\Part_0\\!MAP.ts");
sd.Down(); //开始下载
if (!File.Exists(DownDir + "\\!MAP.ts")) //检测是否成功下载
{
Thread.Sleep(1000);
goto downloadMap;
}
}
//首先下载第一个分片
JToken firstSeg = JArray.Parse(parts[0].ToString())[0];
if (!File.Exists(DownDir + "\\Part_" + 0.ToString(partsPadZero) + "\\" + firstSeg["index"].Value<int>().ToString(segsPadZero) + ".ts"))
{
try
{
Downloader sd = new Downloader();
sd.TimeOut = TimeOut;
sd.SegDur = firstSeg["duration"].Value<double>();
if (sd.SegDur < 0) sd.SegDur = 0; //防止负数
sd.FileUrl = firstSeg["segUri"].Value<string>();
//VTT字幕
if (isVTT == false && (sd.FileUrl.Trim('\"').EndsWith(".vtt") || sd.FileUrl.Trim('\"').EndsWith(".webvtt")))
isVTT = true;
sd.Method = firstSeg["method"].Value<string>();
if (sd.Method != "NONE")
{
sd.Key = firstSeg["key"].Value<string>();
sd.Iv = firstSeg["iv"].Value<string>();
}
if (firstSeg["expectByte"] != null)
sd.ExpectByte = firstSeg["expectByte"].Value<long>();
if (firstSeg["startByte"] != null)
sd.StartByte = firstSeg["startByte"].Value<long>();
sd.Headers = Headers;
sd.SavePath = DownDir + "\\Part_" + 0.ToString(partsPadZero) + "\\" + firstSeg["index"].Value<int>().ToString(segsPadZero) + ".tsdownloading";
if (File.Exists(sd.SavePath))
File.Delete(sd.SavePath);
LOGGER.PrintLine(strings.downloadingFirstSegement);
if (!Global.ShouldStop)
sd.Down(); //开始下载
}
catch (Exception e)
{
//LOG.WriteLineError(e.ToString());
}
}
if (Global.HadReadInfo == false)
{
string href = DownDir + "\\Part_" + 0.ToString(partsPadZero) + "\\" + firstSeg["index"].Value<int>().ToString(segsPadZero) + ".ts";
if (File.Exists(DownDir + "\\!MAP.ts"))
href = DownDir + "\\!MAP.ts";
Global.GzipHandler(href);
bool flag = false;
foreach (string ss in (string[])Global.GetVideoInfo(href).ToArray(typeof(string)))
{
LOGGER.WriteLine(ss.Trim());
LOGGER.PrintLine(ss.Trim(), 0);
if (ss.Trim().Contains("Error in reading file"))
flag = true;
}
LOGGER.PrintLine(strings.waitForCompletion, LOGGER.Warning);
if (!flag)
Global.HadReadInfo = true;
}
//多线程设置
ParallelOptions parallelOptions = new ParallelOptions
{
MaxDegreeOfParallelism = Threads,
CancellationToken = cts.Token
};
//构造包含所有分片的新的segments
JArray segments = new JArray();
for (int i = 0; i < parts.Count; i++)
{
var tmp = JArray.Parse(parts[i].ToString());
for (int j = 0; j < tmp.Count; j++)
{
JObject t = (JObject)tmp[j];
t.Add("part", i);
segments.Add(t);
}
}
//剔除第一个分片(已下载过)
segments.RemoveAt(0);
try
{
ParallelLoopResult result = Parallel.ForEach(segments,
parallelOptions,
() => new Downloader(),
(info, loopstate, index, sd) =>
{
if (Global.ShouldStop)
loopstate.Stop();
else
{
sd.TimeOut = TimeOut;
sd.SegDur = info["duration"].Value<double>();
if (sd.SegDur < 0) sd.SegDur = 0; //防止负数
sd.FileUrl = info["segUri"].Value<string>();
//VTT字幕
if (isVTT == false && (sd.FileUrl.Trim('\"').EndsWith(".vtt") || sd.FileUrl.Trim('\"').EndsWith(".webvtt")))
isVTT = true;
sd.Method = info["method"].Value<string>();
if (sd.Method != "NONE")
{
sd.Key = info["key"].Value<string>();
sd.Iv = info["iv"].Value<string>();
}
if (firstSeg["expectByte"] != null)
sd.ExpectByte = info["expectByte"].Value<long>();
if (firstSeg["startByte"] != null)
sd.StartByte = info["startByte"].Value<long>();
sd.Headers = Headers;
sd.SavePath = DownDir + "\\Part_" + info["part"].Value<int>().ToString(partsPadZero) + "\\" + info["index"].Value<int>().ToString(segsPadZero) + ".tsdownloading";
if (File.Exists(sd.SavePath))
File.Delete(sd.SavePath);
if (!Global.ShouldStop)
sd.Down(); //开始下载
}
return sd;
},
(sd) => { });
if (result.IsCompleted)
{
//LOGGER.WriteLine("Part " + (info["part"].Value<int>() + 1).ToString(partsPadZero) + " of " + parts.Count + " Completed");
}
}
catch (Exception)
{
;//捕获取消循环产生的异常
}
finally
{
cts.Dispose();
}
watcher.WatcherStop();
//停止速度监测
timer.Enabled = false;
//检测是否下完
IsComplete(Convert.ToInt32(segCount));
}
public void IsComplete(int segCount)
{
int tsCount = 0;
if (DisableIntegrityCheck)
{
tsCount = segCount;
goto ll;
}
for (int i = 0; i < PartsCount; i++)
{
tsCount += Global.GetFileCount(DownDir + "\\Part_" + i.ToString(partsPadZero), ".ts");
}
ll:
if (tsCount != segCount)
{
LOGGER.PrintLine(strings.downloadedCount + tsCount + " / " + segCount);
LOGGER.WriteLine(strings.downloadedCount + tsCount + " of " + segCount);
if (Count <= RetryCount)
{
Count++;
LOGGER.WriteLine(strings.retryCount + Count + " / " + RetryCount);
LOGGER.PrintLine(strings.retryCount + Count + " / " + RetryCount, LOGGER.Warning);
Thread.Sleep(3000);
DoDownload();
}
}
else //开始合并
{
LOGGER.PrintLine(strings.downloadComplete + (DisableIntegrityCheck ? "(" + strings.disableIntegrityCheck + ")" : ""));
if (NoMerge == false)
{
string exePath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
string driverName = exePath.Remove(exePath.IndexOf(':'));
Console.Title = "Done.";
LOGGER.WriteLine(strings.startMerging);
LOGGER.PrintLine(strings.startMerging, LOGGER.Warning);
//VTT字幕
if (isVTT == true)
{
MuxFormat = "vtt";
Global.ReAdjustVtt(Global.GetFiles(DownDir + "\\Part_0", ".ts"));
}
//只有一个Part直接用ffmpeg合并
if (PartsCount == 1)
{
/*
* FFREPORT=file=C\:/Users/nilao/Desktop/新建文件夹/3.log:level=32
* Test with Powershell, its C:/Users/nilao/Desktop/新建文件夹/3.log
*/
FFmpeg.OutPutPath = Path.Combine(Directory.GetParent(DownDir).FullName, DownName);
FFmpeg.ReportFile = driverName + "\\:" + exePath.Remove(0, exePath.IndexOf(':') + 1).Replace("\\", "/") + "/Logs/" + Path.GetFileNameWithoutExtension(LOGGER.LOGFILE) + fflogName;
if (File.Exists(DownDir + "\\!MAP.ts"))
File.Move(DownDir + "\\!MAP.ts", DownDir + "\\Part_0\\!MAP.ts");
if (BinaryMerge)
{
LOGGER.PrintLine(strings.binaryMergingPleaseWait);
MuxFormat = "ts";
//有MAP文件,一般为mp4,采取默认动作
if(File.Exists(DownDir + "\\Part_0\\!MAP.ts"))
MuxFormat = "mp4";
if (isVTT)
MuxFormat = "vtt";
if (Global.AUDIO_TYPE != "")
MuxFormat = Global.AUDIO_TYPE;
Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir + "\\Part_0", ".ts"), FFmpeg.OutPutPath + $".{MuxFormat}");
}
else
{
if (Global.VIDEO_TYPE != "DV") //不是杜比视界
{
//检测是否为MPEG-TS封装,不是的话就转换为TS封装
foreach (string s in Global.GetFiles(DownDir + "\\Part_0", ".ts"))
{
//跳过有MAP的情况
if (!isVTT && !File.Exists(DownDir + "\\Part_0\\!MAP.ts") && !FFmpeg.CheckMPEGTS(s))
{
//转换
LOGGER.PrintLine(strings.remuxToMPEGTS + Path.GetFileName(s));
LOGGER.WriteLine(strings.remuxToMPEGTS + Path.GetFileName(s));
FFmpeg.ConvertToMPEGTS(s);
}
}
//分片过多的情况
if (tsCount >= 1800)
{
LOGGER.WriteLine(strings.partialMergingPleaseWait);
LOGGER.PrintLine(strings.partialMergingPleaseWait, LOGGER.Warning);
Global.PartialCombineMultipleFiles(Global.GetFiles(DownDir + "\\Part_0", ".ts"));
}
if (Global.AUDIO_TYPE != "")
MuxFormat = Global.AUDIO_TYPE;
LOGGER.PrintLine(strings.ffmpegMergingPleaseWait);
if (!File.Exists(MuxSetJson))
FFmpeg.Merge(Global.GetFiles(DownDir + "\\Part_0", ".ts"), MuxFormat, MuxFastStart);
else
{
JObject json = JObject.Parse(File.ReadAllText(MuxSetJson, Encoding.UTF8));
string muxFormat = json["muxFormat"].Value<string>();
bool fastStart = Convert.ToBoolean(json["fastStart"].Value<string>());
string poster = json["poster"].Value<string>();
string audioName = json["audioName"].Value<string>();
string title = json["title"].Value<string>();
string copyright = json["copyright"].Value<string>();
string comment = json["comment"].Value<string>();
string encodingTool = "";
try { encodingTool = json["encodingTool"].Value<string>(); } catch (Exception) {; }
FFmpeg.Merge(Global.GetFiles(DownDir + "\\Part_0", ".ts"), muxFormat, fastStart, poster, audioName, title, copyright, comment, encodingTool);
}
//Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir + "\\Part_0", ".ts"), FFmpeg.OutPutPath + ".ts");
//Global.ExplorerFile(FFmpeg.OutPutPath + ".mp4");
}
else
{
LOGGER.PrintLine(strings.dolbyVisionContentMerging);
Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir + "\\Part_0", ".ts"), FFmpeg.OutPutPath + ".mp4");
}
}
LOGGER.WriteLine(strings.taskDone
+ "\r\n\r\nTask End: " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")
+ "\r\nFile: " + FFmpeg.OutPutPath + "." + (MuxFormat == "aac" ? "m4a" : MuxFormat) + "\r\n\r\n");
//删除文件夹
if (DelAfterDone)
{
try
{
DirectoryInfo directoryInfo = new DirectoryInfo(DownDir);
directoryInfo.Delete(true);
}
catch (Exception) { }
}
if (externalAudio) //下载独立音轨
{
externalAudio = false;
DownloadedSize = 0;
Global.WriteInit();
LOGGER.PrintLine(strings.downloadingExternalAudioTrack, LOGGER.Warning);
Parser parser = new Parser();
parser.Headers = Headers; //继承Header
parser.BaseUrl = "";
parser.M3u8Url = externalAudioUrl;
parser.DownName = DownName + "(Audio)";
parser.DownDir = Path.Combine(Path.GetDirectoryName(DownDir), parser.DownName);
LOGGER.WriteLine(strings.startParsing + externalAudioUrl);
LOGGER.WriteLine(strings.downloadingExternalAudioTrack);
DownName = DownName + "(Audio)";
fflogName = "_ffreport(Audio).log";
DownDir = parser.DownDir;
parser.Parse(); //开始解析
Thread.Sleep(1000);
Global.HadReadInfo = false;
Global.VIDEO_TYPE = "";
Global.AUDIO_TYPE = "";
DoDownload();
}
if (externalSub) //下载独立字幕
{
externalSub = false;
DownloadedSize = 0;
Global.WriteInit();
LOGGER.PrintLine(strings.downloadingExternalSubtitleTrack, LOGGER.Warning);
Parser parser = new Parser();
parser.Headers = Headers; //继承Header
parser.BaseUrl = "";
parser.M3u8Url = externalSubUrl;
parser.DownName = DownName.Replace("(Audio)", "") + "(Subtitle)";
parser.DownDir = Path.Combine(Path.GetDirectoryName(DownDir), parser.DownName);
LOGGER.WriteLine(strings.startParsing + externalSubUrl);
LOGGER.WriteLine(strings.downloadingExternalSubtitleTrack);
DownName = parser.DownName;
fflogName = "_ffreport(Subtitle).log";
DownDir = parser.DownDir;
parser.Parse(); //开始解析
Thread.Sleep(1000);
Global.HadReadInfo = false;
Global.VIDEO_TYPE = "";
Global.AUDIO_TYPE = "";
DoDownload();
}
LOGGER.PrintLine(strings.taskDone, LOGGER.Warning);
Environment.Exit(0); //正常退出程序
return;
}
FFmpeg.OutPutPath = Path.Combine(Directory.GetParent(DownDir).FullName, DownName);
FFmpeg.ReportFile = driverName + "\\:" + exePath.Remove(0, exePath.IndexOf(':') + 1).Replace("\\", "/") + "/Logs/" + Path.GetFileNameWithoutExtension(LOGGER.LOGFILE) + fflogName;
//合并分段
LOGGER.PrintLine(strings.startMerging);
for (int i = 0; i < PartsCount; i++)
{
string outputFilePath = DownDir + "\\Part_" + i.ToString(partsPadZero) + ".ts";
Global.CombineMultipleFilesIntoSingleFile(
Global.GetFiles(DownDir + "\\Part_" + i.ToString(partsPadZero), ".ts"),
outputFilePath);
try
{
DirectoryInfo directoryInfo = new DirectoryInfo(DownDir + "\\Part_" + i.ToString(partsPadZero));
directoryInfo.Delete(true);
}
catch (Exception) { }
}
if (BinaryMerge)
{
LOGGER.PrintLine(strings.binaryMergingPleaseWait);
MuxFormat = "ts";
//有MAP文件,一般为mp4,采取默认动作
if (File.Exists(DownDir + "\\!MAP.ts"))
MuxFormat = "mp4";
if (isVTT)
MuxFormat = "vtt";
Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir, ".ts"), FFmpeg.OutPutPath + $".{MuxFormat}");
}
else
{
if (Global.VIDEO_TYPE != "DV") //不是爱奇艺杜比视界
{
//检测是否为MPEG-TS封装,不是的话就转换为TS封装
foreach (string s in Global.GetFiles(DownDir, ".ts"))
{
//跳过有MAP的情况
if (!isVTT && !File.Exists(DownDir + "\\!MAP.ts") && !FFmpeg.CheckMPEGTS(s))
{
//转换
LOGGER.PrintLine(strings.remuxToMPEGTS + Path.GetFileName(s));
LOGGER.WriteLine(strings.remuxToMPEGTS + Path.GetFileName(s));
FFmpeg.ConvertToMPEGTS(s);
}
}
if (Global.AUDIO_TYPE != "")
MuxFormat = Global.AUDIO_TYPE;
LOGGER.PrintLine(strings.ffmpegMergingPleaseWait);
if (!File.Exists(MuxSetJson))
FFmpeg.Merge(Global.GetFiles(DownDir, ".ts"), MuxFormat, MuxFastStart);
else
{
JObject json = JObject.Parse(File.ReadAllText(MuxSetJson, Encoding.UTF8));
string muxFormat = json["muxFormat"].Value<string>();
bool fastStart = Convert.ToBoolean(json["fastStart"].Value<string>());
string poster = json["poster"].Value<string>();
string audioName = json["audioName"].Value<string>();
string title = json["title"].Value<string>();
string copyright = json["copyright"].Value<string>();
string comment = json["comment"].Value<string>();
string encodingTool = "";
try { encodingTool = json["encodingTool"].Value<string>(); } catch (Exception) {; }
FFmpeg.Merge(Global.GetFiles(DownDir, ".ts"), muxFormat, fastStart, poster, audioName, title, copyright, comment, encodingTool);
}
}
else
{
LOGGER.PrintLine(strings.dolbyVisionContentMerging);
Global.CombineMultipleFilesIntoSingleFile(Global.GetFiles(DownDir, ".ts"), FFmpeg.OutPutPath + ".mp4");
}
}
LOGGER.WriteLine(strings.taskDone
+ "\r\n\r\nTask End: " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")
+ "\r\nFile: " + FFmpeg.OutPutPath + "." + (MuxFormat == "aac" ? "m4a" : MuxFormat) + "\r\n\r\n");
//Global.ExplorerFile(FFmpeg.OutPutPath + ".mp4");
//删除文件夹
if (DelAfterDone)
{
try
{
DirectoryInfo directoryInfo = new DirectoryInfo(DownDir);
directoryInfo.Delete(true);
}
catch (Exception) { }
}
if (externalAudio) //下载独立音轨
{
externalAudio = false;
DownloadedSize = 0;
Global.WriteInit();
LOGGER.PrintLine(strings.downloadingExternalAudioTrack, LOGGER.Warning);
Parser parser = new Parser();
parser.Headers = Headers; //继承Header
parser.BaseUrl = "";
parser.M3u8Url = externalAudioUrl;
parser.DownName = DownName + "(Audio)";
parser.DownDir = Path.Combine(Path.GetDirectoryName(DownDir), parser.DownName);
LOGGER.WriteLine(strings.startParsing + externalAudioUrl);
LOGGER.WriteLine(strings.downloadingExternalAudioTrack);
DownName = parser.DownName;
fflogName = "_ffreport(Audio).log";
DownDir = parser.DownDir;
parser.Parse(); //开始解析
Thread.Sleep(1000);
Global.HadReadInfo = false;
Global.VIDEO_TYPE = "";
Global.AUDIO_TYPE = "";
DoDownload();
}
if (externalSub) //下载独立字幕
{
externalSub = false;
DownloadedSize = 0;
Global.WriteInit();
LOGGER.PrintLine(strings.downloadingExternalSubtitleTrack, LOGGER.Warning);
Parser parser = new Parser();
parser.Headers = Headers; //继承Header
parser.BaseUrl = "";
parser.M3u8Url = externalSubUrl;
parser.DownName = DownName.Replace("(Audio)", "") + "(Subtitle)";
parser.DownDir = Path.Combine(Path.GetDirectoryName(DownDir), parser.DownName);
LOGGER.WriteLine(strings.startParsing + externalSubUrl);
LOGGER.WriteLine(strings.downloadingExternalSubtitleTrack);
DownName = parser.DownName;
fflogName = "_ffreport(Subtitle).log";
DownDir = parser.DownDir;
parser.Parse(); //开始解析
Thread.Sleep(1000);
Global.HadReadInfo = false;
Global.VIDEO_TYPE = "";
Global.AUDIO_TYPE = "";
DoDownload();
}
LOGGER.PrintLine(strings.taskDone, LOGGER.Warning);
Environment.Exit(0); //正常退出程序
}
else
{
Console.Title = "Done.";
LOGGER.PrintLine(strings.taskDone, LOGGER.Warning);
LOGGER.WriteLine(strings.taskDone
+ "\r\n\r\nTask End: " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));
Environment.Exit(0); //正常退出程序
}
}
}
}
}
================================================
FILE: N_m3u8DL-CLI/Downloader.cs
================================================
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; }
public static bool EnableChaCha20 { get; set; } = false;
public static string ChaCha20KeyBase64 { get; set; }
public static string ChaCha20NonceBase64 { get; set; }
//重写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 (File.Exists(fi.FullName) && EnableChaCha20)
{
byte[] decryptBuff = Decrypter.CHACHA20Decrypt(File.ReadAllBytes(fi.FullName), Convert.FromBase64String(ChaCha20KeyBase64), Convert.FromBase64String(ChaCha20NonceBase64));
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();
}
else 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();
}
}
}
}
}
================================================
FILE: N_m3u8DL-CLI/FFmpeg.cs
================================================
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;
}
}
}
================================================
FILE: N_m3u8DL-CLI/Global.cs
================================================
using BrotliSharpLib;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
namespace N_m3u8DL_CLI
{
class Global
{
private volatile static bool shouldStop = false;
public static long BYTEDOWN = 0;
public static long STOP_SPEED = 0; //KB 小于此值自动重试
public static long MAX_SPEED = 0; //KB 速度上限
public static string VIDEO_TYPE = "";
public static string AUDIO_TYPE = "";
public static bool HadReadInfo = false;
private static bool noProxy = false;
private static string useProxyAddress = "";
public static bool ShouldStop { get => shouldStop; set => shouldStop = value; }
public static bool NoProxy { get => noProxy; set => noProxy = value; }
public static string UseProxyAddress { get => useProxyAddress; set => useProxyAddress = value; }
/*===============================================================================*/
static Version ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
static string nowVer = $"{ver.Major}.{ver.Minor}.{ver.Build}";
static string nowDate = "20220711";
public static void WriteInit()
{
Console.WriteLine($"N_m3u8DL-CLI version {nowVer} 2018-2022");
Console.WriteLine($" built date: {nowDate}");
Console.WriteLine();
}
public static void CheckUpdate()
{
try
{
string redirctUrl = Get302("https://github.com/nilaoda/N_m3u8DL-CLI/releases/latest");
string latestVer = redirctUrl.Replace("https://github.com/nilaoda/N_m3u8DL-CLI/releases/tag/", "");
if (nowVer != latestVer && !latestVer.StartsWith("https"))
{
Console.Title = string.Format(strings.newerVisionDetected, latestVer);
try
{
//尝试下载新版本
string url = $"https://mirror.ghproxy.com/https://github.com/nilaoda/N_m3u8DL-CLI/releases/download/{latestVer}/N_m3u8DL-CLI_v{latestVer}.exe";
if (File.Exists(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), $"N_m3u8DL-CLI_v{latestVer}.exe")))
{
Console.Title = string.Format(strings.newerVerisonDownloaded, latestVer);
return;
}
HttpDownloadFile(url, Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), $"N_m3u8DL-CLI_v{latestVer}.exe"));
if (File.Exists(Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), $"N_m3u8DL-CLI_v{latestVer}.exe")))
Console.Title = string.Format(strings.newerVerisonDownloaded, latestVer);
else
Console.Title = string.Format(strings.newerVerisonDownloadFailed, latestVer);
}
catch (Exception)
{
;
}
}
}
catch (Exception)
{
;
}
}
public static string GetValidFileName(string input, string re = ".")
{
string title = input;
foreach (char invalidChar in Path.GetInvalidFileNameChars())
{
title = title.Replace(invalidChar.ToString(), re);
}
return title;
}
// parseInt(s, radix)
public static int GetNum(string str, int numBase)
{
return Convert.ToInt32(Microsoft.JScript.GlobalObject.parseInt(str, numBase));
}
// 统一设置代理
// 替换 else if (UseProxyAddress != "") {
// WebProxy proxy = new WebProxy(UseProxyAddress);
// webRequest.Proxy = proxy;
// }
public static void SetProxy(WebRequest webRequest)
{
var g_ProxyAddress = UseProxyAddress;
if (g_ProxyAddress.StartsWith("http://"))
{
WebProxy proxy = new WebProxy(g_ProxyAddress);
//proxy.Credentials = new NetworkCredential(username, password);
webRequest.Proxy = proxy;
}
// socks5
if (g_ProxyAddress.StartsWith("socks5://"))
{
string input = g_ProxyAddress.Remove(0, 9);
if (input.EndsWith("/"))
{
input = input.Remove(input.LastIndexOf('/'), 1);
}
string[] addr = input.Split(':');
//LOGGER.PrintLine("addr Length :" + addr.Length);
if (addr.Length == 2)
{
int port = 0;
if (int.TryParse(addr[1], out port))
{
var proxySocks5 = new MihaZupan.HttpToSocks5Proxy(addr[0], int.Parse(addr[1]));
webRequest.Proxy = proxySocks5;
//LOGGER.PrintLine("sock5 :" + addr[0] + ":" + addr[1]);
}
}
else
{
LOGGER.PrintLine("Socks5addr String Length : " + addr.Length);
}
}
}
//获取网页源码
public static string GetWebSource(String url, string headers = "", int TimeOut = 60000)
{
string htmlCode = string.Empty;
for (int i = 0; i < 5; i++)
{
try
{
reProcess:
HttpWebRequest webRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
webRequest.Method = "GET";
if (NoProxy)
{
webRequest.Proxy = null;
}
else if (UseProxyAddress != "")
{
SetProxy(webRequest);
}
webRequest.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36";
webRequest.Accept = "*/*";
webRequest.Headers.Add("Accept-Encoding", "gzip, deflate, br");
webRequest.Timeout = TimeOut; //设置超时
webRequest.KeepAlive = false;
webRequest.AllowAutoRedirect = false; //手动处理重定向,否则会丢失Referer
if (url.Contains("pcvideo") && url.Contains(".titan.mgtv.com"))
{
webRequest.UserAgent = "";
if (!url.Contains("/internettv/"))
webRequest.Referer = "https://www.mgtv.com";
webRequest.Headers.Add("Cookie", "MQGUID");
}
//添加headers
if (headers != "")
{
foreach (string att in headers.Split('|'))
{
try
{
if (att.Split(':')[0].ToLower() == "referer")
webRequest.Referer = att.Substring(att.IndexOf(":") + 1);
else if (att.Split(':')[0].ToLower() == "user-agent")
webRequest.UserAgent = att.Substring(att.IndexOf(":") + 1);
else if (att.Split(':')[0].ToLower() == "range")
webRequest.AddRange(Convert.ToInt32(att.Substring(att.IndexOf(":") + 1).Split('-')[0], Convert.ToInt32(att.Substring(att.IndexOf(":") + 1).Split('-')[1])));
else if (att.Split(':')[0].ToLower() == "accept")
webRequest.Accept = att.Substring(att.IndexOf(":") + 1);
else
webRequest.Headers.Add(att);
}
catch (Exception e)
{
LOGGER.WriteLineError(e.Message);
}
}
}
HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
//302
if (webResponse.Headers.Get("Location") != null)
{
url = webResponse.Headers.Get("Location");
webResponse.Close();
goto reProcess;
}
//文件过大则认为不是m3u8
if (webResponse.ContentLength != -1 && webResponse.ContentLength > 50 * 1024 * 1024) return "";
if (webResponse.ContentEncoding != null
&& webResponse.ContentEncoding.ToLower() == "gzip") //如果使用了GZip则先解压
{
using (Stream streamReceive = webResponse.GetResponseStream())
{
using (var zipStream =
new System.IO.Compression.GZipStream(streamReceive, System.IO.Compression.CompressionMode.Decompress))
{
using (StreamReader sr = new StreamReader(zipStream, Encoding.UTF8))
{
htmlCode = sr.ReadToEnd();
}
}
}
}
else if (webResponse.ContentEncoding != null
&& webResponse.ContentEncoding.ToLower() == "br") //如果使用了Brotli则先解压
{
using (Stream streamReceive = webResponse.GetResponseStream())
{
using (var bs = new BrotliStream(streamReceive, CompressionMode.Decompress))
{
using (StreamReader sr = new StreamReader(bs, Encoding.UTF8))
{
htmlCode = sr.ReadToEnd();
}
}
}
}
else
{
using (Stream streamReceive = webResponse.GetResponseStream())
{
using (StreamReader sr = new StreamReader(streamReceive, Encoding.UTF8))
{
htmlCode = sr.ReadToEnd();
}
}
}
if (webResponse != null)
{
webResponse.Close();
}
if (webRequest != null)
{
webRequest.Abort();
}
break;
}
catch (Exception e) //捕获所有异常
{
LOGGER.WriteLine(e.Message);
LOGGER.WriteLineError(e.Message);
Thread.Sleep(1000); //1秒后重试
continue;
}
}
return htmlCode;
}
[DllImport("shell32.dll", ExactSpelling = true)]
private static extern void ILFree(IntPtr pidlList);
[DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
private static extern IntPtr ILCreateFromPathW(string pszPath);
[DllImport("shell32.dll", ExactSpelling = true)]
private static extern int SHOpenFolderAndSelectItems(IntPtr pidlList, uint cild, IntPtr children, uint dwFlags);
//参数:
// string dir 指定的文件夹
// string ext 文件类型的扩展名,如".txt" , “.exe"
public static int GetFileCount(string dir, string ext)
{
if (!Directory.Exists(dir))
return 0;
int count = 0;
DirectoryInfo d = new DirectoryInfo(dir);
foreach (FileInfo fi in d.GetFiles())
{
if (fi.Extension.ToUpper() == ext.ToUpper())
{
count++;
}
}
return count;
}
/// <summary>
/// 寻找指定目录下指定后缀的文件的详细路径 如".txt"
/// </summary>
/// <param name="dir"></param>
/// <param name="ext"></param>
/// <returns></returns>
public static string[] GetFiles(string dir, string ext)
{
ArrayList al = new ArrayList();
StringBuilder sb = new StringBuilder();
DirectoryInfo d = new DirectoryInfo(dir);
foreach (FileInfo fi in d.GetFiles())
{
if (fi.Extension.ToUpper() == ext.ToUpper())
{
al.Add(fi.FullName);
}
}
string[] res = (string[])al.ToArray(typeof(string));
Array.Sort(res); //排序
return res;
}
/// <summary>
/// 获取url字符串参数,返回参数值字符串
/// </summary>
/// <param name="name">参数名称</param>
/// <param name="url">url字符串</param>
/// <returns></returns>
public static string GetQueryString(string name, string url)
{
Regex re = new Regex(@"(^|&)?(\w+)=([^&]+)(&|$)?", System.Text.RegularExpressions.RegexOptions.Compiled);
MatchCollection mc = re.Matches(url);
foreach (Match m in mc)
{
if (m.Result("$2").Equals(name))
{
return m.Result("$3");
}
}
return "";
}
//大量文件分部分二进制合并
public static void PartialCombineMultipleFiles(string[] files)
{
int div = 0;
if (files.Length <= 90000)
div = 100;
else
div = 200;
string outputName = Path.GetDirectoryName(files[0]) + "\\T";
int index = 0; //序号
//按照div的容量分割为小数组
string[][] li = Enumerable.Range(0, files.Count() / div + 1).Select(x => files.Skip(x * div).Take(div).ToArray()).ToArray();
foreach (var items in li)
{
if (items.Count() == 0)
continue;
CombineMultipleFilesIntoSingleFile(items, outputName + index.ToString("0000") + ".ts");
//合并后删除这些文件
foreach (var item in items)
{
File.Delete(item);
}
index++;
}
}
/// <summary>
/// 输入一堆已存在的文件,合并到新文件
/// </summary>
/// <param name="files"></param>
/// <param name="outputFilePath"></param>
public static void CombineMultipleFilesIntoSingleFile(string[] files, string outputFilePath)
{
//同名文件已存在的共存策略
if (File.Exists(outputFilePath))
{
outputFilePath = Path.Combine(Path.GetDirectoryName(outputFilePath),
Path.GetFileNameWithoutExtension(outputFilePath) + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + Path.GetExtension(outputFilePath));
}
if (files.Length == 1)
{
FileInfo fi = new FileInfo(files[0]);
fi.MoveTo(outputFilePath);
return;
}
if (!Directory.Exists(Path.GetDirectoryName(outputFilePath)))
Directory.CreateDirectory(Path.GetDirectoryName(outputFilePath));
string[] inputFilePaths = files;
using (var outputStream = File.Create(outputFilePath))
{
foreach (var inputFilePath in inputFilePaths)
{
if (inputFilePath == "")
continue;
using (var inputStream = File.OpenRead(inputFilePath))
{
// Buffer size can be passed as the second argument.
inputStream.CopyTo(outputStream);
}
//Console.WriteLine("The file {0} has been processed.", inputFilePath);
}
}
//Global.ExplorerFile(outputFilePath);
}
/// <summary>
/// 将一个字节流附加至文件流
/// </summary>
/// <param name="liveStream"></param>
/// <param name="file"></param>
public static void AppendBytesToFileStreamAndDoNotClose(FileStream liveStream, byte[] file)
{
FileStream outputStream = liveStream;
using (var inputStream = new MemoryStream(file))
{
inputStream.CopyTo(outputStream);
}
}
//重定向
public static string Get302(string url, string headers = "", int timeout = 5000)
{
try
{
string redirectUrl;
WebRequest myRequest = WebRequest.Create(url);
myRequest.Timeout = timeout;
if (NoProxy)
{
myRequest.Proxy = null;
}
else if (UseProxyAddress != "")
{
SetProxy(myRequest);
}
//添加headers
if (headers != "")
{
foreach (string att in headers.Split('|'))
{
try
{
myRequest.Headers.Add(att);
}
catch (Exception)
{
}
}
}
WebResponse myResponse = myRequest.GetResponse();
redirectUrl = myResponse.ResponseUri.ToString();
myResponse.Close();
return redirectUrl;
}
catch (Exception) { return url; }
}
/// <summary>
/// 下载文件为字节流
/// </summary>
/// <param name="url"></param>
/// <param name="timeOut"></param>
/// <returns></returns>
public static byte[] HttpDownloadFileToBytes(string url, string headers = "", int timeOut = 60000)
{
//本地文件
if (url.StartsWith("file:"))
{
Uri t = new Uri(url);
url = t.LocalPath;
if (File.Exists(url))
{
FileStream fs = new FileStream(url, FileMode.Open, FileAccess.Read);
byte[] infbytes = new byte[(int)fs.Length];
fs.Read(infbytes, 0, infbytes.Length);
fs.Close();
return infbytes;
}
}
reProcess:
byte[] arraryByte;
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Method = "GET";
req.Timeout = timeOut;
req.ReadWriteTimeout = timeOut; //重要
req.AllowAutoRedirect = false; //手动处理重定向,否则会丢失Referer
if (NoProxy)
{
req.Proxy = null;
}
else if (UseProxyAddress != "")
{
SetProxy(req);
}
req.Headers.Add("Accept-Encoding", "gzip, deflate");
req.Accept = "*/*";
req.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36";
//添加headers
if (headers != "")
{
foreach (string att in headers.Split('|'))
{
try
{
if (att.Split(':')[0].ToLower() == "referer")
req.Referer = att.Substring(att.IndexOf(":") + 1);
else if (att.Split(':')[0].ToLower() == "user-agent")
req.UserAgent = att.Substring(att.IndexOf(":") + 1);
else if (att.Split(':')[0].ToLower() == "range")
req.AddRange(Convert.ToInt32(att.Substring(att.IndexOf(":") + 1).Split('-')[0], Convert.ToInt32(att.Substring(att.IndexOf(":") + 1).Split('-')[1])));
else if (att.Split(':')[0].ToLower() == "accept")
req.Accept = att.Substring(att.IndexOf(":") + 1);
else
req.Headers.Add(att);
}
catch (Exception e)
{
LOGGER.WriteLineError(e.Message);
}
}
}
using (HttpWebResponse wr = (HttpWebResponse)req.GetResponse())
{
//302
if (wr.Headers.Get("Location") != null)
{
url = wr.Headers.Get("Location");
wr.Close();
goto reProcess;
}
if (wr.ContentEncoding != null && wr.ContentEncoding.ToLower() == "gzip") //如果使用了GZip则先解压
{
using (Stream streamReceive = wr.GetResponseStream())
{
using (var zipStream =
new System.IO.Compression.GZipStream(streamReceive, System.IO.Compression.CompressionMode.Decompress))
{
//读取到内存
MemoryStream stmMemory = new MemoryStream();
Stream responseStream = zipStream;
byte[] bArr = new byte[1024];
int size = responseStream.Read(bArr, 0, (int)bArr.Length);
while (size > 0)
{
stmMemory.Write(bArr, 0, size);
size = responseStream.Read(bArr, 0, (int)bArr.Length);
}
arraryByte = stmMemory.ToArray();
responseStream.Close();
stmMemory.Close();
}
}
}
else
{
using (Stream streamReceive = wr.GetResponseStream())
{
//读取到内存
MemoryStream stmMemory = new MemoryStream();
Stream responseStream = streamReceive;
byte[] bArr = new byte[1024];
int size = responseStream.Read(bArr, 0, (int)bArr.Length);
while (size > 0)
{
stmMemory.Write(bArr, 0, size);
size = responseStream.Read(bArr, 0, (int)bArr.Length);
}
arraryByte = stmMemory.ToArray();
responseStream.Close();
stmMemory.Close();
}
}
}
return arraryByte;
}
/// <summary>
/// Http下载文件
/// </summary>
public static void HttpDownloadFile(string url, string path, int timeOut = 20000, string headers = "", long startByte = 0, long expectByte = -1)
{
int retry = 0;
reDownload:
try
{
if (File.Exists(path))
File.Delete(path);
if (shouldStop)
return;
reProcess:
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.Timeout = timeOut;
request.ReadWriteTimeout = timeOut; //重要
request.AllowAutoRedirect = false; //手动处理重定向,否则会丢失Referer
request.KeepAlive = false;
request.Method = "GET";
if (NoProxy)
{
request.Proxy = null;
}
else if (UseProxyAddress != "")
{
SetProxy(request);
}
if (url.Contains("data.video.iqiyi.com"))
request.UserAgent = "QYPlayer/Android/4.4.5;NetType/3G;QTP/1.1.4.3";
else if (url.Contains("pcvideo") && url.Contains(".titan.mgtv.com"))
{
request.UserAgent = "";
if (!url.Contains("/internettv/"))
request.Referer = "https://www.mgtv.com";
request.Headers.Add("Cookie", "MQGUID");
}
else if (url.Contains(".xboku.com/")) //独播库
{
request.Referer = "https://my.duboku.vip/static/player/videojs.html";
}
else
request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36";
//下载部分字节
if (expectByte != -1)
request.AddRange("bytes", startByte, startByte + expectByte - 1);
//添加headers
if (headers != "")
{
foreach (string att in headers.Split('|'))
{
try
{
if (att.Split(':')[0].ToLower() == "referer")
request.Referer = att.Substring(att.IndexOf(":") + 1);
else if (att.Split(':')[0].ToLower() == "user-agent")
request.UserAgent = att.Substring(att.IndexOf(":") + 1);
else if (att.Split(':')[0].ToLower() == "range")
request.AddRange(Convert.ToInt32(att.Substring(att.IndexOf(":") + 1).Split('-')[0], Convert.ToInt32(att.Substring(att.IndexOf(":") + 1).Split('-')[1])));
else if (att.Split(':')[0].ToLower() == "accept")
request.Accept = att.Substring(att.IndexOf(":") + 1);
else
request.Headers.Add(att);
}
catch (Exception e)
{
LOGGER.WriteLineError(e.Message);
}
}
}
long totalLen = 0;
long downLen = 0;
bool pngHeader = false; //PNG HEADER检测
using (var response = (HttpWebResponse)request.GetResponse())
{
//302
if (response.Headers.Get("Location") != null)
{
url = response.Headers.Get("Location");
response.Close();
goto reProcess;
}
using (var responseStream = response.GetResponseStream())
{
using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write))
{
//responseStream.CopyTo(stream);
totalLen = response.ContentLength;
byte[] bArr = new byte[1024];
int size = responseStream.Read(bArr, 0, (int)bArr.Length);
if (!pngHeader && size > 3 && 137 == bArr[0] && 80 == bArr[1] && 78 == bArr[2] && 71 == bArr[3])
{
pngHeader = true;
}
//GIF HEADER检测
if (!pngHeader && size > 3 && 0x47 == bArr[0] && 0x49 == bArr[1] && 0x46 == bArr[2] && 0x38 == bArr[3])
{
bArr = bArr.Skip(42).ToArray();
size -= 42;
downLen += 42;
}
//BMP HEADER检测
if (!pngHeader && size > 10 && 0x42 == bArr[0] && 0x4D == bArr[1] && 0x00 == bArr[5] && 0x00 == bArr[6] && 0x00 == bArr[7] && 0x00 == bArr[8])
{
bArr = bArr.Skip(0x3E).ToArray();
size -= 0x3E;
downLen += 0x3E;
}
while (size > 0)
{
stream.Write(bArr, 0, size);
downLen += size;
BYTEDOWN += size; //计算下载速度
if (MAX_SPEED != 0)
while (BYTEDOWN >= MAX_SPEED * 1024 * DownloadManager.CalcTime) //限速
{
Thread.Sleep(1);
}
size = responseStream.Read(bArr, 0, (int)bArr.Length);
if (shouldStop)
{
request.Abort();
break;
}
}
}
}
}
if (shouldStop)
try { File.Delete(path); } catch (Exception) { }
if (totalLen != -1 && downLen != totalLen)
try { File.Delete(path); } catch (Exception) { }
if (pngHeader)
TrySkipPngHeader(path);
}
catch (Exception e)
{
LOGGER.WriteLineError("DOWN: " + e.Message + " " + url);
try { File.Delete(path); } catch (Exception) { }
if (retry++ < 3)
{
Thread.Sleep(1000);
LOGGER.WriteLineError($"DOWN: AUTO RETRY {retry}/3 " + url);
goto reDownload;
}
}
}
/// <summary>
/// 用于处理利用图床上传TS导致前面被插入PNG Header的情况
/// </summary>
/// <param name="filePath"></param>
public static void TrySkipPngHeader(string filePath)
{
var u = File.ReadAllBytes(filePath);
if (0x47 == u[0])
{
return;
}
else if (u.Length > 120 && 137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[118] && 130 == u[119])
{
u = u.Skip(120).ToArray();
}
else if (u.Length > 6102 && 137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[6100] && 130 == u[6101])
{
u = u.Skip(6102).ToArray();
}
else if (u.Length > 69 && 137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[67] && 130 == u[68])
{
u = u.Skip(69).ToArray();
}
else if (u.Length > 771 && 137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3] && 96 == u[769] && 130 == u[770])
{
u = u.Skip(771).ToArray();
}
else if (u.Length > 4 && 137 == u[0] && 80 == u[1] && 78 == u[2] && 71 == u[3])
{
//确定是PNG但是需要手动查询结尾标记 0x47 出现两次
int skip = 0;
for (int i = 4; i < u.Length - 188 * 2 - 4; i++)
{
if (u[i] == 0x47 && u[i + 188] == 0x47 && u[i + 188 + 188] == 0x47)
{
skip = i;
break;
}
}
u = u.Skip(skip).ToArray();
}
File.WriteAllBytes(filePath, u);
}
//格式化json字符串
public static string ConvertJsonString(string str)
{
//Console.WriteLine(str);
JsonSerializer serializer = new JsonSerializer();
TextReader tr = new StringReader(str);
JsonTextReader jtr = new JsonTextReader(tr);
object obj = serializer.Deserialize(jtr);
if (obj != null)
{
StringWriter textWriter = new StringWriter();
JsonTextWriter jsonWriter = new JsonTextWriter(textWriter)
{
Formatting = Newtonsoft.Json.Formatting.Indented,
Indentation = 2,
IndentChar = ' '
}; //Indentation 为缩进量
serializer.Serialize(jsonWriter, obj);
return textWriter.ToString();
}
else
{
return str;
}
}
//获取属性
public static string GetTagAttribute(string attributeList, string key)
{
/*#EXT-X-STREAM-INF:PROGRAM-ID=1,RESOLUTION=1056x594,BANDWIDTH=1963351,CODECS="mp4a.40.5,avc1.4d001f",FRAME-RATE=30.000,AUDIO="aac",AVERAGE-BANDWIDTH=1655131*/
if (attributeList != "")
{
try
{
string tmp = attributeList.Trim();
if (tmp.Contains(key + "="))
{
if (tmp[tmp.IndexOf(key + "=") + key.Length + 1] == '\"')
{
return tmp.Substring(tmp.IndexOf(key + "=") + key.Length + 2, tmp.Remove(0, tmp.IndexOf(key + "=") + key.Length + 2).IndexOf('\"'));
}
else
{
if (tmp.Remove(0, tmp.IndexOf(key + "=") + key.Length + 2).Contains(","))
return tmp.Substring(tmp.IndexOf(key + "=") + key.Length + 1, tmp.Remove(0, tmp.IndexOf(key + "=") + key.Length + 1).IndexOf(','));
else
return tmp.Substring(tmp.IndexOf(key + "=") + key.Length + 1);
}
}
}
catch (Exception)
{
return string.Empty;
}
}
return string.Empty;
}
//正则表达式
public static ArrayList RegexFind(string regex, string src, int group = -1)
{
ArrayList array = new ArrayList();
Regex reg = new Regex(@regex);
MatchCollection result = reg.Matches(src);
if (result.Count == 0)
array.Add("NULL");
foreach (Match m in result)
{
if (group == -1)
array.Add(m.Value);
else
array.Add(m.Groups[group].Value);
}
return array;
}
//调用ffmpeg获取视频信息
public static ArrayList GetVideoInfo(string file)
{
LOGGER.WriteLine(strings.readingFileInfo);
LOGGER.PrintLine(strings.readingFileInfo, LOGGER.Warning);
StringBuilder sb = new StringBuilder();
ArrayList info = new ArrayList();
string cmd = "-hide_banner -i \"" + file + "\"";
if (!File.Exists(file))
{
info.Add("Error in reading file");
return info;
}
using (Process p = new Process())
{
p.StartInfo.FileName = "ffmpeg";
p.StartInfo.Arguments = cmd;
p.StartInfo.UseShellExecute = false; //是否使用操作系统shell启动
p.StartInfo.RedirectStandardInput = true; //接受来自调用程序的输入信息
p.StartInfo.RedirectStandardOutput = true; //由调用程序获取输出信息
p.StartInfo.RedirectStandardError = true; //重定向标准错误输出
p.StartInfo.CreateNoWindow = true; //不显示程序窗口
p.StartInfo.StandardErrorEncoding = Encoding.UTF8;
p.Start();//启动程序
p.StandardInput.AutoFlush = true;
//获取cmd窗口的输出信息
StreamReader reader = p.StandardError;//截取输出流
sb.Append(reader.ReadLine() + "\r\n");//每次读取一行
while (!reader.EndOfStream)
{
sb.Append(reader.ReadLine() + "\r\n");
}
p.WaitForExit();//等待程序执行完退出进程
p.Close();
}
string res = string.Empty;
foreach (string s in (string[])RegexFind("Stream #.*", sb.ToString()).ToArray(typeof(string)))
{
res = "PID "
+ RegexFind(@"\[(0x\d{2,})\]", s, 1)[0].ToString() + ": "
+ RegexFind(@": (.*)", s, 1)[0].ToString()
.Replace(RegexFind(@" \(\[.*?\)", s)[0].ToString(), "")
.Replace(": ", " ");
if (VIDEO_TYPE == "" && res.Contains(": Video"))
{
if (res.Contains("Video dvvideo")) //爱奇艺杜比视界
{
VIDEO_TYPE = "DV";
}
else if (res.Contains("Video none (dvhe")) //腾讯视频杜比视界
{
VIDEO_TYPE = "DV";
}
else if (res.Contains("Video hevc (dvhe")) //腾讯视频杜比视界
{
VIDEO_TYPE = "DV";
}
else if (res.Contains("Video hevc (DOVI")) //腾讯视频杜比视界
{
VIDEO_TYPE = "DV";
}
else if (res.Contains("Video hevc (Main 10) (DOVI")) //优酷视频杜比视界
{
VIDEO_TYPE = "DV";
}
else if (res.Contains("Video hevc (Main 10) (dvh1")) //优酷视频杜比视界
{
VIDEO_TYPE = "DV";
}
else if (res.Contains("Video hevc (dvh1")) //优酷视频杜比视界
{
VIDEO_TYPE = "DV";
}
else if (res.Contains("Video h264"))
{
VIDEO_TYPE = "H264";
}
else if (res.Contains("Video hevc"))
{
VIDEO_TYPE = "H265";
}
else
{
VIDEO_TYPE = "UNKOWN";
}
}
if (res.Contains("Audio aac"))
{
FFmpeg.UseAACFilter = true;
}
//有非AAC音轨则关闭UseAACFilter
if (res.Contains("Audio") && !res.Contains("Audio aac"))
{
FFmpeg.UseAACFilter = false;
}
if ((VIDEO_TYPE == "" || VIDEO_TYPE == "IGNORE") && res.Contains("Audio eac3"))
{
AUDIO_TYPE = "eac3";
}
else if((VIDEO_TYPE == "" || VIDEO_TYPE == "IGNORE") && res.Contains("Audio aac"))
{
AUDIO_TYPE = "aac";
}
else if ((VIDEO_TYPE == "" || VIDEO_TYPE == "IGNORE") && res.Contains("Audio ac3"))
{
AUDIO_TYPE = "ac3";
}
info.Add(res);
}
if (VIDEO_TYPE != "" && VIDEO_TYPE != "IGNORE")
AUDIO_TYPE = "";
return info;
}
//所给路径中所对应的文件大小
public static long FileSize(string filePath)
{
//定义一个FileInfo对象,是指与filePath所指向的文件相关联,以获取其大小
FileInfo fileInfo = new FileInfo(filePath);
return fileInfo.Length;
}
//获取文件夹大小
public static long GetDirectoryLength(string path)
{
if (!Directory.Exists(path))
{
return 0;
}
long size = 0;
//遍历指定路径下的所有文件
DirectoryInfo di = new DirectoryInfo(path);
foreach (FileInfo fi in di.GetFiles())
{
size += fi.Length;
}
//遍历指定路径下的所有文件夹
DirectoryInfo[] dis = di.GetDirectories();
if (dis.Length > 0)
{
for (int i = 0; i < dis.Length; i++)
{
size += GetDirectoryLength(dis[i].FullName);
}
}
return size;
}
//此函数用于格式化输出时长
public static String FormatTime(Int32 time)
{
TimeSpan ts = new TimeSpan(0, 0, time);
string str = "";
str = (ts.Hours.ToString("00") == "00" ? "" : ts.Hours.ToString("00") + "h") + ts.Minutes.ToString("00") + "m" + ts.Seconds.ToString("00") + "s";
return str;
}
//此函数用于格式化输出文件大小
public static String FormatFileSize(Double fileSize)
{
if (fileSize < 0)
{
return "Error";
}
else if (fileSize >= 1024 * 1024 * 1024)
{
return string.Format("{0:########0.00} GB", ((Double)fileSize) / (1024 * 1024 * 1024));
}
else if (fileSize >= 1024 * 1024)
{
return string.Format("{0:####0.00} MB", ((Double)fileSize) / (1024 * 1024));
}
else if (fileSize >= 1024)
{
return string.Format("{0:####0.00} KB", ((Double)fileSize) / 1024);
}
else
{
return string.Format("{0} bytes", fileSize);
}
}
/// <summary>
/// 获取当前时间戳
/// </summary>
/// <param name="bflag">为真时获取10位时间戳,为假时获取13位时间戳.bool bflag = true</param>
/// <returns></returns>
public static string GetTimeStamp(bool bflag)
{
TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
string ret = string.Empty;
if (bflag)
ret = Convert.ToInt64(ts.TotalSeconds).ToString();
else
ret = Convert.ToInt64(ts.TotalMilliseconds).ToString();
return ret;
}
/// <summary>
/// 获取有效文件名
/// </summary>
/// <param name="text"></param>
/// <param name="replacement"></param>
/// <returns></returns>
public static string MakeValidFileName(string text, string replacement = "_")
{
StringBuilder str = new StringBuilder();
var invalidFileNameChars = Path.GetInvalidFileNameChars();
foreach (var c in text)
{
if (invalidFileNameChars.Contains(c))
{
str.Append(replacement ?? "");
}
else
{
str.Append(c);
}
}
return str.ToString();
}
/// <summary>
/// 从URL获取文件名
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public static string GetUrlFileName(string url)
{
if (File.Exists(url))
{
return Path.GetFileNameWithoutExtension(url);
}
if (string.IsNullOrEmpty(url))
{
return "None";
}
try
{
string[] strs1 = url.Split(new char[] { '/' });
return MakeValidFileName(System.Web.HttpUtility.UrlDecode(strs1[strs1.Length - 1].Split(new char[] { '?' })[0].Replace(".m3u8", "")));
}
catch (Exception)
{
return DateTime.Now.ToString("yyyy.MM.dd-HH.mm.ss");
}
}
//检测GZip并解压
public static void GzipHandler(string file)
{
try
{
using (FileStream fr = File.OpenRead(file))
{
using (GZipStream gz = new GZipStream(fr, CompressionMode.Decompress))
{
using (FileStream fw = File.OpenWrite(Path.Combine(Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(file) + "[t].ts")))
{
byte[] by = new byte[1024];
int r = gz.Read(by, 0, by.Length);
while (r > 0)
{
fw.Write(by, 0, r);
r = gz.Read(by, 0, r);
}
}
}
File.Delete(file);
File.Move(Path.Combine(Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(file) + "[t].ts"), file);
}
}
catch (Exception)
{
if (File.Exists(Path.Combine(Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(file) + "[t].ts")))
File.Delete(Path.Combine(Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(file) + "[t].ts"));
return;
}
}
[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);
//使用Win32 API解析字符串为命令行参数
public static IEnumerable<string> ParseArguments(string commandLine)
{
int argc;
var argv = CommandLineToArgvW(commandLine, out argc);
if (argv == IntPtr.Zero)
throw new System.ComponentModel.Win32Exception();
try
{
var args = new string[argc];
for (var i = 0; i < args.Length; i++)
{
var p = Marshal.ReadIntPtr(argv, i * IntPtr.Size);
args[i] = Marshal.PtrToStringUni(p);
}
return args;
}
finally
{
Marshal.FreeHGlobal(argv);
}
}
//重载
public class WebClientEx : WebClient
{
private readonly long from;
private readonly long to;
private readonly int timeout;
private readonly bool setTimeout;
private readonly bool setRange;
public WebClientEx()
{
}
public WebClientEx(long from, long to)
{
this.from = from;
this.to = to;
setRange = true;
}
public WebClientEx(int timeout)
{
this.timeout = timeout;
setTimeout = true;
}
public WebClientEx(int timeout, long from, long to)
{
this.timeout = timeout;
setTimeout = true;
this.from = from;
this.to = to;
setRange = true;
}
protected override WebRequest GetWebRequest(Uri address)
{
var wr = (HttpWebRequest)base.GetWebRequest(address);
if (NoProxy)
{
wr.Proxy = null;
}
else if (UseProxyAddress != "")
{
SetProxy(wr);
}
if (setRange)
wr.AddRange(this.from, this.to);
if (setTimeout)
wr.Timeout = timeout; // timeout in milliseconds (ms)
return wr;
}
}
/**
* 通过X-TIMESTAMP-MAP 调整VTT字幕的时间轴
*/
public static void ReAdjustVtt(string[] vtts)
{
string MsToTime(int ms)
{
TimeSpan ts = new TimeSpan(0, 0, 0, 0, ms);
string str = "";
str = (ts.Hours.ToString("00") + ":") + ts.Minutes.ToString("00") + ":" + ts.Seconds.ToString("00") + "." + ts.Milliseconds.ToString("000");
return str;
}
int TimeToMs(string line)
{
int hh = Convert.ToInt32(line.Split(':')[0]);
int mm = Convert.ToInt32(line.Split(':')[1]);
int ss = Convert.ToInt32(line.Split(':')[2].Split('.')[0]);
int ms = Convert.ToInt32(line.Split(':')[2].Split('.')[1]);
return hh * 60 * 60 * 1000 + mm * 60 * 1000 + ss * 1000 + ms;
}
int addTime = 0;
int baseTime = 0;
for (int i = 0; i < vtts.Length; i++)
{
string tmp = File.ReadAllText(vtts[i], Encoding.UTF8);
if (!Regex.IsMatch(tmp, "X-TIMESTAMP-MAP.*MPEGTS:(\\d+)"))
break;
if (i > 0)
{
int newTime = Convert.ToInt32(Regex.Match(tmp, "X-TIMESTAMP-MAP.*MPEGTS:(\\d+)").Groups[1].Value);
if (newTime == 900000)
continue;
//计算偏移量
//LOGGER.PrintLine((newTime - baseTime).ToString());
addTime = addTime + ((newTime - baseTime) / 100);
if ((newTime - baseTime) == 6300000)
addTime -= 3000;
//将新的作为基准时间
baseTime = newTime;
foreach (Match m in Regex.Matches(tmp, @"(\d{2}:\d{2}:\d{2}\.\d{3}) --> (\d{2}:\d{2}:\d{2}\.\d{3})"))
{
string start = m.Groups[1].Value;
string end = m.Groups[2].Value;
tmp = tmp.Replace(m.Value, MsToTime(TimeToMs(start) + addTime) + " --> " + MsToTime(TimeToMs(end) + addTime));
}
}
File.WriteAllText(vtts[i], Regex.Replace(tmp, "X-TIMESTAMP-MAP=.*", ""), Encoding.UTF8);
}
//Console.ReadLine();
}
}
}
================================================
FILE: N_m3u8DL-CLI/HLSLiveDownloader.cs
================================================
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 = Math.Abs(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.Headers = Headers;
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;//超时时间不超过下次执行时间
if (sd.TimeOut <= 0) sd.TimeOut = (int)timer.Interval;
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;
}
}
}
================================================
FILE: N_m3u8DL-CLI/HLSTags.cs
================================================
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";
}
}
================================================
FILE: N_m3u8DL-CLI/IqJsonParser.cs
================================================
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();
}
}
}
================================================
FILE: N_m3u8DL-CLI/LOGGER.cs
================================================
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();
}
}
}
================================================
FILE: N_m3u8DL-CLI/MPDParser.cs
================================================
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
namespace N_m3u8DL_CLI
{
//code from https://github.com/ytdl-org/youtube-dl/blob/master/youtube_dl/extractor/common.py#L2076
class MPDParser
{
private static string MPD_URL;
static Dictionary<string, dynamic> ExtractMultisegmentInfo(XmlElement Period, XmlNamespaceManager nsMgr, Dictionary<string, dynamic> info)
{
var MultisegmentInfo = new Dictionary<string, dynamic>(info);
void ExtractCommon(XmlNode source)
{
var sourceE = (XmlElement)source;
var segmentTimeline = source.SelectSingleNode("ns:SegmentTimeline", nsMgr);
if (segmentTimeline != null)
{
var sE = segmentTimeline.SelectNodes("ns:S", nsMgr);
if (sE.Count > 0)
{
MultisegmentInfo["TotalNumber"] = 0;
var SList = new List<Dictionary<string, dynamic>>();
foreach (XmlElement
gitextract_idemaxt1/
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ └── build_latest.yml
├── .gitignore
├── LICENSE
├── N_m3u8DL-CLI/
│ ├── App.config
│ ├── CSChaCha20.cs
│ ├── Decode51CtoKey.cs
│ ├── DecodeCdeledu.cs
│ ├── DecodeDdyun.cs
│ ├── DecodeHuke88Key.cs
│ ├── DecodeImooc.cs
│ ├── DecodeNfmovies.cs
│ ├── Decrypter.cs
│ ├── DownloadManager.cs
│ ├── Downloader.cs
│ ├── FFmpeg.cs
│ ├── Global.cs
│ ├── HLSLiveDownloader.cs
│ ├── HLSTags.cs
│ ├── IqJsonParser.cs
│ ├── LOGGER.cs
│ ├── MPDParser.cs
│ ├── MyOptions.cs
│ ├── N_m3u8DL-CLI.csproj
│ ├── Parser.cs
│ ├── Program.cs
│ ├── ProgressReporter.cs
│ ├── Properties/
│ │ └── AssemblyInfo.cs
│ ├── Watcher.cs
│ ├── changelog.txt
│ ├── packages.config
│ ├── strings.Designer.cs
│ ├── strings.en-US.Designer.cs
│ ├── strings.en-US.resx
│ ├── strings.resx
│ ├── strings.zh-TW.Designer.cs
│ └── strings.zh-TW.resx
├── N_m3u8DL-CLI.sln
├── README.md
├── README_ENG.md
└── docs/
├── Advanced.html
├── GetM3u8.html
├── Introductory.html
├── M3U8URL2File.html
├── SimpleGUI.html
├── gitbook/
│ ├── fonts/
│ │ └── fontawesome/
│ │ └── FontAwesome.otf
│ ├── gitbook-plugin-donate/
│ │ ├── plugin.css
│ │ └── plugin.js
│ ├── gitbook-plugin-fontsettings/
│ │ ├── fontsettings.js
│ │ └── website.css
│ ├── gitbook-plugin-github/
│ │ └── plugin.js
│ ├── gitbook-plugin-github-buttons/
│ │ └── plugin.js
│ ├── gitbook-plugin-highlight/
│ │ ├── ebook.css
│ │ └── website.css
│ ├── gitbook-plugin-lunr/
│ │ └── search-lunr.js
│ ├── gitbook-plugin-search/
│ │ ├── search-engine.js
│ │ ├── search.css
│ │ └── search.js
│ ├── gitbook-plugin-sharing-plus/
│ │ └── buttons.js
│ ├── gitbook.js
│ ├── style.css
│ └── theme.js
├── index.html
└── search_index.json
SYMBOL INDEX (457 symbols across 32 files)
FILE: N_m3u8DL-CLI/CSChaCha20.cs
class ChaCha20 (line 29) | public sealed class ChaCha20 : IDisposable
method ChaCha20 (line 73) | public ChaCha20(byte[] key, byte[] nonce, uint counter)
method ChaCha20 (line 90) | public ChaCha20(ReadOnlySpan<byte> key, ReadOnlySpan<byte> nonce, uint...
method KeySetup (line 121) | private void KeySetup(byte[] key)
method IvSetup (line 161) | private void IvSetup(byte[] nonce, uint counter)
method EncryptBytes (line 193) | public void EncryptBytes(byte[] output, byte[] input, int numBytes)
method EncryptStream (line 204) | public void EncryptStream(Stream output, Stream input, int howManyByte...
method EncryptStreamAsync (line 215) | public async Task EncryptStreamAsync(Stream output, Stream input, int ...
method EncryptBytes (line 226) | public void EncryptBytes(byte[] output, byte[] input)
method EncryptBytes (line 238) | public byte[] EncryptBytes(byte[] input, int numBytes)
method EncryptBytes (line 251) | public byte[] EncryptBytes(byte[] input)
method EncryptString (line 264) | public byte[] EncryptString(string input)
method DecryptBytes (line 285) | public void DecryptBytes(byte[] output, byte[] input, int numBytes)
method DecryptStream (line 296) | public void DecryptStream(Stream output, Stream input, int howManyByte...
method DecryptStreamAsync (line 307) | public async Task DecryptStreamAsync(Stream output, Stream input, int ...
method DecryptBytes (line 318) | public void DecryptBytes(byte[] output, byte[] input)
method DecryptBytes (line 330) | public byte[] DecryptBytes(byte[] input, int numBytes)
method DecryptBytes (line 343) | public byte[] DecryptBytes(byte[] input)
method DecryptUTF8ByteArray (line 356) | public string DecryptUTF8ByteArray(byte[] input)
method WorkStreams (line 366) | private void WorkStreams(Stream output, Stream input, int howManyBytes...
method WorkStreamsAsync (line 383) | private async Task WorkStreamsAsync(Stream output, Stream input, int h...
method WorkBytes (line 408) | private void WorkBytes(byte[] output, byte[] input, int numBytes)
method QuarterRound (line 502) | private static void QuarterRound(uint[] x, uint a, uint b, uint c, uin...
method Dispose (line 530) | public void Dispose()
method Dispose (line 545) | private void Dispose(bool disposing)
class Util (line 567) | public static class Util
method Rotate (line 575) | [MethodImpl(MethodImplOptions.AggressiveInlining)]
method XOr (line 590) | [MethodImpl(MethodImplOptions.AggressiveInlining)]
method Add (line 605) | [MethodImpl(MethodImplOptions.AggressiveInlining)]
method AddOne (line 619) | [MethodImpl(MethodImplOptions.AggressiveInlining)]
method U8To32Little (line 631) | [MethodImpl(MethodImplOptions.AggressiveInlining)]
method ToBytes (line 649) | [MethodImpl(MethodImplOptions.AggressiveInlining)]
FILE: N_m3u8DL-CLI/Decode51CtoKey.cs
class Decode51CtoKey (line 13) | class Decode51CtoKey
method MD5Encoding (line 290) | private static string MD5Encoding(string rawPass)
method GetDecodeKey (line 303) | public static string GetDecodeKey(string encodeKey, string lid)
method GetSign (line 312) | public static string GetSign(string lid)
FILE: N_m3u8DL-CLI/DecodeCdeledu.cs
class DecodeCdeledu (line 9) | internal class DecodeCdeledu
method DecodeKey (line 73) | public static string DecodeKey(string txt)
FILE: N_m3u8DL-CLI/DecodeDdyun.cs
class DecodeDdyun (line 7) | class DecodeDdyun
method DecryptM3u8 (line 9) | public static string DecryptM3u8(byte[] byteArray)
method GetVaildM3u8Url (line 22) | public static string GetVaildM3u8Url(string url)
FILE: N_m3u8DL-CLI/DecodeHuke88Key.cs
class DecodeHuke88Key (line 12) | class DecodeHuke88Key
method GetOverlayInfo (line 14) | private static string[] GetOverlayInfo(string url)
method DecodeKey (line 24) | public static string DecodeKey(string url, byte[] data)
FILE: N_m3u8DL-CLI/DecodeImooc.cs
class DecodeImooc (line 13) | class DecodeImooc
method DecodeM3u8 (line 183) | public static string DecodeM3u8(string resp)
method DecodeKey (line 192) | public static string DecodeKey(string resp)
FILE: N_m3u8DL-CLI/DecodeNfmovies.cs
class DecodeNfmovies (line 8) | class DecodeNfmovies
method DecryptM3u8 (line 11) | public static string DecryptM3u8(byte[] byteArray)
FILE: N_m3u8DL-CLI/Decrypter.cs
class Decrypter (line 9) | class Decrypter
method AES128Decrypt (line 11) | public static byte[] AES128Decrypt(string filePath, byte[] keyByte, by...
method AES128Decrypt (line 33) | public static byte[] AES128Decrypt(byte[] encryptedBuff, byte[] keyByt...
method CHACHA20Decrypt (line 50) | public static byte[] CHACHA20Decrypt(byte[] encryptedBuff, byte[] keyB...
method HexStringToBytes (line 84) | public static byte[] HexStringToBytes(string hexStr)
FILE: N_m3u8DL-CLI/DownloadManager.cs
class DownloadManager (line 12) | class DownloadManager
method DownloadManager (line 52) | public DownloadManager()
method DoDownload (line 92) | public void DoDownload()
method IsComplete (line 322) | public void IsComplete(int segCount)
FILE: N_m3u8DL-CLI/Downloader.cs
class Downloader (line 13) | class Downloader
method Down (line 70) | public void Down()
FILE: N_m3u8DL-CLI/FFmpeg.cs
class FFmpeg (line 11) | class FFmpeg
method Merge (line 21) | public static void Merge(string[] files, string muxFormat, bool fastSt...
method ConvertToMPEGTS (line 92) | public static void ConvertToMPEGTS(string file)
method Run (line 124) | public static void Run(string path, string args, string workDir)
method Output (line 147) | private static void Output(object sendProcess, DataReceivedEventArgs o...
method CheckMPEGTS (line 155) | public static bool CheckMPEGTS(string file)
FILE: N_m3u8DL-CLI/Global.cs
class Global (line 18) | class Global
method WriteInit (line 39) | public static void WriteInit()
method CheckUpdate (line 46) | public static void CheckUpdate()
method GetValidFileName (line 82) | public static string GetValidFileName(string input, string re = ".")
method GetNum (line 93) | public static int GetNum(string str, int numBase)
method SetProxy (line 103) | public static void SetProxy(WebRequest webRequest)
method GetWebSource (line 142) | public static string GetWebSource(String url, string headers = "", int...
method ILFree (line 272) | [DllImport("shell32.dll", ExactSpelling = true)]
method ILCreateFromPathW (line 274) | [DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = t...
method SHOpenFolderAndSelectItems (line 276) | [DllImport("shell32.dll", ExactSpelling = true)]
method GetFileCount (line 282) | public static int GetFileCount(string dir, string ext)
method GetFiles (line 305) | public static string[] GetFiles(string dir, string ext)
method GetQueryString (line 328) | public static string GetQueryString(string name, string url)
method PartialCombineMultipleFiles (line 343) | public static void PartialCombineMultipleFiles(string[] files)
method CombineMultipleFilesIntoSingleFile (line 375) | public static void CombineMultipleFilesIntoSingleFile(string[] files, ...
method AppendBytesToFileStreamAndDoNotClose (line 418) | public static void AppendBytesToFileStreamAndDoNotClose(FileStream liv...
method Get302 (line 428) | public static string Get302(string url, string headers = "", int timeo...
method HttpDownloadFileToBytes (line 472) | public static byte[] HttpDownloadFileToBytes(string url, string header...
method HttpDownloadFile (line 592) | public static void HttpDownloadFile(string url, string path, int timeO...
method TrySkipPngHeader (line 744) | public static void TrySkipPngHeader(string filePath)
method ConvertJsonString (line 786) | public static string ConvertJsonString(string str)
method GetTagAttribute (line 812) | public static string GetTagAttribute(string attributeList, string key)
method RegexFind (line 844) | public static ArrayList RegexFind(string regex, string src, int group ...
method GetVideoInfo (line 862) | public static ArrayList GetVideoInfo(string file)
method FileSize (line 984) | public static long FileSize(string filePath)
method GetDirectoryLength (line 992) | public static long GetDirectoryLength(string path)
method FormatTime (line 1018) | public static String FormatTime(Int32 time)
method FormatFileSize (line 1027) | public static String FormatFileSize(Double fileSize)
method GetTimeStamp (line 1056) | public static string GetTimeStamp(bool bflag)
method MakeValidFileName (line 1074) | public static string MakeValidFileName(string text, string replacement...
method GetUrlFileName (line 1097) | public static string GetUrlFileName(string url)
method GzipHandler (line 1119) | public static void GzipHandler(string file)
method CommandLineToArgvW (line 1150) | [DllImport("shell32.dll", SetLastError = true)]
method ParseArguments (line 1153) | public static IEnumerable<string> ParseArguments(string commandLine)
class WebClientEx (line 1177) | public class WebClientEx : WebClient
method WebClientEx (line 1185) | public WebClientEx()
method WebClientEx (line 1190) | public WebClientEx(long from, long to)
method WebClientEx (line 1197) | public WebClientEx(int timeout)
method WebClientEx (line 1203) | public WebClientEx(int timeout, long from, long to)
method GetWebRequest (line 1212) | protected override WebRequest GetWebRequest(Uri address)
method ReAdjustVtt (line 1234) | public static void ReAdjustVtt(string[] vtts)
FILE: N_m3u8DL-CLI/HLSLiveDownloader.cs
class HLSLiveDownloader (line 13) | class HLSLiveDownloader
method TimerStart (line 35) | public void TimerStart()
method TimerStop (line 45) | public void TimerStop()
method UpdateList (line 51) | private void UpdateList(object source, EventArgs e)
method Record (line 118) | private void Record()
method isNewSeg (line 149) | private bool isNewSeg()
FILE: N_m3u8DL-CLI/HLSTags.cs
class HLSTags (line 9) | class HLSTags
FILE: N_m3u8DL-CLI/IqJsonParser.cs
class IqJsonParser (line 11) | class IqJsonParser
method Parse (line 13) | public static string Parse(string downDir, string json)
FILE: N_m3u8DL-CLI/LOGGER.cs
class LOGGER (line 12) | class LOGGER
method FindLog (line 20) | public static string FindLog(string dir)
method InitLog (line 33) | public static void InitLog()
method PrintLine (line 63) | public static void PrintLine(string text, int printLevel = 1)
method WriteLine (line 102) | public static void WriteLine(string text)
method WriteLineError (line 130) | public static void WriteLineError(string text)
method Show (line 155) | public static void Show(string text)
FILE: N_m3u8DL-CLI/MPDParser.cs
class MPDParser (line 16) | class MPDParser
method ExtractMultisegmentInfo (line 19) | static Dictionary<string, dynamic> ExtractMultisegmentInfo(XmlElement ...
method Parse (line 130) | public static string Parse(string downDir, string mpdUrl, string mpdCo...
method GenerateMasterList (line 624) | static string GenerateMasterList(string downDir, params Dictionary<str...
method GenerateM3u8 (line 680) | static string GenerateM3u8(Dictionary<string, dynamic> f)
method SelectBestVideo (line 787) | static Dictionary<string, dynamic> SelectBestVideo(List<Dictionary<str...
method SelectBestAudio (line 811) | static Dictionary<string, dynamic> SelectBestAudio(List<Dictionary<str...
method IntOrNull (line 831) | static int IntOrNull(object text, int scale = 1)
method DoubleOrNull (line 843) | static double DoubleOrNull(object text, int scale = 1)
method CombineURL (line 861) | static string CombineURL(string baseurl, string url)
FILE: N_m3u8DL-CLI/MyOptions.cs
class MyOptions (line 11) | internal class MyOptions
FILE: N_m3u8DL-CLI/Parser.cs
class Parser (line 12) | class Parser
type Audio (line 14) | struct Audio
method ToString (line 20) | public override string ToString()
type Subtitle (line 26) | struct Subtitle
method ToString (line 31) | public override string ToString()
method Parse (line 74) | public void Parse()
method ParseKey (line 810) | public string[] ParseKey(string line)
method MasterListCheck (line 914) | public void MasterListCheck()
method ForceCanonicalPathAndQuery (line 953) | private void ForceCanonicalPathAndQuery(Uri uri)
method CombineURL (line 968) | public string CombineURL(string baseurl, string url)
method GetBaseUrl (line 1006) | public static string GetBaseUrl(string m3u8url, string headers)
FILE: N_m3u8DL-CLI/Program.cs
class Program (line 23) | class Program
method SetConsoleCtrlHandler (line 26) | [DllImport("kernel32.dll")]
method HandlerRoutine (line 29) | public static bool HandlerRoutine(int CtrlType)
method ValidateServerCertificate (line 45) | private static bool ValidateServerCertificate(object sender, X509Certi...
method Main (line 51) | static void Main(string[] args)
method DoWork (line 200) | private static void DoWork(MyOptions o)
method RegisterUriScheme (line 458) | public static bool RegisterUriScheme(string scheme, string application...
method UnregisterUriScheme (line 487) | public static bool UnregisterUriScheme(string scheme)
method RequireElevated (line 502) | public static void RequireElevated(string cmd)
method DisplayHelp (line 514) | private static void DisplayHelp(ParserResult<MyOptions> result, IEnume...
FILE: N_m3u8DL-CLI/ProgressReporter.cs
class ProgressReporter (line 9) | class ProgressReporter
method Report (line 15) | public static void Report(string progress, string speed)
FILE: N_m3u8DL-CLI/Watcher.cs
class Watcher (line 11) | class Watcher
method Watcher (line 25) | public Watcher(string Dir)
method WatcherStrat (line 30) | public void WatcherStrat()
method WatcherStop (line 45) | public void WatcherStop()
method OnCreated (line 50) | private void OnCreated(object source, FileSystemEventArgs e)
method OnRenamed (line 70) | private void OnRenamed(object source, RenamedEventArgs e)
method OnDeleted (line 90) | private void OnDeleted(object source, FileSystemEventArgs e)
FILE: N_m3u8DL-CLI/strings.Designer.cs
class strings (line 22) | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resource...
method strings (line 31) | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Mic...
FILE: docs/gitbook/gitbook-plugin-donate/plugin.js
function insertDonateLink (line 9) | function insertDonateLink() {
FILE: docs/gitbook/gitbook-plugin-fontsettings/fontsettings.js
function getThemes (line 44) | function getThemes() {
function setThemes (line 49) | function setThemes(themes) {
function getFamilies (line 55) | function getFamilies() {
function setFamilies (line 60) | function setFamilies(families) {
function saveFontSettings (line 66) | function saveFontSettings() {
function enlargeFontSize (line 72) | function enlargeFontSize(e) {
function reduceFontSize (line 81) | function reduceFontSize(e) {
function changeFontFamily (line 90) | function changeFontFamily(configName, e) {
function changeColorTheme (line 101) | function changeColorTheme(configName, e) {
function getFontFamilyId (line 123) | function getFontFamilyId(configName) {
function getThemeId (line 134) | function getThemeId(configName) {
function update (line 143) | function update() {
function init (line 159) | function init(config) {
function updateButtons (line 174) | function updateButtons() {
FILE: docs/gitbook/gitbook-plugin-github-buttons/plugin.js
function addBeforeHeader (line 4) | function addBeforeHeader(element) {
function createButton (line 8) | function createButton(_ref) {
function createUserButton (line 21) | function createUserButton(_ref2) {
function insertGitHubLink (line 31) | function insertGitHubLink(button) {
function init (line 67) | function init(config) {
function getPluginConfig (line 72) | function getPluginConfig() {
FILE: docs/gitbook/gitbook-plugin-lunr/search-lunr.js
function LunrSearchEngine (line 6) | function LunrSearchEngine() {
FILE: docs/gitbook/gitbook-plugin-search/search-engine.js
function setEngine (line 10) | function setEngine(Engine, config) {
function init (line 18) | function init(config) {
function query (line 29) | function query(q, offset, length) {
function getEngine (line 35) | function getEngine() {
function isInitialized (line 39) | function isInitialized() {
FILE: docs/gitbook/gitbook-plugin-search/search.js
function throttle (line 20) | function throttle(fn, wait) {
function displayResults (line 34) | function displayResults(res) {
function launchSearch (line 73) | function launchSearch(q) {
function closeSearch (line 88) | function closeSearch() {
function launchSearchFromQueryString (line 93) | function launchSearchFromQueryString() {
function bindSearch (line 104) | function bindSearch() {
function getParameterByName (line 172) | function getParameterByName(name) {
function updateQueryString (line 182) | function updateQueryString(key, value) {
FILE: docs/gitbook/gitbook-plugin-sharing-plus/buttons.js
function site (line 2) | function site(label, icon, link) {
FILE: docs/gitbook/gitbook.js
function o (line 1) | function o(s,a){if(!n[s]){if(!t[s]){var u="function"==typeof require&&re...
function n (line 1) | function n(e,t){t=t||te;var n=t.createElement("script");n.text=e,t.head....
function r (line 1) | function r(e){var t=!!e&&"length"in e&&e.length,n=de.type(e);return"func...
function o (line 1) | function o(e,t,n){return de.isFunction(t)?de.grep(e,function(e,r){return...
function i (line 1) | function i(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}
function s (line 1) | function s(e){var t={};return de.each(e.match(qe)||[],function(e,n){t[n]...
function a (line 1) | function a(e){return e}
function u (line 1) | function u(e){throw e}
function c (line 1) | function c(e,t,n){var r;try{e&&de.isFunction(r=e.promise)?r.call(e).done...
function l (line 1) | function l(){te.removeEventListener("DOMContentLoaded",l),e.removeEventL...
function f (line 1) | function f(){this.expando=de.expando+f.uid++}
function p (line 1) | function p(e){return"true"===e||"false"!==e&&("null"===e?null:e===+e+""?...
function h (line 1) | function h(e,t,n){var r;if(void 0===n&&1===e.nodeType)if(r="data-"+t.rep...
function d (line 1) | function d(e,t,n,r){var o,i=1,s=20,a=r?function(){return r.cur()}:functi...
function g (line 1) | function g(e){var t,n=e.ownerDocument,r=e.nodeName,o=Ue[r];return o?o:(t...
function m (line 1) | function m(e,t){for(var n,r,o=[],i=0,s=e.length;i<s;i++)r=e[i],r.style&&...
function v (line 1) | function v(e,t){var n;return n="undefined"!=typeof e.getElementsByTagNam...
function y (line 1) | function y(e,t){for(var n=0,r=e.length;n<r;n++)Fe.set(e[n],"globalEval",...
function x (line 1) | function x(e,t,n,r,o){for(var i,s,a,u,c,l,f=t.createDocumentFragment(),p...
function b (line 1) | function b(){return!0}
function w (line 1) | function w(){return!1}
function T (line 1) | function T(){try{return te.activeElement}catch(e){}}
function C (line 1) | function C(e,t,n,r,o,i){var s,a;if("object"==typeof t){"string"!=typeof ...
function j (line 1) | function j(e,t){return de.nodeName(e,"table")&&de.nodeName(11!==t.nodeTy...
function k (line 1) | function k(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}
function E (line 1) | function E(e){var t=rt.exec(e.type);return t?e.type=t[1]:e.removeAttribu...
function S (line 1) | function S(e,t){var n,r,o,i,s,a,u,c;if(1===t.nodeType){if(Fe.hasData(e)&...
function N (line 1) | function N(e,t){var n=t.nodeName.toLowerCase();"input"===n&&ze.test(e.ty...
function A (line 1) | function A(e,t,r,o){t=oe.apply([],t);var i,s,a,u,c,l,f=0,p=e.length,h=p-...
function q (line 1) | function q(e,t,n){for(var r,o=t?de.filter(t,e):e,i=0;null!=(r=o[i]);i++)...
function D (line 1) | function D(e,t,n){var r,o,i,s,a=e.style;return n=n||at(e),n&&(s=n.getPro...
function O (line 1) | function O(e,t){return{get:function(){return e()?void delete this.get:(t...
function L (line 1) | function L(e){if(e in pt)return e;for(var t=e[0].toUpperCase()+e.slice(1...
function H (line 1) | function H(e,t,n){var r=$e.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3...
function F (line 1) | function F(e,t,n,r,o){var i,s=0;for(i=n===(r?"border":"content")?4:"widt...
function R (line 1) | function R(e,t,n){var r,o=!0,i=at(e),s="border-box"===de.css(e,"boxSizin...
function I (line 1) | function I(e,t,n,r,o){return new I.prototype.init(e,t,n,r,o)}
function P (line 1) | function P(){dt&&(e.requestAnimationFrame(P),de.fx.tick())}
function M (line 1) | function M(){return e.setTimeout(function(){ht=void 0}),ht=de.now()}
function $ (line 1) | function $(e,t){var n,r=0,o={height:e};for(t=t?1:0;r<4;r+=2-t)n=We[r],o[...
function W (line 1) | function W(e,t,n){for(var r,o=(U.tweeners[t]||[]).concat(U.tweeners["*"]...
function B (line 1) | function B(e,t,n){var r,o,i,s,a,u,c,l,f="width"in t||"height"in t,p=this...
function _ (line 1) | function _(e,t){var n,r,o,i,s;for(n in e)if(r=de.camelCase(n),o=t[r],i=e...
function U (line 1) | function U(e,t,n){var r,o,i=0,s=U.prefilters.length,a=de.Deferred().alwa...
function z (line 1) | function z(e){var t=e.match(qe)||[];return t.join(" ")}
function X (line 1) | function X(e){return e.getAttribute&&e.getAttribute("class")||""}
function V (line 1) | function V(e,t,n,r){var o;if(de.isArray(t))de.each(t,function(t,o){n||Et...
function G (line 1) | function G(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r...
function Y (line 1) | function Y(e,t,n,r){function o(a){var u;return i[a]=!0,de.each(e[a]||[],...
function Q (line 1) | function Q(e,t){var n,r,o=de.ajaxSettings.flatOptions||{};for(n in t)voi...
function J (line 1) | function J(e,t,n){for(var r,o,i,s,a=e.contents,u=e.dataTypes;"*"===u[0];...
function K (line 1) | function K(e,t,n,r){var o,i,s,a,u,c={},l=e.dataTypes.slice();if(l[1])for...
function Z (line 1) | function Z(e){return de.isWindow(e)?e:9===e.nodeType&&e.defaultView}
function t (line 1) | function t(e,t,n,r){var o,i,s,a,u,c,l,p=t&&t.ownerDocument,d=t?t.nodeTyp...
function n (line 1) | function n(){function e(n,r){return t.push(n+" ")>C.cacheLength&&delete ...
function r (line 1) | function r(e){return e[$]=!0,e}
function o (line 1) | function o(e){var t=L.createElement("fieldset");try{return!!e(t)}catch(e...
function i (line 1) | function i(e,t){for(var n=e.split("|"),r=n.length;r--;)C.attrHandle[n[r]...
function s (line 1) | function s(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.source...
function a (line 1) | function a(e){return function(t){var n=t.nodeName.toLowerCase();return"i...
function u (line 1) | function u(e){return function(t){var n=t.nodeName.toLowerCase();return("...
function c (line 1) | function c(e){return function(t){return"form"in t?t.parentNode&&t.disabl...
function l (line 1) | function l(e){return r(function(t){return t=+t,r(function(n,r){for(var o...
function f (line 1) | function f(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}
function p (line 1) | function p(){}
function h (line 1) | function h(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}
function d (line 1) | function d(e,t,n){var r=t.dir,o=t.next,i=o||r,s=n&&"parentNode"===i,a=_+...
function g (line 1) | function g(e){return e.length>1?function(t,n,r){for(var o=e.length;o--;)...
function m (line 1) | function m(e,n,r){for(var o=0,i=n.length;o<i;o++)t(e,n[o],r);return r}
function v (line 1) | function v(e,t,n,r,o){for(var i,s=[],a=0,u=e.length,c=null!=t;a<u;a++)(i...
function y (line 1) | function y(e,t,n,o,i,s){return o&&!o[$]&&(o=y(o)),i&&!i[$]&&(i=y(i,s)),r...
function x (line 1) | function x(e){for(var t,n,r,o=e.length,i=C.relative[e[0].type],s=i||C.re...
function b (line 1) | function b(e,n){var o=n.length>0,i=e.length>0,s=function(r,s,a,u,c){var ...
function i (line 2) | function i(t,n,r,o){return function(){var c=this,l=arguments,f=function(...
function t (line 2) | function t(){if(a){a.style.cssText="box-sizing:border-box;position:relat...
function r (line 3) | function r(t,n,r,a){var c,p,h,b,w,T=n;l||(l=!0,u&&e.clearTimeout(u),o=vo...
function t (line 3) | function t(e,t){for(var n=0,r=e.length-1;r>=0;r--){var o=e[r];"."===o?e....
function r (line 3) | function r(e,t){if(e.filter)return e.filter(t);for(var n=[],r=0;r<e.leng...
function r (line 3) | function r(e){for(var t=0;t<e.length&&""===e[t];t++);for(var n=e.length-...
function r (line 3) | function r(){throw new Error("setTimeout has not been defined")}
function o (line 3) | function o(){throw new Error("clearTimeout has not been defined")}
function i (line 3) | function i(e){if(f===setTimeout)return setTimeout(e,0);if((f===r||!f)&&s...
function s (line 3) | function s(e){if(p===clearTimeout)return clearTimeout(e);if((p===o||!p)&...
function a (line 3) | function a(){m&&d&&(m=!1,d.length?g=d.concat(g):v=-1,g.length&&u())}
function u (line 3) | function u(){if(!m){var e=i(a);m=!0;for(var t=g.length;t;){for(d=g,g=[];...
function c (line 3) | function c(e,t){this.fun=e,this.array=t}
function l (line 3) | function l(){}
function o (line 3) | function o(e){throw new RangeError(L[e])}
function i (line 3) | function i(e,t){for(var n=e.length,r=[];n--;)r[n]=t(e[n]);return r}
function s (line 3) | function s(e,t){var n=e.split("@"),r="";n.length>1&&(r=n[0]+"@",e=n[1]),...
function a (line 3) | function a(e){for(var t,n,r=[],o=0,i=e.length;o<i;)t=e.charCodeAt(o++),t...
function u (line 3) | function u(e){return i(e,function(e){var t="";return e>65535&&(e-=65536,...
function c (line 3) | function c(e){return e-48<10?e-22:e-65<26?e-65:e-97<26?e-97:T}
function l (line 3) | function l(e,t){return e+22+75*(e<26)-((0!=t)<<5)}
function f (line 3) | function f(e,t,n){var r=0;for(e=n?F(e/E):e>>1,e+=F(e/t);e>H*j>>1;r+=T)e=...
function p (line 3) | function p(e){var t,n,r,i,s,a,l,p,h,d,g=[],m=e.length,v=0,y=N,x=S;for(n=...
function h (line 3) | function h(e){var t,n,r,i,s,u,c,p,h,d,g,m,v,y,x,b=[];for(e=a(e),m=e.leng...
function d (line 3) | function d(e){return s(e,function(e){return q.test(e)?p(e.slice(4).toLow...
function g (line 3) | function g(e){return s(e,function(e){return D.test(e)?"xn--"+h(e):e})}
function r (line 3) | function r(e,t){return Object.prototype.hasOwnProperty.call(e,t)}
function r (line 3) | function r(e,t){if(e.map)return e.map(t);for(var n=[],r=0;r<e.length;r++...
function r (line 3) | function r(){this.protocol=null,this.slashes=null,this.auth=null,this.ho...
function o (line 3) | function o(e,t,n){
function i (line 4) | function i(e){return c.isString(e)&&(e=o(e)),e instanceof r?e.format():r...
function s (line 4) | function s(e,t){return o(e,!1,!0).resolve(t)}
function a (line 4) | function a(e,t){return e?o(e,!1,!0).resolveObject(t):t}
function r (line 4) | function r(e){console.log("page has changed",e),o(e),l||(l=!0,c.trigger(...
function o (line 4) | function o(e){f.page=e.page,f.file=e.file,f.gitbook=e.gitbook,f.config=e...
function i (line 4) | function i(){return f}
FILE: docs/gitbook/theme.js
function o (line 1) | function o(a,s){if(!n[a]){if(!t[a]){var u="function"==typeof require&&re...
function n (line 1) | function n(e,t){t=t||te;var n=t.createElement("script");n.text=e,t.head....
function r (line 1) | function r(e){var t=!!e&&"length"in e&&e.length,n=de.type(e);return"func...
function o (line 1) | function o(e,t,n){return de.isFunction(t)?de.grep(e,function(e,r){return...
function i (line 1) | function i(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}
function a (line 1) | function a(e){var t={};return de.each(e.match(qe)||[],function(e,n){t[n]...
function s (line 1) | function s(e){return e}
function u (line 1) | function u(e){throw e}
function l (line 1) | function l(e,t,n){var r;try{e&&de.isFunction(r=e.promise)?r.call(e).done...
function c (line 1) | function c(){te.removeEventListener("DOMContentLoaded",c),e.removeEventL...
function f (line 1) | function f(){this.expando=de.expando+f.uid++}
function p (line 1) | function p(e){return"true"===e||"false"!==e&&("null"===e?null:e===+e+""?...
function h (line 1) | function h(e,t,n){var r;if(void 0===n&&1===e.nodeType)if(r="data-"+t.rep...
function d (line 1) | function d(e,t,n,r){var o,i=1,a=20,s=r?function(){return r.cur()}:functi...
function g (line 1) | function g(e){var t,n=e.ownerDocument,r=e.nodeName,o=Ue[r];return o?o:(t...
function m (line 1) | function m(e,t){for(var n,r,o=[],i=0,a=e.length;i<a;i++)r=e[i],r.style&&...
function v (line 1) | function v(e,t){var n;return n="undefined"!=typeof e.getElementsByTagNam...
function y (line 1) | function y(e,t){for(var n=0,r=e.length;n<r;n++)Fe.set(e[n],"globalEval",...
function b (line 1) | function b(e,t,n,r,o){for(var i,a,s,u,l,c,f=t.createDocumentFragment(),p...
function x (line 1) | function x(){return!0}
function w (line 1) | function w(){return!1}
function C (line 1) | function C(){try{return te.activeElement}catch(e){}}
function T (line 1) | function T(e,t,n,r,o,i){var a,s;if("object"==typeof t){"string"!=typeof ...
function k (line 1) | function k(e,t){return de.nodeName(e,"table")&&de.nodeName(11!==t.nodeTy...
function j (line 1) | function j(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}
function E (line 1) | function E(e){var t=rt.exec(e.type);return t?e.type=t[1]:e.removeAttribu...
function N (line 1) | function N(e,t){var n,r,o,i,a,s,u,l;if(1===t.nodeType){if(Fe.hasData(e)&...
function S (line 1) | function S(e,t){var n=t.nodeName.toLowerCase();"input"===n&&ze.test(e.ty...
function A (line 1) | function A(e,t,r,o){t=oe.apply([],t);var i,a,s,u,l,c,f=0,p=e.length,h=p-...
function q (line 1) | function q(e,t,n){for(var r,o=t?de.filter(t,e):e,i=0;null!=(r=o[i]);i++)...
function D (line 1) | function D(e,t,n){var r,o,i,a,s=e.style;return n=n||st(e),n&&(a=n.getPro...
function O (line 1) | function O(e,t){return{get:function(){return e()?void delete this.get:(t...
function L (line 1) | function L(e){if(e in pt)return e;for(var t=e[0].toUpperCase()+e.slice(1...
function H (line 1) | function H(e,t,n){var r=$e.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3...
function F (line 1) | function F(e,t,n,r,o){var i,a=0;for(i=n===(r?"border":"content")?4:"widt...
function P (line 1) | function P(e,t,n){var r,o=!0,i=st(e),a="border-box"===de.css(e,"boxSizin...
function I (line 1) | function I(e,t,n,r,o){return new I.prototype.init(e,t,n,r,o)}
function R (line 1) | function R(){dt&&(e.requestAnimationFrame(R),de.fx.tick())}
function M (line 1) | function M(){return e.setTimeout(function(){ht=void 0}),ht=de.now()}
function $ (line 1) | function $(e,t){var n,r=0,o={height:e};for(t=t?1:0;r<4;r+=2-t)n=_e[r],o[...
function _ (line 1) | function _(e,t,n){for(var r,o=(U.tweeners[t]||[]).concat(U.tweeners["*"]...
function W (line 1) | function W(e,t,n){var r,o,i,a,s,u,l,c,f="width"in t||"height"in t,p=this...
function B (line 1) | function B(e,t){var n,r,o,i,a;for(n in e)if(r=de.camelCase(n),o=t[r],i=e...
function U (line 1) | function U(e,t,n){var r,o,i=0,a=U.prefilters.length,s=de.Deferred().alwa...
function z (line 1) | function z(e){var t=e.match(qe)||[];return t.join(" ")}
function X (line 1) | function X(e){return e.getAttribute&&e.getAttribute("class")||""}
function K (line 1) | function K(e,t,n,r){var o;if(de.isArray(t))de.each(t,function(t,o){n||Et...
function V (line 1) | function V(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r...
function G (line 1) | function G(e,t,n,r){function o(s){var u;return i[s]=!0,de.each(e[s]||[],...
function Y (line 1) | function Y(e,t){var n,r,o=de.ajaxSettings.flatOptions||{};for(n in t)voi...
function Q (line 1) | function Q(e,t,n){for(var r,o,i,a,s=e.contents,u=e.dataTypes;"*"===u[0];...
function J (line 1) | function J(e,t,n,r){var o,i,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for...
function Z (line 1) | function Z(e){return de.isWindow(e)?e:9===e.nodeType&&e.defaultView}
function t (line 1) | function t(e,t,n,r){var o,i,a,s,u,l,c,p=t&&t.ownerDocument,d=t?t.nodeTyp...
function n (line 1) | function n(){function e(n,r){return t.push(n+" ")>T.cacheLength&&delete ...
function r (line 1) | function r(e){return e[$]=!0,e}
function o (line 1) | function o(e){var t=L.createElement("fieldset");try{return!!e(t)}catch(e...
function i (line 1) | function i(e,t){for(var n=e.split("|"),r=n.length;r--;)T.attrHandle[n[r]...
function a (line 1) | function a(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.source...
function s (line 1) | function s(e){return function(t){var n=t.nodeName.toLowerCase();return"i...
function u (line 1) | function u(e){return function(t){var n=t.nodeName.toLowerCase();return("...
function l (line 1) | function l(e){return function(t){return"form"in t?t.parentNode&&t.disabl...
function c (line 1) | function c(e){return r(function(t){return t=+t,r(function(n,r){for(var o...
function f (line 1) | function f(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}
function p (line 1) | function p(){}
function h (line 1) | function h(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}
function d (line 1) | function d(e,t,n){var r=t.dir,o=t.next,i=o||r,a=n&&"parentNode"===i,s=B+...
function g (line 1) | function g(e){return e.length>1?function(t,n,r){for(var o=e.length;o--;)...
function m (line 1) | function m(e,n,r){for(var o=0,i=n.length;o<i;o++)t(e,n[o],r);return r}
function v (line 1) | function v(e,t,n,r,o){for(var i,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(i...
function y (line 1) | function y(e,t,n,o,i,a){return o&&!o[$]&&(o=y(o)),i&&!i[$]&&(i=y(i,a)),r...
function b (line 1) | function b(e){for(var t,n,r,o=e.length,i=T.relative[e[0].type],a=i||T.re...
function x (line 1) | function x(e,n){var o=n.length>0,i=e.length>0,a=function(r,a,s,u,l){var ...
function i (line 2) | function i(t,n,r,o){return function(){var l=this,c=arguments,f=function(...
function t (line 2) | function t(){if(s){s.style.cssText="box-sizing:border-box;position:relat...
function r (line 3) | function r(t,n,r,s){var l,p,h,x,w,C=n;c||(c=!0,u&&e.clearTimeout(u),o=vo...
function o (line 3) | function o(e,t,n){return e.addEventListener?void e.addEventListener(t,n,...
function i (line 3) | function i(e){if("keypress"==e.type){var t=String.fromCharCode(e.which);...
function a (line 3) | function a(e,t){return e.sort().join(",")===t.sort().join(",")}
function s (line 3) | function s(e){var t=[];return e.shiftKey&&t.push("shift"),e.altKey&&t.pu...
function u (line 3) | function u(e){return e.preventDefault?void e.preventDefault():void(e.ret...
function l (line 3) | function l(e){return e.stopPropagation?void e.stopPropagation():void(e.c...
function c (line 3) | function c(e){return"shift"==e||"ctrl"==e||"alt"==e||"meta"==e}
function f (line 3) | function f(){if(!v){v={};for(var e in y)e>95&&e<112||y.hasOwnProperty(e)...
function p (line 3) | function p(e,t,n){return n||(n=f()[e]?"keydown":"keypress"),"keypress"==...
function h (line 3) | function h(e){return"+"===e?["+"]:(e=e.replace(/\+{2}/g,"+plus"),e.split...
function d (line 3) | function d(e,t){var n,r,o,i=[];for(n=h(e),o=0;o<n.length;++o)r=n[o],w[r]...
function g (line 3) | function g(e,t){return null!==e&&e!==n&&(e===t||g(e.parentNode,t))}
function m (line 3) | function m(e){function t(e){e=e||{};var t,n=!1;for(t in x)e[t]?n=!0:x[t]...
function o (line 3) | function o(e){throw new RangeError(L[e])}
function i (line 3) | function i(e,t){for(var n=e.length,r=[];n--;)r[n]=t(e[n]);return r}
function a (line 3) | function a(e,t){var n=e.split("@"),r="";n.length>1&&(r=n[0]+"@",e=n[1]),...
function s (line 3) | function s(e){for(var t,n,r=[],o=0,i=e.length;o<i;)t=e.charCodeAt(o++),t...
function u (line 3) | function u(e){return i(e,function(e){var t="";return e>65535&&(e-=65536,...
function l (line 3) | function l(e){return e-48<10?e-22:e-65<26?e-65:e-97<26?e-97:C}
function c (line 3) | function c(e,t){return e+22+75*(e<26)-((0!=t)<<5)}
function f (line 3) | function f(e,t,n){var r=0;for(e=n?F(e/E):e>>1,e+=F(e/t);e>H*k>>1;r+=C)e=...
function p (line 3) | function p(e){var t,n,r,i,a,s,c,p,h,d,g=[],m=e.length,v=0,y=S,b=N;for(n=...
function h (line 3) | function h(e){var t,n,r,i,a,u,l,p,h,d,g,m,v,y,b,x=[];for(e=s(e),m=e.leng...
function d (line 3) | function d(e){return a(e,function(e){return q.test(e)?p(e.slice(4).toLow...
function g (line 3) | function g(e){return a(e,function(e){return D.test(e)?"xn--"+h(e):e})}
function r (line 3) | function r(e,t){return Object.prototype.hasOwnProperty.call(e,t)}
function r (line 4) | function r(e,t){if(e.map)return e.map(t);for(var n=[],r=0;r<e.length;r++...
function r (line 4) | function r(){this.protocol=null,this.slashes=null,this.auth=null,this.ho...
function o (line 4) | function o(e,t,n){if(e&&l.isObject(e)&&e instanceof r)return e;var o=new...
function i (line 4) | function i(e){return l.isString(e)&&(e=o(e)),e instanceof r?e.format():r...
function a (line 4) | function a(e,t){return o(e,!1,!0).resolve(t)}
function s (line 4) | function s(e,t){return e?o(e,!1,!0).resolveObject(t):t}
function r (line 4) | function r(e){var t=a(e.currentTarget).parent().find(".dropdown-menu");t...
function o (line 4) | function o(e){a(".dropdown-menu").removeClass("open")}
function i (line 4) | function i(){a(document).on("click",".toggle-dropdown",r),a(document).on...
function r (line 4) | function r(){s.init(),i.init(),o.init(),a.init(),u.createButton({index:0...
function r (line 4) | function r(e,t){i.bind(e,function(e){return t(),!1})}
function o (line 4) | function o(){r(["right"],function(e){a.goNext()}),r(["left"],function(e)...
function r (line 4) | function r(e){return o.state.$book.addClass("is-loading"),e.always(funct...
function r (line 4) | function r(){return T(E.isSmallScreen()?".book-body":".body-inner")}
function o (line 4) | function o(e){var t=r(),n=0;i(e)&&(e&&(n=u(e)),t.unbind("scroll"),t.anim...
function i (line 4) | function i(e){var t=r(),n=t.find(e);return!!n.length}
function a (line 4) | function a(e){return 0===e.length}
function s (line 4) | function s(e,t){return e.length>0&&e.filter(t).length>0}
function u (line 4) | function u(e){var t=r(),n=t.find(".page-inner"),o=t.find(e),i=o.offsetPa...
function l (line 4) | function l(e,t){if(e||t||(e=w.first()),t&&(e=w.length>1?w.filter(functio...
function c (line 4) | function c(e){var t=e.children("a"),n=t.attr("href").split("#")[1];retur...
function f (line 4) | function f(){var e=r(),t=e.scrollTop(),n=e.prop("scrollHeight"),o=e.prop...
function p (line 4) | function p(e,t){var n=k.parse(A),r=k.resolve(window.location.pathname,e)...
function h (line 4) | function h(){var e,t;e=parseInt(T(".body-inner").css("width"),10),t=pars...
function d (line 4) | function d(e){var t=T(".book-body"),n=t.find(".body-inner"),o=n.find(".p...
function g (line 4) | function g(e){return 0===e.button}
function m (line 4) | function m(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}
function v (line 4) | function v(e){var t=T(this),n=t.attr("target");if(!m(e)&&g(e)&&!n){e.sto...
function y (line 4) | function y(){var e=T(".navigation-next").attr("href");e&&p(e,!0)}
function b (line 4) | function b(){var e=T(".navigation-prev").attr("href");e&&p(e,!0)}
function x (line 4) | function x(){T.ajaxSetup({cache:!1}),history.replaceState({path:window.l...
function r (line 4) | function r(e,t){null!=l.state&&o()==e||(null==t&&(t=!0),l.state.$book.to...
function o (line 4) | function o(){return l.state.$book.hasClass("with-summary")}
function i (line 4) | function i(){u.isMobile()||r(l.storage.get("sidebar",!0),!1),s(document)...
function a (line 4) | function a(e){var t=s(".book-summary");t.find("li").each(function(){var ...
function r (line 4) | function r(){return"btn-"+g++}
function o (line 4) | function o(e,t,n,r){var o=e.children(t).length;n<0&&(n=Math.max(0,o+1+n)...
function i (line 4) | function i(e){e.preventDefault()}
function a (line 4) | function a(e){var t=p("<div>",{class:"dropdown-menu",html:'<div class="d...
function s (line 4) | function s(e){return e=p.extend({label:"",icon:"",text:"",position:"left...
function u (line 4) | function u(e){var t,n=p(".book-header"),r=n.find("h1"),i="pull-"+e.posit...
function l (line 4) | function l(){p(".js-toolbar-action").remove(),d.forEach(u)}
function c (line 4) | function c(e){d=p.grep(d,function(t){return t.id!=e}),l()}
function f (line 4) | function f(e){d=p.grep(d,function(t){return e.indexOf(t.id)==-1}),l()}
Condensed preview — 65 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,038K chars).
[
{
"path": ".gitattributes",
"chars": 2518,
"preview": "###############################################################################\n# Set default behavior to automatically "
},
{
"path": ".github/FUNDING.yml",
"chars": 838,
"preview": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [u"
},
{
"path": ".github/workflows/build_latest.yml",
"chars": 797,
"preview": "name: Build_Latest\n \non: [push]\n \njobs:\n build:\n \n runs-on: windows-latest\n \n steps:\n - uses: actions/checkout"
},
{
"path": ".gitignore",
"chars": 4305,
"preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n\n# User"
},
{
"path": "LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2019 nilaoda\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
},
{
"path": "N_m3u8DL-CLI/App.config",
"chars": 178,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<configuration>\n <startup> \n \n <supportedRuntime version=\"v4.0\" sku="
},
{
"path": "N_m3u8DL-CLI/CSChaCha20.cs",
"chars": 27761,
"preview": "/*\n * Copyright (c) 2015, 2018 Scott Bennett\n * (c) 2018-2021 Kaarlo Räihä\n *\n * Permission to use, copy, mod"
},
{
"path": "N_m3u8DL-CLI/Decode51CtoKey.cs",
"chars": 14771,
"preview": "using NiL.JS.BaseLibrary;\nusing NiL.JS.Core;\nusing NiL.JS.Extensions;\nusing System.Security.Cryptography;\nusing System."
},
{
"path": "N_m3u8DL-CLI/DecodeCdeledu.cs",
"chars": 2496,
"preview": "using NiL.JS.BaseLibrary;\nusing NiL.JS.Core;\nusing NiL.JS.Extensions;\nusing System;\nusing Array = System.Array;\n\nnamesp"
},
{
"path": "N_m3u8DL-CLI/DecodeDdyun.cs",
"chars": 1429,
"preview": "using System.Security.Cryptography;\nusing System.Text;\nusing System.Text.RegularExpressions;\n\nnamespace N_m3u8DL_CLI\n{\n"
},
{
"path": "N_m3u8DL-CLI/DecodeHuke88Key.cs",
"chars": 1857,
"preview": "using Newtonsoft.Json.Linq;\nusing System;\nusing System.Collections.Generic;\nusing System.Security.Cryptography;\nusing S"
},
{
"path": "N_m3u8DL-CLI/DecodeImooc.cs",
"chars": 8865,
"preview": "using NiL.JS.BaseLibrary;\nusing NiL.JS.Core;\nusing NiL.JS.Extensions;\nusing System;\nusing Array = System.Array;\n\nnamesp"
},
{
"path": "N_m3u8DL-CLI/DecodeNfmovies.cs",
"chars": 1134,
"preview": "using System;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\n\nnamespace N_m3u8DL_CLI\n{\n class DecodeNfmovies"
},
{
"path": "N_m3u8DL-CLI/Decrypter.cs",
"chars": 3985,
"preview": "using CSChaCha20;\nusing System;\nusing System.IO;\nusing System.Linq;\nusing System.Security.Cryptography;\n\nnamespace N_m3"
},
{
"path": "N_m3u8DL-CLI/DownloadManager.cs",
"chars": 32626,
"preview": "using Newtonsoft.Json.Linq;\nusing System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing System.Te"
},
{
"path": "N_m3u8DL-CLI/Downloader.cs",
"chars": 12637,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.ComponentModel;\nusing System.IO;\nusing System.Linq;\nusing "
},
{
"path": "N_m3u8DL-CLI/FFmpeg.cs",
"chars": 8634,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing Sys"
},
{
"path": "N_m3u8DL-CLI/Global.cs",
"chars": 50931,
"preview": "using BrotliSharpLib;\nusing Newtonsoft.Json;\nusing System;\nusing System.Collections;\nusing System.Collections.Generic;\n"
},
{
"path": "N_m3u8DL-CLI/HLSLiveDownloader.cs",
"chars": 5749,
"preview": "using Newtonsoft.Json.Linq;\nusing System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.IO;\n"
},
{
"path": "N_m3u8DL-CLI/HLSTags.cs",
"chars": 1958,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nna"
},
{
"path": "N_m3u8DL-CLI/IqJsonParser.cs",
"chars": 5640,
"preview": "using Newtonsoft.Json.Linq;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing S"
},
{
"path": "N_m3u8DL-CLI/LOGGER.cs",
"chars": 5539,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing Sys"
},
{
"path": "N_m3u8DL-CLI/MPDParser.cs",
"chars": 41509,
"preview": "using Newtonsoft.Json;\nusing Newtonsoft.Json.Linq;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusi"
},
{
"path": "N_m3u8DL-CLI/MyOptions.cs",
"chars": 4911,
"preview": "using CommandLine;\nusing CommandLine.Text;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Sys"
},
{
"path": "N_m3u8DL-CLI/N_m3u8DL-CLI.csproj",
"chars": 9296,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbui"
},
{
"path": "N_m3u8DL-CLI/Parser.cs",
"chars": 45347,
"preview": "using Newtonsoft.Json.Linq;\nusing System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.IO;\n"
},
{
"path": "N_m3u8DL-CLI/Program.cs",
"chars": 22955,
"preview": "using CommandLine;\nusing CommandLine.Text;\nusing Newtonsoft.Json.Linq;\nusing System;\nusing System.Collections;\nusing Sy"
},
{
"path": "N_m3u8DL-CLI/ProgressReporter.cs",
"chars": 1326,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nna"
},
{
"path": "N_m3u8DL-CLI/Properties/AssemblyInfo.cs",
"chars": 964,
"preview": "using System.Reflection;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\n\n// 有关程序集的一般信息由以下"
},
{
"path": "N_m3u8DL-CLI/Watcher.cs",
"chars": 4890,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Thr"
},
{
"path": "N_m3u8DL-CLI/changelog.txt",
"chars": 7438,
"preview": "2018年12月3日\n - 通过监控文件夹的更改来营造下载进度\n - 增加对EXT-X-DISCONTINUITY的处理(分部分处理,分别合并)\n - 增加对腾讯视频HDR的支持(主要是EXT-X-MAP的处理)\n - 增加对Ma"
},
{
"path": "N_m3u8DL-CLI/packages.config",
"chars": 919,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<packages>\n <package id=\"BrotliSharpLib\" version=\"0.3.3\" targetFramework=\"net46"
},
{
"path": "N_m3u8DL-CLI/strings.Designer.cs",
"chars": 26041,
"preview": "//------------------------------------------------------------------------------\n// <auto-generated>\n// 此代码由工具生成。\n/"
},
{
"path": "N_m3u8DL-CLI/strings.en-US.Designer.cs",
"chars": 0,
"preview": ""
},
{
"path": "N_m3u8DL-CLI/strings.en-US.resx",
"chars": 15532,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n <!-- \n Microsoft ResX Schema \n \n Version 2.0\n \n The prim"
},
{
"path": "N_m3u8DL-CLI/strings.resx",
"chars": 14122,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n <!-- \n Microsoft ResX Schema \n \n Version 2.0\n \n The prim"
},
{
"path": "N_m3u8DL-CLI/strings.zh-TW.Designer.cs",
"chars": 0,
"preview": ""
},
{
"path": "N_m3u8DL-CLI/strings.zh-TW.resx",
"chars": 14122,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n <!-- \n Microsoft ResX Schema \n \n Version 2.0\n \n The prim"
},
{
"path": "N_m3u8DL-CLI.sln",
"chars": 1115,
"preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 16\nVisualStudioVersion = 16.0.2921"
},
{
"path": "README.md",
"chars": 4830,
"preview": "```\n\n███╗ ██╗ ███╗ ███╗██████╗ ██╗ ██╗ █████╗ ██████╗ ██╗ ██████╗██╗ ██╗\n████╗ ██║ ████╗ "
},
{
"path": "README_ENG.md",
"chars": 4185,
"preview": "```\n\n███╗ ██╗ ███╗ ███╗██████╗ ██╗ ██╗ █████╗ ██████╗ ██╗ ██████╗██╗ ██╗\n████╗ ██║ ████╗ "
},
{
"path": "docs/Advanced.html",
"chars": 36668,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/GetM3u8.html",
"chars": 11508,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/Introductory.html",
"chars": 12312,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/M3U8URL2File.html",
"chars": 11535,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/SimpleGUI.html",
"chars": 14075,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/gitbook/gitbook-plugin-donate/plugin.css",
"chars": 2720,
"preview": ".gitbook-donate {\n padding: 10px 0; margin: 20px auto; width: 90%; text-align: center;\n}\n\n#rewardButton {\n cursor: poi"
},
{
"path": "docs/gitbook/gitbook-plugin-donate/plugin.js",
"chars": 1940,
"preview": "require(['gitbook', 'jQuery'], function(gitbook, $) {\n\tvar wechatURL;\n\tvar alipayURL;\n\tvar titleText;\n\tvar buttonText;\n\t"
},
{
"path": "docs/gitbook/gitbook-plugin-fontsettings/fontsettings.js",
"chars": 6447,
"preview": "require(['gitbook', 'jquery'], function(gitbook, $) {\n // Configuration\n var MAX_SIZE = 4,\n MIN_SIZE "
},
{
"path": "docs/gitbook/gitbook-plugin-fontsettings/website.css",
"chars": 8596,
"preview": "/*\n * Theme 1\n */\n.color-theme-1 .dropdown-menu {\n background-color: #111111;\n border-color: #7e888b;\n}\n.color-theme-1"
},
{
"path": "docs/gitbook/gitbook-plugin-github/plugin.js",
"chars": 388,
"preview": "require([ 'gitbook' ], function (gitbook) {\n gitbook.events.bind('start', function (e, config) {\n var githubUR"
},
{
"path": "docs/gitbook/gitbook-plugin-github-buttons/plugin.js",
"chars": 3318,
"preview": "// LICENSE : MIT\n\"use strict\";\nrequire(['gitbook'], function (gitbook) {\n function addBeforeHeader(element) {\n "
},
{
"path": "docs/gitbook/gitbook-plugin-highlight/ebook.css",
"chars": 2865,
"preview": "pre,\ncode {\n /* http://jmblog.github.io/color-themes-for-highlightjs */\n /* Tomorrow Comment */\n /* Tomorrow Red */\n "
},
{
"path": "docs/gitbook/gitbook-plugin-highlight/website.css",
"chars": 31446,
"preview": ".book .book-body .page-wrapper .page-inner section.normal pre,\n.book .book-body .page-wrapper .page-inner section.normal"
},
{
"path": "docs/gitbook/gitbook-plugin-lunr/search-lunr.js",
"chars": 1616,
"preview": "require([\n 'gitbook',\n 'jquery'\n], function(gitbook, $) {\n // Define global search engine\n function LunrSear"
},
{
"path": "docs/gitbook/gitbook-plugin-search/search-engine.js",
"chars": 1268,
"preview": "require([\n 'gitbook',\n 'jquery'\n], function(gitbook, $) {\n // Global search objects\n var engine = null;"
},
{
"path": "docs/gitbook/gitbook-plugin-search/search.css",
"chars": 974,
"preview": "/*\n This CSS only styled the search results section, not the search input\n It defines the basic interraction to hi"
},
{
"path": "docs/gitbook/gitbook-plugin-search/search.js",
"chars": 6368,
"preview": "require([\n 'gitbook',\n 'jquery'\n], function(gitbook, $) {\n var MAX_RESULTS = 15;\n var MAX_DESCRIPTION_SIZE ="
},
{
"path": "docs/gitbook/gitbook-plugin-sharing-plus/buttons.js",
"chars": 3173,
"preview": "require(['gitbook', 'jquery'], function(gitbook, $) {\n function site(label, icon, link) {\n return {\n "
},
{
"path": "docs/gitbook/gitbook.js",
"chars": 105399,
"preview": "!function e(t,n,r){function o(s,a){if(!n[s]){if(!t[s]){var u=\"function\"==typeof require&&require;if(!a&&u)return u(s,!0)"
},
{
"path": "docs/gitbook/style.css",
"chars": 52701,
"preview": "/*! normalize.css v2.1.0 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup"
},
{
"path": "docs/gitbook/theme.js",
"chars": 113262,
"preview": "!function e(t,n,r){function o(a,s){if(!n[a]){if(!t[a]){var u=\"function\"==typeof require&&require;if(!s&&u)return u(a,!0)"
},
{
"path": "docs/index.html",
"chars": 12820,
"preview": "\n<!DOCTYPE HTML>\n<html lang=\"zh-hans\" >\n <head>\n <meta charset=\"UTF-8\">\n <meta content=\"text/html; char"
},
{
"path": "docs/search_index.json",
"chars": 119839,
"preview": "{\"index\":{\"version\":\"0.5.12\",\"fields\":[{\"name\":\"title\",\"boost\":10},{\"name\":\"keywords\",\"boost\":15},{\"name\":\"body\",\"boost\""
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the nilaoda/N_m3u8DL-CLI GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 65 files (953.6 KB), approximately 293.7k tokens, and a symbol index with 457 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.