mirror of
https://github.com/NohamR/N_m3u8DL-RE.git
synced 2025-05-24 14:21:58 +00:00
Compare commits
44 Commits
6b181338c9
...
948f7aa75a
Author | SHA1 | Date | |
---|---|---|---|
![]() |
948f7aa75a | ||
![]() |
cd4dfb5e75 | ||
![]() |
f47a0722cf | ||
![]() |
ea9de55eac | ||
![]() |
87914a30cc | ||
![]() |
e350ab7233 | ||
![]() |
4c4617b780 | ||
![]() |
7a54c54786 | ||
![]() |
9f530f2cf6 | ||
![]() |
a8646eb7e7 | ||
![]() |
800ce3d615 | ||
![]() |
49d37c9f14 | ||
![]() |
117c73f54b | ||
![]() |
b044cdb305 | ||
![]() |
adbe376ae0 | ||
![]() |
cacf9b0ff0 | ||
![]() |
bc8b5a92a9 | ||
![]() |
7463915fbb | ||
![]() |
8702d36276 | ||
![]() |
3081701a32 | ||
![]() |
77fdaaf9bd | ||
![]() |
8095c6e172 | ||
![]() |
6bd906e4e6 | ||
![]() |
09d9f0e320 | ||
![]() |
9752df8aae | ||
![]() |
b3d95963db | ||
![]() |
312325ca18 | ||
![]() |
e8e92b6337 | ||
![]() |
0c73b730bb | ||
![]() |
c004a1c72f | ||
![]() |
bb20d50122 | ||
![]() |
3cd3bb9516 | ||
![]() |
5a56e34cd5 | ||
![]() |
f6ad09255e | ||
![]() |
b9d3b57b39 | ||
![]() |
7d8e7c6402 | ||
![]() |
9fc37d5b61 | ||
![]() |
8a25815c1f | ||
![]() |
dd30bd99f9 | ||
![]() |
9c49fce4ff | ||
![]() |
6e92acfda9 | ||
![]() |
d1ffac817d | ||
![]() |
164f2cb59b | ||
![]() |
2bf4f29f28 |
320
.github/workflows/build_latest.yml
vendored
320
.github/workflows/build_latest.yml
vendored
@ -10,7 +10,7 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
tag:
|
tag:
|
||||||
type: string
|
type: string
|
||||||
description: 'Release version tag (e.g. v1.2.3)'
|
description: 'Release version tag (e.g. v0.2.1-beta)'
|
||||||
required: true
|
required: true
|
||||||
ref:
|
ref:
|
||||||
type: string
|
type: string
|
||||||
@ -19,16 +19,73 @@ on:
|
|||||||
default: 'main'
|
default: 'main'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DOTNET_SDK_VERSION: "8.0.*"
|
DOTNET_SDK_VERSION: "9.0.*"
|
||||||
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
|
ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-win-x64-arm64:
|
set-date:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
date: ${{ steps.get_date.outputs.date }}
|
||||||
|
tag: ${{ steps.format_tag.outputs.tag }}
|
||||||
|
steps:
|
||||||
|
- name: Get Date in UTC+8
|
||||||
|
id: get_date
|
||||||
|
run: |
|
||||||
|
DATE=$(date -u -d '8 hours' +'%Y%m%d')
|
||||||
|
echo "date=${DATE}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Determine Tag
|
||||||
|
id: format_tag
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event.inputs.doRelease }}" == "true" ]; then
|
||||||
|
TAG="${{ github.event.inputs.tag }}"
|
||||||
|
else
|
||||||
|
TAG="actions-$GITHUB_RUN_ID"
|
||||||
|
fi
|
||||||
|
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
build-win-nt6_0-x86:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
needs: set-date
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Install zip
|
||||||
|
run: choco install zip --no-progress --yes
|
||||||
|
|
||||||
|
- name: Set up dotnet
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
||||||
|
|
||||||
|
- run: powershell -Command "(Get-Content src/N_m3u8DL-RE/N_m3u8DL-RE.csproj) -replace '<TargetFramework>.*</TargetFramework>', '<TargetFramework>net9.0-windows</TargetFramework>' | Set-Content src/N_m3u8DL-RE/N_m3u8DL-RE.csproj"
|
||||||
|
- run: dotnet add src/N_m3u8DL-RE/N_m3u8DL-RE.csproj package YY-Thunks --version 1.1.4
|
||||||
|
- run: dotnet add src/N_m3u8DL-RE/N_m3u8DL-RE.csproj package VC-LTL --version 5.1.1
|
||||||
|
- run: dotnet publish src/N_m3u8DL-RE -p:TargetPlatformMinVersion=6.0 -r win-x86 -c Release -o artifact-x86
|
||||||
|
|
||||||
|
- name: Package [win-x86]
|
||||||
|
run: |
|
||||||
|
cd artifact-x86
|
||||||
|
zip ../N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_win-NT6.0-x86_${{ needs.set-date.outputs.date }}.zip N_m3u8DL-RE.exe
|
||||||
|
|
||||||
|
- name: Upload Artifact[win-x86]
|
||||||
|
uses: actions/upload-artifact@v3.1.3
|
||||||
|
with:
|
||||||
|
name: win-NT6.0-x86
|
||||||
|
path: N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_win-NT6.0-x86_${{ needs.set-date.outputs.date }}.zip
|
||||||
|
|
||||||
|
build-win-x64-arm64:
|
||||||
|
runs-on: windows-latest
|
||||||
|
needs: set-date
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Install zip
|
||||||
|
run: choco install zip --no-progress --yes
|
||||||
|
|
||||||
- name: Set up dotnet
|
- name: Set up dotnet
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
@ -37,59 +94,169 @@ jobs:
|
|||||||
- run: dotnet publish src/N_m3u8DL-RE -r win-x64 -c Release -o artifact-x64
|
- run: dotnet publish src/N_m3u8DL-RE -r win-x64 -c Release -o artifact-x64
|
||||||
- run: dotnet publish src/N_m3u8DL-RE -r win-arm64 -c Release -o artifact-arm64
|
- run: dotnet publish src/N_m3u8DL-RE -r win-arm64 -c Release -o artifact-arm64
|
||||||
|
|
||||||
|
- name: Package [win]
|
||||||
|
run: |
|
||||||
|
cd artifact-x64
|
||||||
|
zip ../N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_win-x64_${{ needs.set-date.outputs.date }}.zip N_m3u8DL-RE.exe
|
||||||
|
cd ../artifact-arm64
|
||||||
|
zip ../N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_win-arm64_${{ needs.set-date.outputs.date }}.zip N_m3u8DL-RE.exe
|
||||||
|
|
||||||
- name: Upload Artifact [win-x64]
|
- name: Upload Artifact [win-x64]
|
||||||
uses: actions/upload-artifact@v3.1.3
|
uses: actions/upload-artifact@v3.1.3
|
||||||
with:
|
with:
|
||||||
name: N_m3u8DL-RE_Beta_win-x64
|
name: win-x64
|
||||||
path: artifact-x64\N_m3u8DL-RE.exe
|
path: N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_win-x64_${{ needs.set-date.outputs.date }}.zip
|
||||||
|
|
||||||
- name: Upload Artifact [win-arm64]
|
- name: Upload Artifact [win-arm64]
|
||||||
uses: actions/upload-artifact@v3.1.3
|
uses: actions/upload-artifact@v3.1.3
|
||||||
with:
|
with:
|
||||||
name: N_m3u8DL-RE_Beta_win-arm64
|
name: win-arm64
|
||||||
path: artifact-arm64\N_m3u8DL-RE.exe
|
path: N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_win-arm64_${{ needs.set-date.outputs.date }}.zip
|
||||||
|
|
||||||
build-linux-x64:
|
build-linux-x64-arm64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: ubuntu:18.04
|
needs: set-date
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- run: apt-get update
|
# https://learn.microsoft.com/zh-cn/dotnet/core/deploying/native-aot/cross-compile
|
||||||
- run: apt-get install -y curl wget
|
- run: |
|
||||||
|
sudo dpkg --add-architecture arm64
|
||||||
|
sudo bash -c 'cat > /etc/apt/sources.list.d/arm64.list <<EOF
|
||||||
|
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted
|
||||||
|
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main restricted
|
||||||
|
deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ jammy-backports main restricted universe multiverse
|
||||||
|
EOF'
|
||||||
|
sudo sed -i -e 's/deb http/deb [arch=amd64] http/g' /etc/apt/sources.list
|
||||||
|
sudo sed -i -e 's/deb mirror/deb [arch=amd64] mirror/g' /etc/apt/sources.list
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y curl wget libicu-dev libcurl4-openssl-dev zlib1g-dev libkrb5-dev clang llvm binutils-aarch64-linux-gnu gcc-aarch64-linux-gnu zlib1g-dev:arm64
|
||||||
|
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- name: Set up dotnet
|
- name: Set up dotnet
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
||||||
- run: apt-get install -y libicu-dev libcurl4-openssl-dev zlib1g-dev libkrb5-dev
|
|
||||||
- run: dotnet publish src/N_m3u8DL-RE -r linux-x64 -c Release -o artifact
|
- run: dotnet publish src/N_m3u8DL-RE -r linux-x64 -c Release -o artifact
|
||||||
|
- run: dotnet publish src/N_m3u8DL-RE -r linux-arm64 -c Release -o artifact-arm64
|
||||||
|
|
||||||
|
- name: Package [linux]
|
||||||
|
run: |
|
||||||
|
cd artifact
|
||||||
|
tar -czvf ../N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_linux-x64_${{ needs.set-date.outputs.date }}.tar.gz N_m3u8DL-RE
|
||||||
|
cd ../artifact-arm64
|
||||||
|
tar -czvf ../N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_linux-arm64_${{ needs.set-date.outputs.date }}.tar.gz N_m3u8DL-RE
|
||||||
|
|
||||||
- name: Upload Artifact [linux-x64]
|
- name: Upload Artifact [linux-x64]
|
||||||
uses: actions/upload-artifact@v3.1.3
|
uses: actions/upload-artifact@v3.1.3
|
||||||
with:
|
with:
|
||||||
name: N_m3u8DL-RE_Beta_linux-x64
|
name: linux-x64
|
||||||
path: artifact/N_m3u8DL-RE
|
path: N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_linux-x64_${{ needs.set-date.outputs.date }}.tar.gz
|
||||||
|
|
||||||
build-linux-arm64:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container: mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-18.04-cross-arm64-20220312201346-b2c2436
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- name: Set up dotnet
|
|
||||||
uses: actions/setup-dotnet@v3
|
|
||||||
with:
|
|
||||||
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
|
||||||
- run: dotnet publish src/N_m3u8DL-RE -r linux-arm64 -c Release -p:StripSymbols=true -p:CppCompilerAndLinker=clang-9 -p:SysRoot=/crossrootfs/arm64 -o artifact
|
|
||||||
|
|
||||||
- name: Upload Artifact[linux-arm64]
|
- name: Upload Artifact[linux-arm64]
|
||||||
uses: actions/upload-artifact@v3.1.3
|
uses: actions/upload-artifact@v3.1.3
|
||||||
with:
|
with:
|
||||||
name: N_m3u8DL-RE_Beta_linux-arm64
|
name: linux-arm64
|
||||||
path: artifact/N_m3u8DL-RE
|
path: N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_linux-arm64_${{ needs.set-date.outputs.date }}.tar.gz
|
||||||
|
|
||||||
|
build-android-bionic-x64-arm64:
|
||||||
|
runs-on: windows-latest
|
||||||
|
needs: set-date
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Set up NDK
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Invoke-WebRequest -Uri "https://dl.google.com/android/repository/android-ndk-r27c-windows.zip" -OutFile "android-ndk.zip"
|
||||||
|
Expand-Archive -Path "android-ndk.zip" -DestinationPath "./android-ndk"
|
||||||
|
Get-ChildItem -Path "./android-ndk"
|
||||||
|
$ndkRoot = "${{ github.workspace }}\android-ndk\android-ndk-r27c"
|
||||||
|
echo "NDK_ROOT=$ndkRoot" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8
|
||||||
|
$ndkBinPath = "$ndkRoot\toolchains\llvm\prebuilt\windows-x86_64\bin"
|
||||||
|
echo $ndkBinPath | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8
|
||||||
|
|
||||||
|
- name: Set up dotnet
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
||||||
|
|
||||||
|
- run: dotnet publish src/N_m3u8DL-RE -r linux-bionic-x64 -p:DisableUnsupportedError=true -p:PublishAotUsingRuntimePack=true -o artifact
|
||||||
|
- run: dotnet publish src/N_m3u8DL-RE -r linux-bionic-arm64 -p:DisableUnsupportedError=true -p:PublishAotUsingRuntimePack=true -o artifact-arm64
|
||||||
|
|
||||||
|
- name: Package [linux-bionic]
|
||||||
|
run: |
|
||||||
|
cd artifact
|
||||||
|
tar -czvf ../N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_android-bionic-x64_${{ needs.set-date.outputs.date }}.tar.gz N_m3u8DL-RE
|
||||||
|
cd ../artifact-arm64
|
||||||
|
tar -czvf ../N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_android-bionic-arm64_${{ needs.set-date.outputs.date }}.tar.gz N_m3u8DL-RE
|
||||||
|
|
||||||
|
- name: Upload Artifact [linux-bionic-x64]
|
||||||
|
uses: actions/upload-artifact@v3.1.3
|
||||||
|
with:
|
||||||
|
name: android-bionic-x64
|
||||||
|
path: N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_android-bionic-x64_${{ needs.set-date.outputs.date }}.tar.gz
|
||||||
|
|
||||||
|
- name: Upload Artifact[linux-bionic-arm64]
|
||||||
|
uses: actions/upload-artifact@v3.1.3
|
||||||
|
with:
|
||||||
|
name: android-bionic-arm64
|
||||||
|
path: N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_android-bionic-arm64_${{ needs.set-date.outputs.date }}.tar.gz
|
||||||
|
|
||||||
|
build-linux-musl-x64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: set-date
|
||||||
|
container: mcr.microsoft.com/dotnet/sdk:9.0-alpine-amd64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- run: apk add clang build-base zlib-dev
|
||||||
|
- run: dotnet publish src/N_m3u8DL-RE -r linux-musl-x64 -c Release -o artifact -p:InvariantGlobalization=true
|
||||||
|
|
||||||
|
- name: Package [linux-musl-x64]
|
||||||
|
run: |
|
||||||
|
cd artifact
|
||||||
|
tar -czvf ../N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_linux-musl-x64_${{ needs.set-date.outputs.date }}.tar.gz N_m3u8DL-RE
|
||||||
|
|
||||||
|
- name: Upload Artifact [linux-musl-x64]
|
||||||
|
uses: actions/upload-artifact@v3.1.3
|
||||||
|
with:
|
||||||
|
name: linux-musl-x64
|
||||||
|
path: N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_linux-musl-x64_${{ needs.set-date.outputs.date }}.tar.gz
|
||||||
|
|
||||||
|
build-linux-musl-arm64:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: set-date
|
||||||
|
container: mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-cross-arm64-alpine
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Set up dotnet
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
with:
|
||||||
|
dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
|
||||||
|
|
||||||
|
- run: apt-get update
|
||||||
|
- run: apt-get install -y build-essential clang binutils-aarch64-linux-gnu
|
||||||
|
- run: dotnet publish src/N_m3u8DL-RE -r linux-musl-arm64 -c Release -o artifact -p:CppCompilerAndLinker=clang -p:SysRoot=/crossrootfs/arm64 -p:InvariantGlobalization=true
|
||||||
|
|
||||||
|
- name: Package [linux-musl-arm64]
|
||||||
|
run: |
|
||||||
|
cd artifact
|
||||||
|
tar -czvf ../N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_linux-musl-arm64_${{ needs.set-date.outputs.date }}.tar.gz N_m3u8DL-RE
|
||||||
|
|
||||||
|
- name: Upload Artifact [linux-musl-arm64]
|
||||||
|
uses: actions/upload-artifact@v3.1.3
|
||||||
|
with:
|
||||||
|
name: linux-musl-arm64
|
||||||
|
path: N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_linux-musl-arm64_${{ needs.set-date.outputs.date }}.tar.gz
|
||||||
|
|
||||||
build-mac-x64-arm64:
|
build-mac-x64-arm64:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
needs: set-date
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
@ -101,83 +268,44 @@ jobs:
|
|||||||
- run: dotnet publish src/N_m3u8DL-RE -r osx-arm64 -c Release -o artifact-arm64
|
- run: dotnet publish src/N_m3u8DL-RE -r osx-arm64 -c Release -o artifact-arm64
|
||||||
- run: dotnet publish src/N_m3u8DL-RE -r osx-x64 -c Release -o artifact-x64
|
- run: dotnet publish src/N_m3u8DL-RE -r osx-x64 -c Release -o artifact-x64
|
||||||
|
|
||||||
|
- name: Package [osx]
|
||||||
|
run: |
|
||||||
|
cd artifact-x64
|
||||||
|
tar -czvf ../N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_osx-x64_${{ needs.set-date.outputs.date }}.tar.gz N_m3u8DL-RE
|
||||||
|
cd ../artifact-arm64
|
||||||
|
tar -czvf ../N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_osx-arm64_${{ needs.set-date.outputs.date }}.tar.gz N_m3u8DL-RE
|
||||||
|
|
||||||
- name: Upload Artifact [osx-x64]
|
- name: Upload Artifact [osx-x64]
|
||||||
uses: actions/upload-artifact@v3.1.3
|
uses: actions/upload-artifact@v3.1.3
|
||||||
with:
|
with:
|
||||||
name: N_m3u8DL-RE_Beta_osx-x64
|
name: osx-x64
|
||||||
path: artifact-x64/N_m3u8DL-RE
|
path: N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_osx-x64_${{ needs.set-date.outputs.date }}.tar.gz
|
||||||
|
|
||||||
- name: Upload Artifact[osx-arm64]
|
- name: Upload Artifact[osx-arm64]
|
||||||
uses: actions/upload-artifact@v3.1.3
|
uses: actions/upload-artifact@v3.1.3
|
||||||
with:
|
with:
|
||||||
name: N_m3u8DL-RE_Beta_osx-arm64
|
name: osx-arm64
|
||||||
path: artifact-arm64/N_m3u8DL-RE
|
path: N_m3u8DL-RE_${{ needs.set-date.outputs.tag }}_osx-arm64_${{ needs.set-date.outputs.date }}.tar.gz
|
||||||
|
|
||||||
create_draft_release:
|
create_release:
|
||||||
name: Create Github draft release
|
name: Create release
|
||||||
if: ${{ github.event.inputs.doRelease == 'true' }}
|
|
||||||
needs: [build-win-x64-arm64,build-linux-x64,build-linux-arm64,build-mac-x64-arm64]
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
permissions:
|
||||||
- name: Audit gh version
|
contents: write
|
||||||
run: gh --version
|
|
||||||
|
|
||||||
- name: Check for existing release
|
|
||||||
id: check_release
|
|
||||||
run: |
|
|
||||||
echo "::echo::on"
|
|
||||||
gh release view --repo '${{ github.repository }}' '${{ github.event.inputs.tag }}' \
|
|
||||||
&& echo "already_exists=true" >> $GITHUB_ENV \
|
|
||||||
|| echo "already_exists=false" >> $GITHUB_ENV
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Checkout repo
|
|
||||||
if: env.already_exists == 'false'
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
ref: '${{ github.event.inputs.ref }}'
|
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
if: env.already_exists == 'false'
|
|
||||||
run: >
|
|
||||||
gh release create
|
|
||||||
'${{ github.event.inputs.tag }}'
|
|
||||||
--draft
|
|
||||||
--repo '${{ github.repository }}'
|
|
||||||
--title '${{ github.event.inputs.tag }}'
|
|
||||||
--target '${{ github.event.inputs.ref }}'
|
|
||||||
--generate-notes
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
attach_to_release:
|
|
||||||
name: Attach native executables to release
|
|
||||||
if: ${{ github.event.inputs.doRelease == 'true' }}
|
if: ${{ github.event.inputs.doRelease == 'true' }}
|
||||||
needs: create_draft_release
|
needs: [set-date,build-win-nt6_0-x86,build-win-x64-arm64,build-linux-x64-arm64,build-android-bionic-x64-arm64,build-linux-musl-x64,build-linux-musl-arm64,build-mac-x64-arm64]
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
steps:
|
||||||
- name: Get current date
|
- name: Fetch artifacts
|
||||||
id: date
|
|
||||||
run: echo "date=$(date +'%Y%m%d')" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: GH version
|
|
||||||
run: gh --version
|
|
||||||
|
|
||||||
- name: Fetch executables
|
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
|
|
||||||
- name: Tar (linux, macOS)
|
- name: Create GitHub Release
|
||||||
run: for dir in *{osx,linux}*; do tar cvzfp "${dir}_${{ env.date }}.tar.gz" "$dir"; done
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
- name: Zip (windows)
|
tag: ${{ github.event.inputs.tag }}
|
||||||
run: for dir in *win*; do zip -r "${dir}_${{ env.date }}.zip" "$dir"; done
|
name: N_m3u8DL-RE_${{ github.event.inputs.tag }}
|
||||||
|
artifacts: "android-bionic-x64/*,android-bionic-arm64/*,linux-x64/*,linux-arm64/*,linux-musl-x64/*,linux-musl-arm64/*,osx-x64/*,osx-arm64/*,win-x64/*,win-arm64/*,win-NT6.0-x86/*"
|
||||||
- name: Upload
|
draft: false
|
||||||
run: |
|
allowUpdates: true
|
||||||
until gh release upload --clobber --repo ${{ github.repository }} ${{ github.event.inputs.tag }} *.zip *.tar.gz; do
|
generateReleaseNotes: true
|
||||||
echo "Attempt $((++attempts)) to upload release artifacts failed. Will retry in 20s"
|
discussionCategory: 'Announcements'
|
||||||
sleep 20
|
|
||||||
done
|
|
||||||
timeout-minutes: 10
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
10
README.md
10
README.md
@ -25,7 +25,7 @@ yay -Syu n-m3u8dl-re-git
|
|||||||
# 命令行参数
|
# 命令行参数
|
||||||
```
|
```
|
||||||
Description:
|
Description:
|
||||||
N_m3u8DL-RE (Beta version) 20240630
|
N_m3u8DL-RE (Beta version) 20241201
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
N_m3u8DL-RE <input> [options]
|
N_m3u8DL-RE <input> [options]
|
||||||
@ -40,6 +40,7 @@ Options:
|
|||||||
--base-url <base-url> 设置BaseURL
|
--base-url <base-url> 设置BaseURL
|
||||||
--thread-count <number> 设置下载线程数 [default: 本机CPU线程数]
|
--thread-count <number> 设置下载线程数 [default: 本机CPU线程数]
|
||||||
--download-retry-count <number> 每个分片下载异常时的重试次数 [default: 3]
|
--download-retry-count <number> 每个分片下载异常时的重试次数 [default: 3]
|
||||||
|
--http-request-timeout <seconds> HTTP请求的超时时间(秒) [default: 100]
|
||||||
--force-ansi-console 强制认定终端为支持ANSI且可交互的终端
|
--force-ansi-console 强制认定终端为支持ANSI且可交互的终端
|
||||||
--no-ansi-color 去除ANSI颜色
|
--no-ansi-color 去除ANSI颜色
|
||||||
--auto-select 自动选择所有类型的最佳轨道 [default: False]
|
--auto-select 自动选择所有类型的最佳轨道 [default: False]
|
||||||
@ -63,11 +64,12 @@ Options:
|
|||||||
--log-level <DEBUG|ERROR|INFO|OFF|WARN> 设置日志级别 [default: INFO]
|
--log-level <DEBUG|ERROR|INFO|OFF|WARN> 设置日志级别 [default: INFO]
|
||||||
--ui-language <en-US|zh-CN|zh-TW> 设置UI语言
|
--ui-language <en-US|zh-CN|zh-TW> 设置UI语言
|
||||||
--urlprocessor-args <urlprocessor-args> 此字符串将直接传递给URL Processor
|
--urlprocessor-args <urlprocessor-args> 此字符串将直接传递给URL Processor
|
||||||
--key <key> 设置解密密钥, 程序调用mp4decrpyt/shaka-packager进行解密. 格式:
|
--key <key> 设置解密密钥, 程序调用mp4decrpyt/shaka-packager/ffmpeg进行解密. 格式:
|
||||||
--key KID1:KEY1 --key KID2:KEY2
|
--key KID1:KEY1 --key KID2:KEY2
|
||||||
|
对于KEY相同的情况可以直接输入 --key KEY
|
||||||
--key-text-file <key-text-file> 设置密钥文件,程序将从文件中按KID搜寻KEY以解密.(不建议使用特大文件)
|
--key-text-file <key-text-file> 设置密钥文件,程序将从文件中按KID搜寻KEY以解密.(不建议使用特大文件)
|
||||||
|
--decryption-engine <FFMPEG|MP4DECRYPT|SHAKA_PACKAGER> 设置解密时使用的第三方程序 [default: MP4DECRYPT]
|
||||||
--decryption-binary-path <PATH> MP4解密所用工具的全路径, 例如 C:\Tools\mp4decrypt.exe
|
--decryption-binary-path <PATH> MP4解密所用工具的全路径, 例如 C:\Tools\mp4decrypt.exe
|
||||||
--use-shaka-packager 解密时使用shaka-packager替代mp4decrypt [default: False]
|
|
||||||
--mp4-real-time-decryption 实时解密MP4分片 [default: False]
|
--mp4-real-time-decryption 实时解密MP4分片 [default: False]
|
||||||
-R, --max-speed <SPEED> 设置限速,单位支持 Mbps 或 Kbps,如:15M 100K
|
-R, --max-speed <SPEED> 设置限速,单位支持 Mbps 或 Kbps,如:15M 100K
|
||||||
-M, --mux-after-done <OPTIONS> 所有工作完成时尝试混流分离的音视频. 输入 "--morehelp mux-after-done" 以查看详细信息
|
-M, --mux-after-done <OPTIONS> 所有工作完成时尝试混流分离的音视频. 输入 "--morehelp mux-after-done" 以查看详细信息
|
||||||
@ -94,6 +96,8 @@ Options:
|
|||||||
-da, --drop-audio <OPTIONS> 通过正则表达式去除符合要求的音频流.
|
-da, --drop-audio <OPTIONS> 通过正则表达式去除符合要求的音频流.
|
||||||
-ds, --drop-subtitle <OPTIONS> 通过正则表达式去除符合要求的字幕流.
|
-ds, --drop-subtitle <OPTIONS> 通过正则表达式去除符合要求的字幕流.
|
||||||
--ad-keyword <REG> 设置广告分片的URL关键字(正则表达式)
|
--ad-keyword <REG> 设置广告分片的URL关键字(正则表达式)
|
||||||
|
--disable-update-check 禁用版本更新检测 [default: False]
|
||||||
|
--allow-hls-multi-ext-map 允许HLS中的多个#EXT-X-MAP(实验性) [default: False]
|
||||||
--morehelp <OPTION> 查看某个选项的详细帮助信息
|
--morehelp <OPTION> 查看某个选项的详细帮助信息
|
||||||
--version Show version information
|
--version Show version information
|
||||||
-?, -h, --help Show help and usage information
|
-?, -h, --help Show help and usage information
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
{
|
|
||||||
public class EncryptInfo
|
public class EncryptInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -34,10 +29,6 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
{
|
{
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return EncryptMethod.UNKNOWN;
|
return EncryptMethod.UNKNOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
|
||||||
{
|
|
||||||
public class MSSData
|
public class MSSData
|
||||||
{
|
{
|
||||||
public string FourCC { get; set; } = "";
|
public string FourCC { get; set; } = "";
|
||||||
@ -22,4 +16,3 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
public string ProtectionSystemID { get; set; } = "";
|
public string ProtectionSystemID { get; set; } = "";
|
||||||
public string ProtectionData { get; set; } = "";
|
public string ProtectionData { get; set; } = "";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
|
||||||
{
|
|
||||||
// 主要处理 EXT-X-DISCONTINUITY
|
// 主要处理 EXT-X-DISCONTINUITY
|
||||||
public class MediaPart
|
public class MediaPart
|
||||||
{
|
{
|
||||||
public List<MediaSegment> MediaSegments { get; set; } = new List<MediaSegment>();
|
public List<MediaSegment> MediaSegments { get; set; } = [];
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,11 +1,7 @@
|
|||||||
using System;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
|
||||||
{
|
|
||||||
public class MediaSegment
|
public class MediaSegment
|
||||||
{
|
{
|
||||||
public long Index { get; set; }
|
public long Index { get; set; }
|
||||||
@ -14,12 +10,14 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
public DateTime? DateTime { get; set; }
|
public DateTime? DateTime { get; set; }
|
||||||
|
|
||||||
public long? StartRange { get; set; }
|
public long? StartRange { get; set; }
|
||||||
public long? StopRange { get => (StartRange != null && ExpectLength != null) ? StartRange + ExpectLength - 1 : null; }
|
public long? StopRange => (StartRange != null && ExpectLength != null) ? StartRange + ExpectLength - 1 : null;
|
||||||
public long? ExpectLength { get; set; }
|
public long? ExpectLength { get; set; }
|
||||||
|
|
||||||
public EncryptInfo EncryptInfo { get; set; } = new EncryptInfo();
|
public EncryptInfo EncryptInfo { get; set; } = new();
|
||||||
|
|
||||||
public string Url { get; set; }
|
public bool IsEncrypted => EncryptInfo.Method != EncryptMethod.NONE;
|
||||||
|
|
||||||
|
public string Url { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string? NameFromVar { get; set; } // MPD分段文件名
|
public string? NameFromVar { get; set; } // MPD分段文件名
|
||||||
|
|
||||||
@ -27,7 +25,7 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
{
|
{
|
||||||
return obj is MediaSegment segment &&
|
return obj is MediaSegment segment &&
|
||||||
Index == segment.Index &&
|
Index == segment.Index &&
|
||||||
Duration == segment.Duration &&
|
Math.Abs(Duration - segment.Duration) < 0.001 &&
|
||||||
Title == segment.Title &&
|
Title == segment.Title &&
|
||||||
StartRange == segment.StartRange &&
|
StartRange == segment.StartRange &&
|
||||||
StopRange == segment.StopRange &&
|
StopRange == segment.StopRange &&
|
||||||
@ -40,4 +38,3 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
return HashCode.Combine(Index, Duration, Title, StartRange, StopRange, ExpectLength, Url);
|
return HashCode.Combine(Index, Duration, Title, StartRange, StopRange, ExpectLength, Url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,27 +1,20 @@
|
|||||||
using N_m3u8DL_RE.Common.Enum;
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
|
||||||
{
|
|
||||||
public class Playlist
|
public class Playlist
|
||||||
{
|
{
|
||||||
// 对应Url信息
|
// 对应Url信息
|
||||||
public string Url { get; set; }
|
public string Url { get; set; } = string.Empty;
|
||||||
// 是否直播
|
// 是否直播
|
||||||
public bool IsLive { get; set; } = false;
|
public bool IsLive { get; set; } = false;
|
||||||
// 直播刷新间隔毫秒(默认15秒)
|
// 直播刷新间隔毫秒(默认15秒)
|
||||||
public double RefreshIntervalMs { get; set; } = 15000;
|
public double RefreshIntervalMs { get; set; } = 15000;
|
||||||
// 所有分片时长总和
|
// 所有分片时长总和
|
||||||
public double TotalDuration { get => MediaParts.Sum(x => x.MediaSegments.Sum(m => m.Duration)); }
|
public double TotalDuration => MediaParts.Sum(x => x.MediaSegments.Sum(m => m.Duration));
|
||||||
|
|
||||||
// 所有分片中最长时长
|
// 所有分片中最长时长
|
||||||
public double? TargetDuration { get; set; }
|
public double? TargetDuration { get; set; }
|
||||||
// INIT信息
|
// INIT信息
|
||||||
public MediaSegment? MediaInit { get; set; }
|
public MediaSegment? MediaInit { get; set; }
|
||||||
// 分片信息
|
// 分片信息
|
||||||
public List<MediaPart> MediaParts { get; set; } = new List<MediaPart>();
|
public List<MediaPart> MediaParts { get; set; } = [];
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,14 +1,9 @@
|
|||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
{
|
|
||||||
public class StreamSpec
|
public class StreamSpec
|
||||||
{
|
{
|
||||||
public MediaType? MediaType { get; set; }
|
public MediaType? MediaType { get; set; }
|
||||||
@ -51,12 +46,12 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// URL
|
/// URL
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Url { get; set; }
|
public string Url { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 原始URL
|
/// 原始URL
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string OriginalUrl { get; set; }
|
public string OriginalUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
public Playlist? Playlist { get; set; }
|
public Playlist? Playlist { get; set; }
|
||||||
|
|
||||||
@ -185,4 +180,3 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
return returnStr.TrimEnd().TrimEnd('|').TrimEnd();
|
return returnStr.TrimEnd().TrimEnd('|').TrimEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
|
||||||
{
|
|
||||||
public class SubCue
|
public class SubCue
|
||||||
{
|
{
|
||||||
public TimeSpan StartTime { get; set; }
|
public TimeSpan StartTime { get; set; }
|
||||||
@ -27,4 +21,3 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
return HashCode.Combine(StartTime, EndTime, Payload, Settings);
|
return HashCode.Combine(StartTime, EndTime, Payload, Settings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
using System;
|
using System.Text;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Entity
|
namespace N_m3u8DL_RE.Common.Entity;
|
||||||
{
|
|
||||||
public partial class WebVttSub
|
public partial class WebVttSub
|
||||||
{
|
{
|
||||||
[GeneratedRegex("X-TIMESTAMP-MAP.*")]
|
[GeneratedRegex("X-TIMESTAMP-MAP.*")]
|
||||||
@ -15,10 +11,10 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
private static partial Regex TSValueRegex();
|
private static partial Regex TSValueRegex();
|
||||||
[GeneratedRegex("\\s")]
|
[GeneratedRegex("\\s")]
|
||||||
private static partial Regex SplitRegex();
|
private static partial Regex SplitRegex();
|
||||||
[GeneratedRegex("<c\\..*?>([\\s\\S]*?)<\\/c>")]
|
[GeneratedRegex(@"<c\..*?>([\s\S]*?)<\/c>")]
|
||||||
private static partial Regex VttClassRegex();
|
private static partial Regex VttClassRegex();
|
||||||
|
|
||||||
public List<SubCue> Cues { get; set; } = new List<SubCue>();
|
public List<SubCue> Cues { get; set; } = [];
|
||||||
public long MpegtsTimestamp { get; set; } = 0L;
|
public long MpegtsTimestamp { get; set; } = 0L;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -75,8 +71,8 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needPayload)
|
if (!needPayload) continue;
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(line.Trim()))
|
if (string.IsNullOrEmpty(line.Trim()))
|
||||||
{
|
{
|
||||||
var payload = string.Join(Environment.NewLine, payloads);
|
var payload = string.Join(Environment.NewLine, payloads);
|
||||||
@ -101,10 +97,9 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
payloads.Add(line.Trim());
|
payloads.Add(line.Trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (BaseTimestamp != 0)
|
if (BaseTimestamp == 0) return webSub;
|
||||||
{
|
|
||||||
foreach (var item in webSub.Cues)
|
foreach (var item in webSub.Cues)
|
||||||
{
|
{
|
||||||
if (item.StartTime.TotalMilliseconds - BaseTimestamp >= 0)
|
if (item.StartTime.TotalMilliseconds - BaseTimestamp >= 0)
|
||||||
@ -117,7 +112,6 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return webSub;
|
return webSub;
|
||||||
}
|
}
|
||||||
@ -131,7 +125,7 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
return string.Concat(VttClassRegex().Matches(line).Select(x => x.Groups[1].Value + " "));
|
return string.Concat(VttClassRegex().Matches(line).Select(x => x.Groups[1].Value + " "));
|
||||||
})).TrimEnd();
|
})).TrimEnd();
|
||||||
}
|
}
|
||||||
else return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -144,8 +138,8 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
FixTimestamp(webSub, this.MpegtsTimestamp);
|
FixTimestamp(webSub, this.MpegtsTimestamp);
|
||||||
foreach (var item in webSub.Cues)
|
foreach (var item in webSub.Cues)
|
||||||
{
|
{
|
||||||
if (!this.Cues.Contains(item))
|
if (this.Cues.Contains(item)) continue;
|
||||||
{
|
|
||||||
// 如果相差只有1ms,且payload相同,则拼接
|
// 如果相差只有1ms,且payload相同,则拼接
|
||||||
var last = this.Cues.LastOrDefault();
|
var last = this.Cues.LastOrDefault();
|
||||||
if (last != null && this.Cues.Count > 0 && (item.StartTime - last.EndTime).TotalMilliseconds <= 1 && item.Payload == last.Payload)
|
if (last != null && this.Cues.Count > 0 && (item.StartTime - last.EndTime).TotalMilliseconds <= 1 && item.Payload == last.Payload)
|
||||||
@ -157,7 +151,6 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
this.Cues.Add(item);
|
this.Cues.Add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,10 +170,10 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
// 当前预添加的字幕的起始时间小于实际上已经走过的时间(如offset已经是100秒,而字幕起始却是2秒),才修复
|
// 当前预添加的字幕的起始时间小于实际上已经走过的时间(如offset已经是100秒,而字幕起始却是2秒),才修复
|
||||||
if (sub.Cues.Count > 0 && sub.Cues.First().StartTime < offset)
|
if (sub.Cues.Count > 0 && sub.Cues.First().StartTime < offset)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < sub.Cues.Count; i++)
|
foreach (var subCue in sub.Cues)
|
||||||
{
|
{
|
||||||
sub.Cues[i].StartTime += offset;
|
subCue.StartTime += offset;
|
||||||
sub.Cues[i].EndTime += offset;
|
subCue.EndTime += offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,11 +194,15 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
}
|
}
|
||||||
|
|
||||||
str = str.Replace(',', '.');
|
str = str.Replace(',', '.');
|
||||||
var ms = Convert.ToInt32(str.Split('.').Last());
|
long time = 0;
|
||||||
var o = str.Split('.').First();
|
string[] parts = str.Split('.');
|
||||||
var t = o.Split(':').Reverse().ToList();
|
if (parts.Length > 1)
|
||||||
var time = 0L + ms;
|
{
|
||||||
for (int i = 0; i < t.Count(); i++)
|
time += Convert.ToInt32(parts.Last().PadRight(3, '0'));
|
||||||
|
str = parts.First();
|
||||||
|
}
|
||||||
|
var t = str.Split(':').Reverse().ToList();
|
||||||
|
for (int i = 0; i < t.Count; i++)
|
||||||
{
|
{
|
||||||
time += (long)Math.Pow(60, i) * Convert.ToInt32(t[i]) * 1000;
|
time += (long)Math.Pow(60, i) * Convert.ToInt32(t[i]) * 1000;
|
||||||
}
|
}
|
||||||
@ -214,7 +211,7 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
StringBuilder sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
foreach (var c in GetCues()) // 输出时去除空串
|
foreach (var c in GetCues()) // 输出时去除空串
|
||||||
{
|
{
|
||||||
sb.AppendLine(c.StartTime.ToString(@"hh\:mm\:ss\.fff") + " --> " + c.EndTime.ToString(@"hh\:mm\:ss\.fff") + " " + c.Settings);
|
sb.AppendLine(c.StartTime.ToString(@"hh\:mm\:ss\.fff") + " --> " + c.EndTime.ToString(@"hh\:mm\:ss\.fff") + " " + c.Settings);
|
||||||
@ -269,4 +266,3 @@ namespace N_m3u8DL_RE.Common.Entity
|
|||||||
return srt;
|
return srt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Common.Enum;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Enum
|
|
||||||
{
|
|
||||||
public enum Choise
|
public enum Choise
|
||||||
{
|
{
|
||||||
YES = 1,
|
YES = 1,
|
||||||
NO = 0
|
NO = 0
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Common.Enum;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Enum
|
|
||||||
{
|
|
||||||
public enum EncryptMethod
|
public enum EncryptMethod
|
||||||
{
|
{
|
||||||
NONE,
|
NONE,
|
||||||
@ -17,4 +11,3 @@ namespace N_m3u8DL_RE.Common.Enum
|
|||||||
CHACHA20,
|
CHACHA20,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Common.Enum;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Enum
|
|
||||||
{
|
|
||||||
public enum ExtractorType
|
public enum ExtractorType
|
||||||
{
|
{
|
||||||
MPEG_DASH,
|
MPEG_DASH,
|
||||||
@ -13,4 +7,3 @@ namespace N_m3u8DL_RE.Common.Enum
|
|||||||
HTTP_LIVE,
|
HTTP_LIVE,
|
||||||
MSS
|
MSS
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Common.Enum;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Enum
|
|
||||||
{
|
|
||||||
public enum MediaType
|
public enum MediaType
|
||||||
{
|
{
|
||||||
AUDIO = 0,
|
AUDIO = 0,
|
||||||
@ -13,4 +7,3 @@ namespace N_m3u8DL_RE.Common.Enum
|
|||||||
SUBTITLES = 2,
|
SUBTITLES = 2,
|
||||||
CLOSED_CAPTIONS = 3
|
CLOSED_CAPTIONS = 3
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
namespace N_m3u8DL_RE.Common.Enum
|
namespace N_m3u8DL_RE.Common.Enum;
|
||||||
{
|
|
||||||
public enum RoleType
|
public enum RoleType
|
||||||
{
|
{
|
||||||
Subtitle = 0,
|
Subtitle = 0,
|
||||||
@ -11,5 +11,5 @@
|
|||||||
Description = 6,
|
Description = 6,
|
||||||
Sign = 7,
|
Sign = 7,
|
||||||
Metadata = 8,
|
Metadata = 8,
|
||||||
}
|
ForcedSubtitle = 9
|
||||||
}
|
}
|
@ -2,8 +2,8 @@
|
|||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common
|
namespace N_m3u8DL_RE.Common;
|
||||||
{
|
|
||||||
[JsonSourceGenerationOptions(
|
[JsonSourceGenerationOptions(
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
|
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
|
||||||
@ -19,4 +19,3 @@ namespace N_m3u8DL_RE.Common
|
|||||||
[JsonSerializable(typeof(List<MediaSegment>))]
|
[JsonSerializable(typeof(List<MediaSegment>))]
|
||||||
[JsonSerializable(typeof(Dictionary<string, string>))]
|
[JsonSerializable(typeof(Dictionary<string, string>))]
|
||||||
internal partial class JsonContext : JsonSerializerContext { }
|
internal partial class JsonContext : JsonSerializerContext { }
|
||||||
}
|
|
||||||
|
@ -1,17 +1,11 @@
|
|||||||
using System;
|
using System.Text.Json;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.JsonConverter
|
namespace N_m3u8DL_RE.Common.JsonConverter;
|
||||||
{
|
|
||||||
internal class BytesBase64Converter : JsonConverter<byte[]>
|
internal class BytesBase64Converter : JsonConverter<byte[]>
|
||||||
{
|
{
|
||||||
public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.GetBytesFromBase64();
|
public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.GetBytesFromBase64();
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options) => writer.WriteStringValue(Convert.ToBase64String(value));
|
public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options) => writer.WriteStringValue(Convert.ToBase64String(value));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -4,39 +4,46 @@ using Spectre.Console;
|
|||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Log;
|
namespace N_m3u8DL_RE.Common.Log;
|
||||||
|
|
||||||
public class NonAnsiWriter : TextWriter
|
public partial class NonAnsiWriter : TextWriter
|
||||||
{
|
{
|
||||||
public override Encoding Encoding => Console.OutputEncoding;
|
public override Encoding Encoding => Console.OutputEncoding;
|
||||||
|
|
||||||
private string lastOut = "";
|
private string? _lastOut = "";
|
||||||
|
|
||||||
public override void Write(char value)
|
public override void Write(char value)
|
||||||
{
|
{
|
||||||
Console.Write(value);
|
Console.Write(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(string value)
|
public override void Write(string? value)
|
||||||
{
|
{
|
||||||
if (lastOut == value)
|
if (_lastOut == value)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastOut = value;
|
_lastOut = value;
|
||||||
RemoveAnsiEscapeSequences(value);
|
RemoveAnsiEscapeSequences(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveAnsiEscapeSequences(string input)
|
private void RemoveAnsiEscapeSequences(string? input)
|
||||||
{
|
{
|
||||||
// Use regular expression to remove ANSI escape sequences
|
// Use regular expression to remove ANSI escape sequences
|
||||||
string output = Regex.Replace(input, @"\x1B\[(\d+;?)+m", "");
|
var output = MyRegex().Replace(input ?? "", "");
|
||||||
output = Regex.Replace(output, @"\[\??\d+[AKlh]", "");
|
output = MyRegex1().Replace(output, "");
|
||||||
output = Regex.Replace(output,"[\r\n] +","");
|
output = MyRegex2().Replace(output, "");
|
||||||
if (string.IsNullOrWhiteSpace(output))
|
if (string.IsNullOrWhiteSpace(output))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Console.Write(output);
|
Console.Write(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\x1B\[(\d+;?)+m")]
|
||||||
|
private static partial Regex MyRegex();
|
||||||
|
[GeneratedRegex(@"\[\??\d+[AKlh]")]
|
||||||
|
private static partial Regex MyRegex1();
|
||||||
|
[GeneratedRegex("[\r\n] +")]
|
||||||
|
private static partial Regex MyRegex2();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Common.Log;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Log
|
|
||||||
{
|
|
||||||
public enum LogLevel
|
public enum LogLevel
|
||||||
{
|
{
|
||||||
OFF,
|
OFF,
|
||||||
@ -14,4 +8,3 @@ namespace N_m3u8DL_RE.Common.Log
|
|||||||
INFO,
|
INFO,
|
||||||
DEBUG,
|
DEBUG,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using static System.Net.Mime.MediaTypeNames;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Log
|
namespace N_m3u8DL_RE.Common.Log;
|
||||||
{
|
|
||||||
public partial class Logger
|
public static partial class Logger
|
||||||
{
|
{
|
||||||
[GeneratedRegex("{}")]
|
[GeneratedRegex("{}")]
|
||||||
private static partial Regex VarsRepRegex();
|
private static partial Regex VarsRepRegex();
|
||||||
@ -86,8 +80,8 @@ namespace N_m3u8DL_RE.Common.Log
|
|||||||
Console.WriteLine(subWrite);
|
Console.WriteLine(subWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsWriteFile && File.Exists(LogFilePath))
|
if (!IsWriteFile || !File.Exists(LogFilePath)) return;
|
||||||
{
|
|
||||||
var plain = write.RemoveMarkup() + subWrite.RemoveMarkup();
|
var plain = write.RemoveMarkup() + subWrite.RemoveMarkup();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -104,7 +98,6 @@ namespace N_m3u8DL_RE.Common.Log
|
|||||||
LogWriteLock.ExitWriteLock();
|
LogWriteLock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Failed to write: " + write);
|
Console.WriteLine("Failed to write: " + write);
|
||||||
@ -123,83 +116,75 @@ namespace N_m3u8DL_RE.Common.Log
|
|||||||
|
|
||||||
public static void Info(string data, params object[] ps)
|
public static void Info(string data, params object[] ps)
|
||||||
{
|
{
|
||||||
if (LogLevel >= LogLevel.INFO)
|
if (LogLevel < LogLevel.INFO) return;
|
||||||
{
|
|
||||||
data = ReplaceVars(data, ps);
|
data = ReplaceVars(data, ps);
|
||||||
var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : ";
|
var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : ";
|
||||||
HandleLog(write, data);
|
HandleLog(write, data);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void InfoMarkUp(string data, params object[] ps)
|
public static void InfoMarkUp(string data, params object[] ps)
|
||||||
{
|
{
|
||||||
if (LogLevel >= LogLevel.INFO)
|
if (LogLevel < LogLevel.INFO) return;
|
||||||
{
|
|
||||||
data = ReplaceVars(data, ps);
|
data = ReplaceVars(data, ps);
|
||||||
var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : " + data;
|
var write = GetCurrTime() + " " + "[underline #548c26]INFO[/] : " + data;
|
||||||
HandleLog(write);
|
HandleLog(write);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void Debug(string data, params object[] ps)
|
public static void Debug(string data, params object[] ps)
|
||||||
{
|
{
|
||||||
if (LogLevel >= LogLevel.DEBUG)
|
if (LogLevel < LogLevel.DEBUG) return;
|
||||||
{
|
|
||||||
data = ReplaceVars(data, ps);
|
data = ReplaceVars(data, ps);
|
||||||
var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: ";
|
var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: ";
|
||||||
HandleLog(write, data);
|
HandleLog(write, data);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void DebugMarkUp(string data, params object[] ps)
|
public static void DebugMarkUp(string data, params object[] ps)
|
||||||
{
|
{
|
||||||
if (LogLevel >= LogLevel.DEBUG)
|
if (LogLevel < LogLevel.DEBUG) return;
|
||||||
{
|
|
||||||
data = ReplaceVars(data, ps);
|
data = ReplaceVars(data, ps);
|
||||||
var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: " + data;
|
var write = GetCurrTime() + " " + "[underline grey]DEBUG[/]: " + data;
|
||||||
HandleLog(write);
|
HandleLog(write);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void Warn(string data, params object[] ps)
|
public static void Warn(string data, params object[] ps)
|
||||||
{
|
{
|
||||||
if (LogLevel >= LogLevel.WARN)
|
if (LogLevel < LogLevel.WARN) return;
|
||||||
{
|
|
||||||
data = ReplaceVars(data, ps);
|
data = ReplaceVars(data, ps);
|
||||||
var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : ";
|
var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : ";
|
||||||
HandleLog(write, data);
|
HandleLog(write, data);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void WarnMarkUp(string data, params object[] ps)
|
public static void WarnMarkUp(string data, params object[] ps)
|
||||||
{
|
{
|
||||||
if (LogLevel >= LogLevel.WARN)
|
if (LogLevel < LogLevel.WARN) return;
|
||||||
{
|
|
||||||
data = ReplaceVars(data, ps);
|
data = ReplaceVars(data, ps);
|
||||||
var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : " + data;
|
var write = GetCurrTime() + " " + "[underline #a89022]WARN[/] : " + data;
|
||||||
HandleLog(write);
|
HandleLog(write);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void Error(string data, params object[] ps)
|
public static void Error(string data, params object[] ps)
|
||||||
{
|
{
|
||||||
if (LogLevel >= LogLevel.ERROR)
|
if (LogLevel < LogLevel.ERROR) return;
|
||||||
{
|
|
||||||
data = ReplaceVars(data, ps);
|
data = ReplaceVars(data, ps);
|
||||||
var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: ";
|
var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: ";
|
||||||
HandleLog(write, data);
|
HandleLog(write, data);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void ErrorMarkUp(string data, params object[] ps)
|
public static void ErrorMarkUp(string data, params object[] ps)
|
||||||
{
|
{
|
||||||
if (LogLevel >= LogLevel.ERROR)
|
if (LogLevel < LogLevel.ERROR) return;
|
||||||
{
|
|
||||||
data = ReplaceVars(data, ps);
|
data = ReplaceVars(data, ps);
|
||||||
var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: " + data;
|
var write = GetCurrTime() + " " + "[underline red1]ERROR[/]: " + data;
|
||||||
HandleLog(write);
|
HandleLog(write);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void ErrorMarkUp(Exception exception)
|
public static void ErrorMarkUp(Exception exception)
|
||||||
{
|
{
|
||||||
@ -219,8 +204,8 @@ namespace N_m3u8DL_RE.Common.Log
|
|||||||
/// <param name="ps"></param>
|
/// <param name="ps"></param>
|
||||||
public static void Extra(string data, params object[] ps)
|
public static void Extra(string data, params object[] ps)
|
||||||
{
|
{
|
||||||
if (IsWriteFile && File.Exists(LogFilePath))
|
if (!IsWriteFile || !File.Exists(LogFilePath)) return;
|
||||||
{
|
|
||||||
data = ReplaceVars(data, ps);
|
data = ReplaceVars(data, ps);
|
||||||
var plain = GetCurrTime() + " " + "EXTRA: " + data.RemoveMarkup();
|
var plain = GetCurrTime() + " " + "EXTRA: " + data.RemoveMarkup();
|
||||||
try
|
try
|
||||||
@ -239,5 +224,3 @@ namespace N_m3u8DL_RE.Common.Log
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>library</OutputType>
|
<OutputType>library</OutputType>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<RootNamespace>N_m3u8DL_RE.Common</RootNamespace>
|
<RootNamespace>N_m3u8DL_RE.Common</RootNamespace>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Spectre.Console" Version="0.47.1-preview.0.11" />
|
<PackageReference Include="Spectre.Console" Version="0.49.2-preview.0.50" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,151 +1,150 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Common.Resource;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Resource
|
public static class ResString
|
||||||
{
|
{
|
||||||
public class ResString
|
public static string CurrentLoc { get; set; } = "en-US";
|
||||||
{
|
|
||||||
public readonly static string ReLiveTs = "<RE_LIVE_TS>";
|
public static readonly string ReLiveTs = "<RE_LIVE_TS>";
|
||||||
public static string singleFileRealtimeDecryptWarn { get => GetText("singleFileRealtimeDecryptWarn"); }
|
public static string singleFileRealtimeDecryptWarn => GetText("singleFileRealtimeDecryptWarn");
|
||||||
public static string singleFileSplitWarn { get => GetText("singleFileSplitWarn"); }
|
public static string singleFileSplitWarn => GetText("singleFileSplitWarn");
|
||||||
public static string customRangeWarn { get => GetText("customRangeWarn"); }
|
public static string customRangeWarn => GetText("customRangeWarn");
|
||||||
public static string customRangeFound { get => GetText("customRangeFound"); }
|
public static string customRangeFound => GetText("customRangeFound");
|
||||||
public static string customAdKeywordsFound { get => GetText("customAdKeywordsFound"); }
|
public static string customAdKeywordsFound => GetText("customAdKeywordsFound");
|
||||||
public static string customRangeInvalid { get => GetText("customRangeInvalid"); }
|
public static string customRangeInvalid => GetText("customRangeInvalid");
|
||||||
public static string consoleRedirected { get => GetText("consoleRedirected"); }
|
public static string consoleRedirected => GetText("consoleRedirected");
|
||||||
public static string autoBinaryMerge { get => GetText("autoBinaryMerge"); }
|
public static string autoBinaryMerge => GetText("autoBinaryMerge");
|
||||||
public static string autoBinaryMerge2 { get => GetText("autoBinaryMerge2"); }
|
public static string autoBinaryMerge2 => GetText("autoBinaryMerge2");
|
||||||
public static string autoBinaryMerge3 { get => GetText("autoBinaryMerge3"); }
|
public static string autoBinaryMerge3 => GetText("autoBinaryMerge3");
|
||||||
public static string autoBinaryMerge4 { get => GetText("autoBinaryMerge4"); }
|
public static string autoBinaryMerge4 => GetText("autoBinaryMerge4");
|
||||||
public static string autoBinaryMerge5 { get => GetText("autoBinaryMerge5"); }
|
public static string autoBinaryMerge5 => GetText("autoBinaryMerge5");
|
||||||
public static string autoBinaryMerge6 { get => GetText("autoBinaryMerge6"); }
|
public static string autoBinaryMerge6 => GetText("autoBinaryMerge6");
|
||||||
public static string badM3u8 { get => GetText("badM3u8"); }
|
public static string badM3u8 => GetText("badM3u8");
|
||||||
public static string binaryMerge { get => GetText("binaryMerge"); }
|
public static string binaryMerge => GetText("binaryMerge");
|
||||||
public static string checkingLast { get => GetText("checkingLast"); }
|
public static string checkingLast => GetText("checkingLast");
|
||||||
public static string cmd_appendUrlParams { get => GetText("cmd_appendUrlParams"); }
|
public static string cmd_appendUrlParams => GetText("cmd_appendUrlParams");
|
||||||
public static string cmd_autoSelect { get => GetText("cmd_autoSelect"); }
|
public static string cmd_autoSelect => GetText("cmd_autoSelect");
|
||||||
public static string cmd_binaryMerge { get => GetText("cmd_binaryMerge"); }
|
public static string cmd_disableUpdateCheck => GetText("cmd_disableUpdateCheck");
|
||||||
public static string cmd_useFFmpegConcatDemuxer { get => GetText("cmd_useFFmpegConcatDemuxer"); }
|
public static string cmd_binaryMerge => GetText("cmd_binaryMerge");
|
||||||
public static string cmd_checkSegmentsCount { get => GetText("cmd_checkSegmentsCount"); }
|
public static string cmd_useFFmpegConcatDemuxer => GetText("cmd_useFFmpegConcatDemuxer");
|
||||||
public static string cmd_decryptionBinaryPath { get => GetText("cmd_decryptionBinaryPath"); }
|
public static string cmd_checkSegmentsCount => GetText("cmd_checkSegmentsCount");
|
||||||
public static string cmd_delAfterDone { get => GetText("cmd_delAfterDone"); }
|
public static string cmd_decryptionBinaryPath => GetText("cmd_decryptionBinaryPath");
|
||||||
public static string cmd_ffmpegBinaryPath { get => GetText("cmd_ffmpegBinaryPath"); }
|
public static string cmd_delAfterDone => GetText("cmd_delAfterDone");
|
||||||
public static string cmd_mkvmergeBinaryPath { get => GetText("cmd_mkvmergeBinaryPath"); }
|
public static string cmd_ffmpegBinaryPath => GetText("cmd_ffmpegBinaryPath");
|
||||||
public static string cmd_baseUrl { get => GetText("cmd_baseUrl"); }
|
public static string cmd_mkvmergeBinaryPath => GetText("cmd_mkvmergeBinaryPath");
|
||||||
public static string cmd_maxSpeed { get => GetText("cmd_maxSpeed"); }
|
public static string cmd_baseUrl => GetText("cmd_baseUrl");
|
||||||
public static string cmd_adKeyword { get => GetText("cmd_adKeyword"); }
|
public static string cmd_maxSpeed => GetText("cmd_maxSpeed");
|
||||||
public static string cmd_moreHelp { get => GetText("cmd_moreHelp"); }
|
public static string cmd_adKeyword => GetText("cmd_adKeyword");
|
||||||
public static string cmd_header { get => GetText("cmd_header"); }
|
public static string cmd_moreHelp => GetText("cmd_moreHelp");
|
||||||
public static string cmd_muxImport { get => GetText("cmd_muxImport"); }
|
public static string cmd_header => GetText("cmd_header");
|
||||||
public static string cmd_muxImport_more { get => GetText("cmd_muxImport_more"); }
|
public static string cmd_muxImport => GetText("cmd_muxImport");
|
||||||
public static string cmd_selectVideo { get => GetText("cmd_selectVideo"); }
|
public static string cmd_muxImport_more => GetText("cmd_muxImport_more");
|
||||||
public static string cmd_dropVideo { get => GetText("cmd_dropVideo"); }
|
public static string cmd_selectVideo => GetText("cmd_selectVideo");
|
||||||
public static string cmd_selectVideo_more { get => GetText("cmd_selectVideo_more"); }
|
public static string cmd_dropVideo => GetText("cmd_dropVideo");
|
||||||
public static string cmd_selectAudio { get => GetText("cmd_selectAudio"); }
|
public static string cmd_selectVideo_more => GetText("cmd_selectVideo_more");
|
||||||
public static string cmd_dropAudio { get => GetText("cmd_dropAudio"); }
|
public static string cmd_selectAudio => GetText("cmd_selectAudio");
|
||||||
public static string cmd_selectAudio_more { get => GetText("cmd_selectAudio_more"); }
|
public static string cmd_dropAudio => GetText("cmd_dropAudio");
|
||||||
public static string cmd_selectSubtitle { get => GetText("cmd_selectSubtitle"); }
|
public static string cmd_selectAudio_more => GetText("cmd_selectAudio_more");
|
||||||
public static string cmd_dropSubtitle { get => GetText("cmd_dropSubtitle"); }
|
public static string cmd_selectSubtitle => GetText("cmd_selectSubtitle");
|
||||||
public static string cmd_selectSubtitle_more { get => GetText("cmd_selectSubtitle_more"); }
|
public static string cmd_dropSubtitle => GetText("cmd_dropSubtitle");
|
||||||
public static string cmd_custom_range { get => GetText("cmd_custom_range"); }
|
public static string cmd_selectSubtitle_more => GetText("cmd_selectSubtitle_more");
|
||||||
public static string cmd_customHLSMethod { get => GetText("cmd_customHLSMethod"); }
|
public static string cmd_custom_range => GetText("cmd_custom_range");
|
||||||
public static string cmd_customHLSKey { get => GetText("cmd_customHLSKey"); }
|
public static string cmd_customHLSMethod => GetText("cmd_customHLSMethod");
|
||||||
public static string cmd_customHLSIv { get => GetText("cmd_customHLSIv"); }
|
public static string cmd_customHLSKey => GetText("cmd_customHLSKey");
|
||||||
public static string cmd_Input { get => GetText("cmd_Input"); }
|
public static string cmd_customHLSIv => GetText("cmd_customHLSIv");
|
||||||
public static string cmd_forceAnsiConsole { get => GetText("cmd_forceAnsiConsole"); }
|
public static string cmd_Input => GetText("cmd_Input");
|
||||||
public static string cmd_noAnsiColor { get => GetText("cmd_noAnsiColor"); }
|
public static string cmd_forceAnsiConsole => GetText("cmd_forceAnsiConsole");
|
||||||
public static string cmd_keys { get => GetText("cmd_keys"); }
|
public static string cmd_noAnsiColor => GetText("cmd_noAnsiColor");
|
||||||
public static string cmd_keyText { get => GetText("cmd_keyText"); }
|
public static string cmd_keys => GetText("cmd_keys");
|
||||||
public static string cmd_loadKeyFailed { get => GetText("cmd_loadKeyFailed"); }
|
public static string cmd_keyText => GetText("cmd_keyText");
|
||||||
public static string cmd_logLevel { get => GetText("cmd_logLevel"); }
|
public static string cmd_loadKeyFailed => GetText("cmd_loadKeyFailed");
|
||||||
public static string cmd_MP4RealTimeDecryption { get => GetText("cmd_MP4RealTimeDecryption"); }
|
public static string cmd_logLevel => GetText("cmd_logLevel");
|
||||||
public static string cmd_saveDir { get => GetText("cmd_saveDir"); }
|
public static string cmd_MP4RealTimeDecryption => GetText("cmd_MP4RealTimeDecryption");
|
||||||
public static string cmd_saveName { get => GetText("cmd_saveName"); }
|
public static string cmd_saveDir => GetText("cmd_saveDir");
|
||||||
public static string cmd_savePattern { get => GetText("cmd_savePattern"); }
|
public static string cmd_saveName => GetText("cmd_saveName");
|
||||||
public static string cmd_skipDownload { get => GetText("cmd_skipDownload"); }
|
public static string cmd_savePattern => GetText("cmd_savePattern");
|
||||||
public static string cmd_noDateInfo { get => GetText("cmd_noDateInfo"); }
|
public static string cmd_skipDownload => GetText("cmd_skipDownload");
|
||||||
public static string cmd_noLog { get => GetText("cmd_noLog"); }
|
public static string cmd_noDateInfo => GetText("cmd_noDateInfo");
|
||||||
public static string cmd_skipMerge { get => GetText("cmd_skipMerge"); }
|
public static string cmd_noLog => GetText("cmd_noLog");
|
||||||
public static string cmd_subFormat { get => GetText("cmd_subFormat"); }
|
public static string cmd_allowHlsMultiExtMap => GetText("cmd_allowHlsMultiExtMap");
|
||||||
public static string cmd_subOnly { get => GetText("cmd_subOnly"); }
|
public static string cmd_skipMerge => GetText("cmd_skipMerge");
|
||||||
public static string cmd_subtitleFix { get => GetText("cmd_subtitleFix"); }
|
public static string cmd_subFormat => GetText("cmd_subFormat");
|
||||||
public static string cmd_threadCount { get => GetText("cmd_threadCount"); }
|
public static string cmd_subOnly => GetText("cmd_subOnly");
|
||||||
public static string cmd_downloadRetryCount { get => GetText("cmd_downloadRetryCount"); }
|
public static string cmd_subtitleFix => GetText("cmd_subtitleFix");
|
||||||
public static string cmd_tmpDir { get => GetText("cmd_tmpDir"); }
|
public static string cmd_threadCount => GetText("cmd_threadCount");
|
||||||
public static string cmd_uiLanguage { get => GetText("cmd_uiLanguage"); }
|
public static string cmd_downloadRetryCount => GetText("cmd_downloadRetryCount");
|
||||||
public static string cmd_urlProcessorArgs { get => GetText("cmd_urlProcessorArgs"); }
|
public static string cmd_httpRequestTimeout => GetText("cmd_httpRequestTimeout");
|
||||||
public static string cmd_useShakaPackager { get => GetText("cmd_useShakaPackager"); }
|
public static string cmd_tmpDir => GetText("cmd_tmpDir");
|
||||||
public static string cmd_concurrentDownload { get => GetText("cmd_concurrentDownload"); }
|
public static string cmd_uiLanguage => GetText("cmd_uiLanguage");
|
||||||
public static string cmd_useSystemProxy { get => GetText("cmd_useSystemProxy"); }
|
public static string cmd_urlProcessorArgs => GetText("cmd_urlProcessorArgs");
|
||||||
public static string cmd_customProxy { get => GetText("cmd_customProxy"); }
|
public static string cmd_useShakaPackager => GetText("cmd_useShakaPackager");
|
||||||
public static string cmd_customRange { get => GetText("cmd_customRange"); }
|
public static string cmd_decryptionEngine => GetText("cmd_decryptionEngine");
|
||||||
public static string cmd_liveKeepSegments { get => GetText("cmd_liveKeepSegments"); }
|
public static string cmd_concurrentDownload => GetText("cmd_concurrentDownload");
|
||||||
public static string cmd_livePipeMux { get => GetText("cmd_livePipeMux"); }
|
public static string cmd_useSystemProxy => GetText("cmd_useSystemProxy");
|
||||||
public static string cmd_liveRecordLimit { get => GetText("cmd_liveRecordLimit"); }
|
public static string cmd_customProxy => GetText("cmd_customProxy");
|
||||||
public static string cmd_taskStartAt { get => GetText("cmd_taskStartAt"); }
|
public static string cmd_customRange => GetText("cmd_customRange");
|
||||||
public static string cmd_liveWaitTime { get => GetText("cmd_liveWaitTime"); }
|
public static string cmd_liveKeepSegments => GetText("cmd_liveKeepSegments");
|
||||||
public static string cmd_liveTakeCount { get => GetText("cmd_liveTakeCount"); }
|
public static string cmd_livePipeMux => GetText("cmd_livePipeMux");
|
||||||
public static string cmd_liveFixVttByAudio { get => GetText("cmd_liveFixVttByAudio"); }
|
public static string cmd_liveRecordLimit => GetText("cmd_liveRecordLimit");
|
||||||
public static string cmd_liveRealTimeMerge { get => GetText("cmd_liveRealTimeMerge"); }
|
public static string cmd_taskStartAt => GetText("cmd_taskStartAt");
|
||||||
public static string cmd_livePerformAsVod { get => GetText("cmd_livePerformAsVod"); }
|
public static string cmd_liveWaitTime => GetText("cmd_liveWaitTime");
|
||||||
public static string cmd_muxAfterDone { get => GetText("cmd_muxAfterDone"); }
|
public static string cmd_liveTakeCount => GetText("cmd_liveTakeCount");
|
||||||
public static string cmd_muxAfterDone_more { get => GetText("cmd_muxAfterDone_more"); }
|
public static string cmd_liveFixVttByAudio => GetText("cmd_liveFixVttByAudio");
|
||||||
public static string cmd_writeMetaJson { get => GetText("cmd_writeMetaJson"); }
|
public static string cmd_liveRealTimeMerge => GetText("cmd_liveRealTimeMerge");
|
||||||
public static string liveLimit { get => GetText("liveLimit"); }
|
public static string cmd_livePerformAsVod => GetText("cmd_livePerformAsVod");
|
||||||
public static string realTimeDecMessage { get => GetText("realTimeDecMessage"); }
|
public static string cmd_muxAfterDone => GetText("cmd_muxAfterDone");
|
||||||
public static string liveLimitReached { get => GetText("liveLimitReached"); }
|
public static string cmd_muxAfterDone_more => GetText("cmd_muxAfterDone_more");
|
||||||
public static string saveName { get => GetText("saveName"); }
|
public static string cmd_writeMetaJson => GetText("cmd_writeMetaJson");
|
||||||
public static string taskStartAt { get => GetText("taskStartAt"); }
|
public static string liveLimit => GetText("liveLimit");
|
||||||
public static string namedPipeCreated { get => GetText("namedPipeCreated"); }
|
public static string realTimeDecMessage => GetText("realTimeDecMessage");
|
||||||
public static string namedPipeMux { get => GetText("namedPipeMux"); }
|
public static string liveLimitReached => GetText("liveLimitReached");
|
||||||
public static string partMerge { get => GetText("partMerge"); }
|
public static string saveName => GetText("saveName");
|
||||||
public static string fetch { get => GetText("fetch"); }
|
public static string taskStartAt => GetText("taskStartAt");
|
||||||
public static string ffmpegMerge { get => GetText("ffmpegMerge"); }
|
public static string namedPipeCreated => GetText("namedPipeCreated");
|
||||||
public static string ffmpegNotFound { get => GetText("ffmpegNotFound"); }
|
public static string namedPipeMux => GetText("namedPipeMux");
|
||||||
public static string fixingTTML { get => GetText("fixingTTML"); }
|
public static string partMerge => GetText("partMerge");
|
||||||
public static string fixingTTMLmp4 { get => GetText("fixingTTMLmp4"); }
|
public static string fetch => GetText("fetch");
|
||||||
public static string fixingVTT { get => GetText("fixingVTT"); }
|
public static string ffmpegMerge => GetText("ffmpegMerge");
|
||||||
public static string fixingVTTmp4 { get => GetText("fixingVTTmp4"); }
|
public static string ffmpegNotFound => GetText("ffmpegNotFound");
|
||||||
public static string keyProcessorNotFound { get => GetText("keyProcessorNotFound"); }
|
public static string mkvmergeNotFound => GetText("mkvmergeNotFound");
|
||||||
public static string liveFound { get => GetText("liveFound"); }
|
public static string mp4decryptNotFound => GetText("mp4decryptNotFound");
|
||||||
public static string loadingUrl { get => GetText("loadingUrl"); }
|
public static string shakaPackagerNotFound => GetText("shakaPackagerNotFound");
|
||||||
public static string masterM3u8Found { get => GetText("masterM3u8Found"); }
|
public static string fixingTTML => GetText("fixingTTML");
|
||||||
public static string matchDASH { get => GetText("matchDASH"); }
|
public static string fixingTTMLmp4 => GetText("fixingTTMLmp4");
|
||||||
public static string matchMSS { get => GetText("matchMSS"); }
|
public static string fixingVTT => GetText("fixingVTT");
|
||||||
public static string matchTS { get => GetText("matchTS"); }
|
public static string fixingVTTmp4 => GetText("fixingVTTmp4");
|
||||||
public static string matchHLS { get => GetText("matchHLS"); }
|
public static string keyProcessorNotFound => GetText("keyProcessorNotFound");
|
||||||
public static string notSupported { get => GetText("notSupported"); }
|
public static string liveFound => GetText("liveFound");
|
||||||
public static string parsingStream { get => GetText("parsingStream"); }
|
public static string loadingUrl => GetText("loadingUrl");
|
||||||
public static string promptChoiceText { get => GetText("promptChoiceText"); }
|
public static string masterM3u8Found => GetText("masterM3u8Found");
|
||||||
public static string promptInfo { get => GetText("promptInfo"); }
|
public static string allowHlsMultiExtMap => GetText("allowHlsMultiExtMap");
|
||||||
public static string promptTitle { get => GetText("promptTitle"); }
|
public static string matchDASH => GetText("matchDASH");
|
||||||
public static string readingInfo { get => GetText("readingInfo"); }
|
public static string matchMSS => GetText("matchMSS");
|
||||||
public static string searchKey { get => GetText("searchKey"); }
|
public static string matchTS => GetText("matchTS");
|
||||||
public static string segmentCountCheckNotPass { get => GetText("segmentCountCheckNotPass"); }
|
public static string matchHLS => GetText("matchHLS");
|
||||||
public static string selectedStream { get => GetText("selectedStream"); }
|
public static string notSupported => GetText("notSupported");
|
||||||
public static string startDownloading { get => GetText("startDownloading"); }
|
public static string parsingStream => GetText("parsingStream");
|
||||||
public static string streamsInfo { get => GetText("streamsInfo"); }
|
public static string promptChoiceText => GetText("promptChoiceText");
|
||||||
public static string writeJson { get => GetText("writeJson"); }
|
public static string promptInfo => GetText("promptInfo");
|
||||||
public static string noStreamsToDownload { get => GetText("noStreamsToDownload"); }
|
public static string promptTitle => GetText("promptTitle");
|
||||||
public static string newVersionFound { get => GetText("newVersionFound"); }
|
public static string readingInfo => GetText("readingInfo");
|
||||||
public static string processImageSub { get => GetText("processImageSub"); }
|
public static string searchKey => GetText("searchKey");
|
||||||
|
public static string decryptionFailed => GetText("decryptionFailed");
|
||||||
|
public static string segmentCountCheckNotPass => GetText("segmentCountCheckNotPass");
|
||||||
|
public static string selectedStream => GetText("selectedStream");
|
||||||
|
public static string startDownloading => GetText("startDownloading");
|
||||||
|
public static string streamsInfo => GetText("streamsInfo");
|
||||||
|
public static string writeJson => GetText("writeJson");
|
||||||
|
public static string noStreamsToDownload => GetText("noStreamsToDownload");
|
||||||
|
public static string newVersionFound => GetText("newVersionFound");
|
||||||
|
public static string processImageSub => GetText("processImageSub");
|
||||||
|
|
||||||
private static string GetText(string key)
|
private static string GetText(string key)
|
||||||
{
|
{
|
||||||
if (!StaticText.LANG_DIC.ContainsKey(key))
|
if (!StaticText.LANG_DIC.TryGetValue(key, out var textObj))
|
||||||
return "<...LANG TEXT MISSING...>";
|
return "<...LANG TEXT MISSING...>";
|
||||||
|
|
||||||
var current = Thread.CurrentThread.CurrentUICulture.Name;
|
if (CurrentLoc is "zh-CN" or "zh-SG" or "zh-Hans")
|
||||||
if (current == "zh-CN" || current == "zh-SG" || current == "zh-Hans")
|
return textObj.ZH_CN;
|
||||||
return StaticText.LANG_DIC[key].ZH_CN;
|
return CurrentLoc.StartsWith("zh-") ? textObj.ZH_TW : textObj.EN_US;
|
||||||
else if (current.StartsWith("zh-"))
|
|
||||||
return StaticText.LANG_DIC[key].ZH_TW;
|
|
||||||
else
|
|
||||||
return StaticText.LANG_DIC[key].EN_US;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,14 +1,8 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Common.Resource;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Resource
|
internal static class StaticText
|
||||||
{
|
{
|
||||||
internal class StaticText
|
public static readonly Dictionary<string, TextContainer> LANG_DIC = new()
|
||||||
{
|
|
||||||
public static Dictionary<string, TextContainer> LANG_DIC = new()
|
|
||||||
{
|
{
|
||||||
["singleFileSplitWarn"] = new TextContainer
|
["singleFileSplitWarn"] = new TextContainer
|
||||||
(
|
(
|
||||||
@ -172,6 +166,12 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
zhTW: "關閉日誌文件輸出",
|
zhTW: "關閉日誌文件輸出",
|
||||||
enUS: "Disable log file output"
|
enUS: "Disable log file output"
|
||||||
),
|
),
|
||||||
|
["cmd_allowHlsMultiExtMap"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "允许HLS中的多个#EXT-X-MAP(实验性)",
|
||||||
|
zhTW: "允許HLS中的多個#EXT-X-MAP(實驗性)",
|
||||||
|
enUS: "Allow multiple #EXT-X-MAP in HLS (experimental)"
|
||||||
|
),
|
||||||
["cmd_appendUrlParams"] = new TextContainer
|
["cmd_appendUrlParams"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "将输入Url的Params添加至分片, 对某些网站很有用, 例如 kakao.com",
|
zhCN: "将输入Url的Params添加至分片, 对某些网站很有用, 例如 kakao.com",
|
||||||
@ -184,6 +184,12 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
zhTW: "自動選擇所有類型的最佳軌道",
|
zhTW: "自動選擇所有類型的最佳軌道",
|
||||||
enUS: "Automatically selects the best tracks of all types"
|
enUS: "Automatically selects the best tracks of all types"
|
||||||
),
|
),
|
||||||
|
["cmd_disableUpdateCheck"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "禁用版本更新检测",
|
||||||
|
zhTW: "禁用版本更新檢測",
|
||||||
|
enUS: "Disable version update check"
|
||||||
|
),
|
||||||
["cmd_binaryMerge"] = new TextContainer
|
["cmd_binaryMerge"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "二进制合并",
|
zhCN: "二进制合并",
|
||||||
@ -208,11 +214,17 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
zhTW: "每個分片下載異常時的重試次數",
|
zhTW: "每個分片下載異常時的重試次數",
|
||||||
enUS: "The number of retries when download segment error"
|
enUS: "The number of retries when download segment error"
|
||||||
),
|
),
|
||||||
|
["cmd_httpRequestTimeout"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "HTTP请求的超时时间(秒)",
|
||||||
|
zhTW: "HTTP請求的超時時間(秒)",
|
||||||
|
enUS: "Timeout duration for HTTP requests (in seconds)"
|
||||||
|
),
|
||||||
["cmd_decryptionBinaryPath"] = new TextContainer
|
["cmd_decryptionBinaryPath"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "MP4解密所用工具的全路径, 例如 C:\\Tools\\mp4decrypt.exe",
|
zhCN: @"MP4解密所用工具的全路径, 例如 C:\Tools\mp4decrypt.exe",
|
||||||
zhTW: "MP4解密所用工具的全路徑, 例如 C:\\Tools\\mp4decrypt.exe",
|
zhTW: @"MP4解密所用工具的全路徑, 例如 C:\Tools\mp4decrypt.exe",
|
||||||
enUS: "Full path to the tool used for MP4 decryption, like C:\\Tools\\mp4decrypt.exe"
|
enUS: @"Full path to the tool used for MP4 decryption, like C:\Tools\mp4decrypt.exe"
|
||||||
),
|
),
|
||||||
["cmd_delAfterDone"] = new TextContainer
|
["cmd_delAfterDone"] = new TextContainer
|
||||||
(
|
(
|
||||||
@ -222,15 +234,15 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
),
|
),
|
||||||
["cmd_ffmpegBinaryPath"] = new TextContainer
|
["cmd_ffmpegBinaryPath"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "ffmpeg可执行程序全路径, 例如 C:\\Tools\\ffmpeg.exe",
|
zhCN: @"ffmpeg可执行程序全路径, 例如 C:\Tools\ffmpeg.exe",
|
||||||
zhTW: "ffmpeg可執行程序全路徑, 例如 C:\\Tools\\ffmpeg.exe",
|
zhTW: @"ffmpeg可執行程序全路徑, 例如 C:\Tools\ffmpeg.exe",
|
||||||
enUS: "Full path to the ffmpeg binary, like C:\\Tools\\ffmpeg.exe"
|
enUS: @"Full path to the ffmpeg binary, like C:\Tools\ffmpeg.exe"
|
||||||
),
|
),
|
||||||
["cmd_mkvmergeBinaryPath"] = new TextContainer
|
["cmd_mkvmergeBinaryPath"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "mkvmerge可执行程序全路径, 例如 C:\\Tools\\mkvmerge.exe",
|
zhCN: @"mkvmerge可执行程序全路径, 例如 C:\Tools\mkvmerge.exe",
|
||||||
zhTW: "mkvmerge可執行程序全路徑, 例如 C:\\Tools\\mkvmerge.exe",
|
zhTW: @"mkvmerge可執行程序全路徑, 例如 C:\Tools\mkvmerge.exe",
|
||||||
enUS: "Full path to the mkvmerge binary, like C:\\Tools\\mkvmerge.exe"
|
enUS: @"Full path to the mkvmerge binary, like C:\Tools\mkvmerge.exe"
|
||||||
),
|
),
|
||||||
["cmd_liveFixVttByAudio"] = new TextContainer
|
["cmd_liveFixVttByAudio"] = new TextContainer
|
||||||
(
|
(
|
||||||
@ -252,9 +264,9 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
),
|
),
|
||||||
["cmd_keys"] = new TextContainer
|
["cmd_keys"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "设置解密密钥, 程序调用mp4decrpyt/shaka-packager进行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2",
|
zhCN: "设置解密密钥, 程序调用mp4decrpyt/shaka-packager/ffmpeg进行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n对于KEY相同的情况可以直接输入 --key KEY",
|
||||||
zhTW: "設置解密密鑰, 程序調用mp4decrpyt/shaka-packager進行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2",
|
zhTW: "設置解密密鑰, 程序調用mp4decrpyt/shaka-packager/ffmpeg進行解密. 格式:\r\n--key KID1:KEY1 --key KID2:KEY2\r\n對於KEY相同的情況可以直接輸入 --key KEY",
|
||||||
enUS: "Pass decryption key(s) to mp4decrypt/shaka-packager. format:\r\n--key KID1:KEY1 --key KID2:KEY2"
|
enUS: "Set decryption key(s) to mp4decrypt/shaka-packager/ffmpeg. format:\r\n--key KID1:KEY1 --key KID2:KEY2\r\nor use --key KEY if all tracks share the same key."
|
||||||
),
|
),
|
||||||
["cmd_keyText"] = new TextContainer
|
["cmd_keyText"] = new TextContainer
|
||||||
(
|
(
|
||||||
@ -454,6 +466,12 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
zhTW: "解密時使用shaka-packager替代mp4decrypt",
|
zhTW: "解密時使用shaka-packager替代mp4decrypt",
|
||||||
enUS: "Use shaka-packager instead of mp4decrypt to decrypt"
|
enUS: "Use shaka-packager instead of mp4decrypt to decrypt"
|
||||||
),
|
),
|
||||||
|
["cmd_decryptionEngine"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "设置解密时使用的第三方程序",
|
||||||
|
zhTW: "設置解密時使用的第三方程序",
|
||||||
|
enUS: "Set the third-party program used for decryption"
|
||||||
|
),
|
||||||
["cmd_concurrentDownload"] = new TextContainer
|
["cmd_concurrentDownload"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "并发下载已选择的音频、视频和字幕",
|
zhCN: "并发下载已选择的音频、视频和字幕",
|
||||||
@ -631,7 +649,7 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
["cmd_muxAfterDone_more"] = new TextContainer
|
["cmd_muxAfterDone_more"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "所有工作完成时尝试混流分离的音视频. 你能够以:分隔形式指定如下参数:\r\n\r\n" +
|
zhCN: "所有工作完成时尝试混流分离的音视频. 你能够以:分隔形式指定如下参数:\r\n\r\n" +
|
||||||
"* format=FORMAT: 指定混流容器 mkv, mp4\r\n" +
|
"* format=FORMAT: 指定混流容器 mkv, mp4, ts\r\n" +
|
||||||
"* muxer=MUXER: 指定混流程序 ffmpeg, mkvmerge (默认: ffmpeg)\r\n" +
|
"* muxer=MUXER: 指定混流程序 ffmpeg, mkvmerge (默认: ffmpeg)\r\n" +
|
||||||
"* bin_path=PATH: 指定程序路径 (默认: 自动寻找)\r\n" +
|
"* bin_path=PATH: 指定程序路径 (默认: 自动寻找)\r\n" +
|
||||||
"* skip_sub=BOOL: 是否忽略字幕文件 (默认: false)\r\n" +
|
"* skip_sub=BOOL: 是否忽略字幕文件 (默认: false)\r\n" +
|
||||||
@ -644,7 +662,7 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
"# 使用mkvmerge, 自定义程序路径\r\n" +
|
"# 使用mkvmerge, 自定义程序路径\r\n" +
|
||||||
"-M format=mkv:muxer=mkvmerge:bin_path=\"C\\:\\Program Files\\MKVToolNix\\mkvmerge.exe\"\r\n",
|
"-M format=mkv:muxer=mkvmerge:bin_path=\"C\\:\\Program Files\\MKVToolNix\\mkvmerge.exe\"\r\n",
|
||||||
zhTW: "所有工作完成時嘗試混流分離的影音. 你能夠以:分隔形式指定如下參數:\r\n\r\n" +
|
zhTW: "所有工作完成時嘗試混流分離的影音. 你能夠以:分隔形式指定如下參數:\r\n\r\n" +
|
||||||
"* format=FORMAT: 指定混流容器 mkv, mp4\r\n" +
|
"* format=FORMAT: 指定混流容器 mkv, mp4, ts\r\n" +
|
||||||
"* muxer=MUXER: 指定混流程序 ffmpeg, mkvmerge (默認: ffmpeg)\r\n" +
|
"* muxer=MUXER: 指定混流程序 ffmpeg, mkvmerge (默認: ffmpeg)\r\n" +
|
||||||
"* bin_path=PATH: 指定程序路徑 (默認: 自動尋找)\r\n" +
|
"* bin_path=PATH: 指定程序路徑 (默認: 自動尋找)\r\n" +
|
||||||
"* skip_sub=BOOL: 是否忽略字幕文件 (默認: false)\r\n" +
|
"* skip_sub=BOOL: 是否忽略字幕文件 (默認: false)\r\n" +
|
||||||
@ -657,7 +675,7 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
"# 使用mkvmerge, 自訂程序路徑\r\n" +
|
"# 使用mkvmerge, 自訂程序路徑\r\n" +
|
||||||
"-M format=mkv:muxer=mkvmerge:bin_path=\"C\\:\\Program Files\\MKVToolNix\\mkvmerge.exe\"\r\n",
|
"-M format=mkv:muxer=mkvmerge:bin_path=\"C\\:\\Program Files\\MKVToolNix\\mkvmerge.exe\"\r\n",
|
||||||
enUS: "When all works is done, try to mux the downloaded streams. OPTIONS is a colon separated list of:\r\n\r\n" +
|
enUS: "When all works is done, try to mux the downloaded streams. OPTIONS is a colon separated list of:\r\n\r\n" +
|
||||||
"* format=FORMAT: set container. mkv, mp4\r\n" +
|
"* format=FORMAT: set container. mkv, mp4, ts\r\n" +
|
||||||
"* muxer=MUXER: set muxer. ffmpeg, mkvmerge (Default: ffmpeg)\r\n" +
|
"* muxer=MUXER: set muxer. ffmpeg, mkvmerge (Default: ffmpeg)\r\n" +
|
||||||
"* bin_path=PATH: set binary file path. (Default: auto)\r\n" +
|
"* bin_path=PATH: set binary file path. (Default: auto)\r\n" +
|
||||||
"* skip_sub=BOOL: set whether or not skip subtitle files (Default: false)\r\n" +
|
"* skip_sub=BOOL: set whether or not skip subtitle files (Default: false)\r\n" +
|
||||||
@ -726,9 +744,9 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
),
|
),
|
||||||
["realTimeDecMessage"] = new TextContainer
|
["realTimeDecMessage"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "启用实时解密时,建议用shaka-packager而非mp4decrypt",
|
zhCN: "启用实时解密时,建议用shaka-packager而非mp4decrypt/ffmpeg",
|
||||||
zhTW: "啟用即時解密時,建議用shaka-packager而非mp4decrypt",
|
zhTW: "啟用即時解密時,建議用shaka-packager而非mp4decrypt/ffmpeg",
|
||||||
enUS: "When enabling real-time decryption, it is recommended to use shaka-packager instead of mp4decrypt"
|
enUS: "When enabling real-time decryption, it is recommended to use shaka-packager instead of mp4decrypt/ffmpeg"
|
||||||
),
|
),
|
||||||
["liveLimitReached"] = new TextContainer
|
["liveLimitReached"] = new TextContainer
|
||||||
(
|
(
|
||||||
@ -760,6 +778,24 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
zhTW: "找不到ffmpeg,請自行下載:https://ffmpeg.org/download.html",
|
zhTW: "找不到ffmpeg,請自行下載:https://ffmpeg.org/download.html",
|
||||||
enUS: "ffmpeg not found, please download at: https://ffmpeg.org/download.html"
|
enUS: "ffmpeg not found, please download at: https://ffmpeg.org/download.html"
|
||||||
),
|
),
|
||||||
|
["mkvmergeNotFound"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "找不到mkvmerge,请自行下载:https://mkvtoolnix.download/downloads.html",
|
||||||
|
zhTW: "找不到mkvmerge,請自行下載:https://mkvtoolnix.download/downloads.html",
|
||||||
|
enUS: "mkvmerge not found, please download at: https://mkvtoolnix.download/downloads.html"
|
||||||
|
),
|
||||||
|
["shakaPackagerNotFound"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "找不到shaka-packager,请自行下载:https://github.com/shaka-project/shaka-packager/releases",
|
||||||
|
zhTW: "找不到shaka-packager,請自行下載:https://github.com/shaka-project/shaka-packager/releases",
|
||||||
|
enUS: "shaka-packager not found, please download at: https://github.com/shaka-project/shaka-packager/releases"
|
||||||
|
),
|
||||||
|
["mp4decryptNotFound"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "找不到mp4decrypt,请自行下载:https://www.bento4.com/downloads/",
|
||||||
|
zhTW: "找不到mp4decrypt,請自行下載:https://www.bento4.com/downloads/",
|
||||||
|
enUS: "mp4decrypt not found, please download at: https://www.bento4.com/downloads/"
|
||||||
|
),
|
||||||
["fixingTTML"] = new TextContainer
|
["fixingTTML"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "正在提取TTML(raw)字幕...",
|
zhCN: "正在提取TTML(raw)字幕...",
|
||||||
@ -808,6 +844,12 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
zhTW: "檢測到Master列表,開始解析全部流訊息",
|
zhTW: "檢測到Master列表,開始解析全部流訊息",
|
||||||
enUS: "Master List detected, try parse all streams"
|
enUS: "Master List detected, try parse all streams"
|
||||||
),
|
),
|
||||||
|
["allowHlsMultiExtMap"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "已经允许识别多个#EXT-X-MAP标签, 本软件可能无法正确处理, 请手动确认内容完整性",
|
||||||
|
zhTW: "已經允許識別多個#EXT-X-MAP標籤, 本軟件可能無法正確處理, 請手動確認內容完整性",
|
||||||
|
enUS: "Multiple #EXT-X-MAP tags are now allowed for detection. However, this software may not handle them correctly. Please manually verify the content's integrity"
|
||||||
|
),
|
||||||
["matchTS"] = new TextContainer
|
["matchTS"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "内容匹配: [white on green3]HTTP Live MPEG2-TS[/]",
|
zhCN: "内容匹配: [white on green3]HTTP Live MPEG2-TS[/]",
|
||||||
@ -880,6 +922,12 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
zhTW: "正在嘗試從文本文件搜尋KEY...",
|
zhTW: "正在嘗試從文本文件搜尋KEY...",
|
||||||
enUS: "Trying to search for KEY from text file..."
|
enUS: "Trying to search for KEY from text file..."
|
||||||
),
|
),
|
||||||
|
["decryptionFailed"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "解密失败",
|
||||||
|
zhTW: "解密失敗",
|
||||||
|
enUS: "Decryption failed"
|
||||||
|
),
|
||||||
["segmentCountCheckNotPass"] = new TextContainer
|
["segmentCountCheckNotPass"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "分片数量校验不通过, 共{}个,已下载{}.",
|
zhCN: "分片数量校验不通过, 共{}个,已下载{}.",
|
||||||
@ -919,4 +967,3 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Common.Resource;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Resource
|
|
||||||
{
|
|
||||||
internal class TextContainer
|
internal class TextContainer
|
||||||
{
|
{
|
||||||
public string ZH_CN { get; set; }
|
public string ZH_CN { get; }
|
||||||
public string ZH_TW { get; set; }
|
public string ZH_TW { get; }
|
||||||
public string EN_US { get; set; }
|
public string EN_US { get; }
|
||||||
|
|
||||||
public TextContainer(string zhCN, string zhTW, string enUS)
|
public TextContainer(string zhCN, string zhTW, string enUS)
|
||||||
{
|
{
|
||||||
@ -19,4 +13,3 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||||||
EN_US = enUS;
|
EN_US = enUS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.JsonConverter;
|
using N_m3u8DL_RE.Common.JsonConverter;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Util
|
namespace N_m3u8DL_RE.Common.Util;
|
||||||
|
|
||||||
|
public static class GlobalUtil
|
||||||
{
|
{
|
||||||
public class GlobalUtil
|
private static readonly JsonSerializerOptions Options = new()
|
||||||
{
|
|
||||||
private static readonly JsonSerializerOptions Options = new JsonSerializerOptions
|
|
||||||
{
|
{
|
||||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
@ -27,15 +22,15 @@ namespace N_m3u8DL_RE.Common.Util
|
|||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(s, Context.StreamSpec);
|
return JsonSerializer.Serialize(s, Context.StreamSpec);
|
||||||
}
|
}
|
||||||
else if (o is IOrderedEnumerable<StreamSpec> ss)
|
if (o is IOrderedEnumerable<StreamSpec> ss)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(ss, Context.IOrderedEnumerableStreamSpec);
|
return JsonSerializer.Serialize(ss, Context.IOrderedEnumerableStreamSpec);
|
||||||
}
|
}
|
||||||
else if (o is List<StreamSpec> sList)
|
if (o is List<StreamSpec> sList)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(sList, Context.ListStreamSpec);
|
return JsonSerializer.Serialize(sList, Context.ListStreamSpec);
|
||||||
}
|
}
|
||||||
else if (o is IEnumerable<MediaSegment> mList)
|
if (o is IEnumerable<MediaSegment> mList)
|
||||||
{
|
{
|
||||||
return JsonSerializer.Serialize(mList, Context.IEnumerableMediaSegment);
|
return JsonSerializer.Serialize(mList, Context.IEnumerableMediaSegment);
|
||||||
}
|
}
|
||||||
@ -47,10 +42,10 @@ namespace N_m3u8DL_RE.Common.Util
|
|||||||
return fileSize switch
|
return fileSize switch
|
||||||
{
|
{
|
||||||
< 0 => throw new ArgumentOutOfRangeException(nameof(fileSize)),
|
< 0 => throw new ArgumentOutOfRangeException(nameof(fileSize)),
|
||||||
>= 1024 * 1024 * 1024 => string.Format("{0:########0.00}GB", (double)fileSize / (1024 * 1024 * 1024)),
|
>= 1024 * 1024 * 1024 => $"{fileSize / (1024 * 1024 * 1024):########0.00}GB",
|
||||||
>= 1024 * 1024 => string.Format("{0:####0.00}MB", (double)fileSize / (1024 * 1024)),
|
>= 1024 * 1024 => $"{fileSize / (1024 * 1024):####0.00}MB",
|
||||||
>= 1024 => string.Format("{0:####0.00}KB", (double)fileSize / 1024),
|
>= 1024 => $"{fileSize / 1024:####0.00}KB",
|
||||||
_ => string.Format("{0:####0.00}B", fileSize)
|
_ => $"{fileSize:####0.00}B"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,9 +67,7 @@ namespace N_m3u8DL_RE.Common.Util
|
|||||||
{
|
{
|
||||||
var fileExt = OperatingSystem.IsWindows() ? ".exe" : "";
|
var fileExt = OperatingSystem.IsWindows() ? ".exe" : "";
|
||||||
var searchPath = new[] { Environment.CurrentDirectory, Path.GetDirectoryName(Environment.ProcessPath) };
|
var searchPath = new[] { Environment.CurrentDirectory, Path.GetDirectoryName(Environment.ProcessPath) };
|
||||||
var envPath = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ??
|
var envPath = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? [];
|
||||||
Array.Empty<string>();
|
return searchPath.Concat(envPath).Select(p => Path.Combine(p!, name + fileExt)).FirstOrDefault(File.Exists);
|
||||||
return searchPath.Concat(envPath).Select(p => Path.Combine(p, name + fileExt)).FirstOrDefault(File.Exists);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,9 +3,9 @@ using System.Net.Http.Headers;
|
|||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Util
|
namespace N_m3u8DL_RE.Common.Util;
|
||||||
{
|
|
||||||
public class HTTPUtil
|
public static class HTTPUtil
|
||||||
{
|
{
|
||||||
public static readonly HttpClientHandler HttpClientHandler = new()
|
public static readonly HttpClientHandler HttpClientHandler = new()
|
||||||
{
|
{
|
||||||
@ -43,7 +43,7 @@ namespace N_m3u8DL_RE.Common.Util
|
|||||||
{
|
{
|
||||||
HttpResponseHeaders respHeaders = webResponse.Headers;
|
HttpResponseHeaders respHeaders = webResponse.Headers;
|
||||||
Logger.Debug(respHeaders.ToString());
|
Logger.Debug(respHeaders.ToString());
|
||||||
if (respHeaders != null && respHeaders.Location != null)
|
if (respHeaders.Location != null)
|
||||||
{
|
{
|
||||||
var redirectedUrl = "";
|
var redirectedUrl = "";
|
||||||
if (!respHeaders.Location.IsAbsoluteUri)
|
if (!respHeaders.Location.IsAbsoluteUri)
|
||||||
@ -76,9 +76,8 @@ namespace N_m3u8DL_RE.Common.Util
|
|||||||
{
|
{
|
||||||
return await File.ReadAllBytesAsync(new Uri(url).LocalPath);
|
return await File.ReadAllBytesAsync(new Uri(url).LocalPath);
|
||||||
}
|
}
|
||||||
byte[] bytes = new byte[0];
|
|
||||||
var webResponse = await DoGetAsync(url, headers);
|
var webResponse = await DoGetAsync(url, headers);
|
||||||
bytes = await webResponse.Content.ReadAsByteArrayAsync();
|
var bytes = await webResponse.Content.ReadAsByteArrayAsync();
|
||||||
Logger.Debug(HexUtil.BytesToHex(bytes, " "));
|
Logger.Debug(HexUtil.BytesToHex(bytes, " "));
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
@ -91,9 +90,8 @@ namespace N_m3u8DL_RE.Common.Util
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static async Task<string> GetWebSourceAsync(string url, Dictionary<string, string>? headers = null)
|
public static async Task<string> GetWebSourceAsync(string url, Dictionary<string, string>? headers = null)
|
||||||
{
|
{
|
||||||
string htmlCode = string.Empty;
|
|
||||||
var webResponse = await DoGetAsync(url, headers);
|
var webResponse = await DoGetAsync(url, headers);
|
||||||
htmlCode = await webResponse.Content.ReadAsStringAsync();
|
string htmlCode = await webResponse.Content.ReadAsStringAsync();
|
||||||
Logger.Debug(htmlCode);
|
Logger.Debug(htmlCode);
|
||||||
return htmlCode;
|
return htmlCode;
|
||||||
}
|
}
|
||||||
@ -101,7 +99,7 @@ namespace N_m3u8DL_RE.Common.Util
|
|||||||
private static bool CheckMPEG2TS(HttpResponseMessage? webResponse)
|
private static bool CheckMPEG2TS(HttpResponseMessage? webResponse)
|
||||||
{
|
{
|
||||||
var mediaType = webResponse?.Content.Headers.ContentType?.MediaType?.ToLower();
|
var mediaType = webResponse?.Content.Headers.ContentType?.MediaType?.ToLower();
|
||||||
return mediaType == "video/ts" || mediaType == "video/mp2t" || mediaType == "video/mpeg";
|
return mediaType is "video/ts" or "video/mp2t" or "video/mpeg";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -112,7 +110,7 @@ namespace N_m3u8DL_RE.Common.Util
|
|||||||
/// <returns>(Source Code, RedirectedUrl)</returns>
|
/// <returns>(Source Code, RedirectedUrl)</returns>
|
||||||
public static async Task<(string, string)> GetWebSourceAndNewUrlAsync(string url, Dictionary<string, string>? headers = null)
|
public static async Task<(string, string)> GetWebSourceAndNewUrlAsync(string url, Dictionary<string, string>? headers = null)
|
||||||
{
|
{
|
||||||
string htmlCode = string.Empty;
|
string htmlCode;
|
||||||
var webResponse = await DoGetAsync(url, headers);
|
var webResponse = await DoGetAsync(url, headers);
|
||||||
if (CheckMPEG2TS(webResponse))
|
if (CheckMPEG2TS(webResponse))
|
||||||
{
|
{
|
||||||
@ -128,7 +126,7 @@ namespace N_m3u8DL_RE.Common.Util
|
|||||||
|
|
||||||
public static async Task<string> GetPostResponseAsync(string Url, byte[] postData)
|
public static async Task<string> GetPostResponseAsync(string Url, byte[] postData)
|
||||||
{
|
{
|
||||||
string htmlCode = string.Empty;
|
string htmlCode;
|
||||||
using HttpRequestMessage request = new(HttpMethod.Post, Url);
|
using HttpRequestMessage request = new(HttpMethod.Post, Url);
|
||||||
request.Headers.TryAddWithoutValidation("Content-Type", "application/json");
|
request.Headers.TryAddWithoutValidation("Content-Type", "application/json");
|
||||||
request.Headers.TryAddWithoutValidation("Content-Length", postData.Length.ToString());
|
request.Headers.TryAddWithoutValidation("Content-Length", postData.Length.ToString());
|
||||||
@ -138,4 +136,3 @@ namespace N_m3u8DL_RE.Common.Util
|
|||||||
return htmlCode;
|
return htmlCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,12 +1,6 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Common.Util;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Util
|
public static class HexUtil
|
||||||
{
|
|
||||||
public class HexUtil
|
|
||||||
{
|
{
|
||||||
public static string BytesToHex(byte[] data, string split = "")
|
public static string BytesToHex(byte[] data, string split = "")
|
||||||
{
|
{
|
||||||
@ -34,15 +28,12 @@ namespace N_m3u8DL_RE.Common.Util
|
|||||||
|
|
||||||
public static byte[] HexToBytes(string hex)
|
public static byte[] HexToBytes(string hex)
|
||||||
{
|
{
|
||||||
hex = hex.Trim();
|
var hexSpan = hex.AsSpan().Trim();
|
||||||
if (hex.StartsWith("0x") || hex.StartsWith("0X"))
|
if (hexSpan.StartsWith("0x") || hexSpan.StartsWith("0X"))
|
||||||
hex = hex.Substring(2);
|
{
|
||||||
byte[] bytes = new byte[hex.Length / 2];
|
hexSpan = hexSpan[2..];
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < hex.Length; i += 2)
|
return Convert.FromHexString(hexSpan);
|
||||||
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
|
|
||||||
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,7 +4,7 @@ using Spectre.Console;
|
|||||||
|
|
||||||
namespace N_m3u8DL_RE.Common.Util;
|
namespace N_m3u8DL_RE.Common.Util;
|
||||||
|
|
||||||
public class RetryUtil
|
public static class RetryUtil
|
||||||
{
|
{
|
||||||
public static async Task<T?> WebRequestRetryAsync<T>(Func<Task<T>> funcAsync, int maxRetries = 10, int retryDelayMilliseconds = 1500, int retryDelayIncrementMilliseconds = 0)
|
public static async Task<T?> WebRequestRetryAsync<T>(Func<Task<T>> funcAsync, int maxRetries = 10, int retryDelayMilliseconds = 1500, int retryDelayIncrementMilliseconds = 0)
|
||||||
{
|
{
|
||||||
|
@ -3,17 +3,19 @@ using N_m3u8DL_RE.Parser.Processor;
|
|||||||
using N_m3u8DL_RE.Parser.Processor.DASH;
|
using N_m3u8DL_RE.Parser.Processor.DASH;
|
||||||
using N_m3u8DL_RE.Parser.Processor.HLS;
|
using N_m3u8DL_RE.Parser.Processor.HLS;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Config
|
namespace N_m3u8DL_RE.Parser.Config;
|
||||||
{
|
|
||||||
public class ParserConfig
|
public class ParserConfig
|
||||||
{
|
{
|
||||||
public string Url { get; set; }
|
public string Url { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string OriginalUrl { get; set; }
|
public string OriginalUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string BaseUrl { get; set; }
|
public string BaseUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
|
public Dictionary<string, string> CustomParserArgs { get; } = new();
|
||||||
|
|
||||||
|
public Dictionary<string, string> Headers { get; init; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 内容前置处理器. 调用顺序与列表顺序相同
|
/// 内容前置处理器. 调用顺序与列表顺序相同
|
||||||
@ -65,4 +67,3 @@ namespace N_m3u8DL_RE.Parser.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int KeyRetryCount { get; set; } = 3;
|
public int KeyRetryCount { get; set; } = 3;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,16 +1,9 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Parser.Constants;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Constants
|
internal static class DASHTags
|
||||||
{
|
{
|
||||||
internal class DASHTags
|
public const string TemplateRepresentationID = "$RepresentationID$";
|
||||||
{
|
public const string TemplateBandwidth = "$Bandwidth$";
|
||||||
public static string TemplateRepresentationID = "$RepresentationID$";
|
public const string TemplateNumber = "$Number$";
|
||||||
public static string TemplateBandwidth = "$Bandwidth$";
|
public const string TemplateTime = "$Time$";
|
||||||
public static string TemplateNumber = "$Number$";
|
|
||||||
public static string TemplateTime = "$Time$";
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,38 +1,31 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Parser.Constants;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Constants
|
internal static class HLSTags
|
||||||
{
|
{
|
||||||
internal class HLSTags
|
public const string ext_m3u = "#EXTM3U";
|
||||||
{
|
public const string ext_x_targetduration = "#EXT-X-TARGETDURATION";
|
||||||
public static string ext_m3u = "#EXTM3U";
|
public const string ext_x_media_sequence = "#EXT-X-MEDIA-SEQUENCE";
|
||||||
public static string ext_x_targetduration = "#EXT-X-TARGETDURATION";
|
public const string ext_x_discontinuity_sequence = "#EXT-X-DISCONTINUITY-SEQUENCE";
|
||||||
public static string ext_x_media_sequence = "#EXT-X-MEDIA-SEQUENCE";
|
public const string ext_x_program_date_time = "#EXT-X-PROGRAM-DATE-TIME";
|
||||||
public static string ext_x_discontinuity_sequence = "#EXT-X-DISCONTINUITY-SEQUENCE";
|
public const string ext_x_media = "#EXT-X-MEDIA";
|
||||||
public static string ext_x_program_date_time = "#EXT-X-PROGRAM-DATE-TIME";
|
public const string ext_x_playlist_type = "#EXT-X-PLAYLIST-TYPE";
|
||||||
public static string ext_x_media = "#EXT-X-MEDIA";
|
public const string ext_x_key = "#EXT-X-KEY";
|
||||||
public static string ext_x_playlist_type = "#EXT-X-PLAYLIST-TYPE";
|
public const string ext_x_stream_inf = "#EXT-X-STREAM-INF";
|
||||||
public static string ext_x_key = "#EXT-X-KEY";
|
public const string ext_x_version = "#EXT-X-VERSION";
|
||||||
public static string ext_x_stream_inf = "#EXT-X-STREAM-INF";
|
public const string ext_x_allow_cache = "#EXT-X-ALLOW-CACHE";
|
||||||
public static string ext_x_version = "#EXT-X-VERSION";
|
public const string ext_x_endlist = "#EXT-X-ENDLIST";
|
||||||
public static string ext_x_allow_cache = "#EXT-X-ALLOW-CACHE";
|
public const string extinf = "#EXTINF";
|
||||||
public static string ext_x_endlist = "#EXT-X-ENDLIST";
|
public const string ext_i_frames_only = "#EXT-X-I-FRAMES-ONLY";
|
||||||
public static string extinf = "#EXTINF";
|
public const string ext_x_byterange = "#EXT-X-BYTERANGE";
|
||||||
public static string ext_i_frames_only = "#EXT-X-I-FRAMES-ONLY";
|
public const string ext_x_i_frame_stream_inf = "#EXT-X-I-FRAME-STREAM-INF";
|
||||||
public static string ext_x_byterange = "#EXT-X-BYTERANGE";
|
public const string ext_x_discontinuity = "#EXT-X-DISCONTINUITY";
|
||||||
public static string ext_x_i_frame_stream_inf = "#EXT-X-I-FRAME-STREAM-INF";
|
public const string ext_x_cue_out_start = "#EXT-X-CUE-OUT";
|
||||||
public static string ext_x_discontinuity = "#EXT-X-DISCONTINUITY";
|
public const string ext_x_cue_out = "#EXT-X-CUE-OUT-CONT";
|
||||||
public static string ext_x_cue_out_start = "#EXT-X-CUE-OUT";
|
public const string ext_is_independent_segments = "#EXT-X-INDEPENDENT-SEGMENTS";
|
||||||
public static string ext_x_cue_out = "#EXT-X-CUE-OUT-CONT";
|
public const string ext_x_scte35 = "#EXT-OATCLS-SCTE35";
|
||||||
public static string ext_is_independent_segments = "#EXT-X-INDEPENDENT-SEGMENTS";
|
public const string ext_x_cue_start = "#EXT-X-CUE-OUT";
|
||||||
public static string ext_x_scte35 = "#EXT-OATCLS-SCTE35";
|
public const string ext_x_cue_end = "#EXT-X-CUE-IN";
|
||||||
public static string ext_x_cue_start = "#EXT-X-CUE-OUT";
|
public const string ext_x_cue_span = "#EXT-X-CUE-SPAN";
|
||||||
public static string ext_x_cue_end = "#EXT-X-CUE-IN";
|
public const string ext_x_map = "#EXT-X-MAP";
|
||||||
public static string ext_x_cue_span = "#EXT-X-CUE-SPAN";
|
public const string ext_x_start = "#EXT-X-START";
|
||||||
public static string ext_x_map = "#EXT-X-MAP";
|
|
||||||
public static string ext_x_start = "#EXT-X-START";
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,16 +1,9 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Parser.Constants;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Constants
|
internal static class MSSTags
|
||||||
{
|
{
|
||||||
internal class MSSTags
|
public const string Bitrate = "{Bitrate}";
|
||||||
{
|
public const string Bitrate_BK = "{bitrate}";
|
||||||
public static string Bitrate = "{Bitrate}";
|
public const string StartTime = "{start_time}";
|
||||||
public static string Bitrate_BK = "{bitrate}";
|
public const string StartTime_BK = "{start time}";
|
||||||
public static string StartTime = "{start_time}";
|
|
||||||
public static string StartTime_BK = "{start time}";
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -4,19 +4,14 @@ using N_m3u8DL_RE.Common.Util;
|
|||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Parser.Constants;
|
using N_m3u8DL_RE.Parser.Constants;
|
||||||
using N_m3u8DL_RE.Parser.Util;
|
using N_m3u8DL_RE.Parser.Util;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Extractor
|
namespace N_m3u8DL_RE.Parser.Extractor;
|
||||||
{
|
|
||||||
// https://blog.csdn.net/leek5533/article/details/117750191
|
// https://blog.csdn.net/leek5533/article/details/117750191
|
||||||
internal class DASHExtractor2 : IExtractor
|
internal partial class DASHExtractor2 : IExtractor
|
||||||
{
|
{
|
||||||
private static EncryptMethod DEFAULT_METHOD = EncryptMethod.CENC;
|
private static EncryptMethod DEFAULT_METHOD = EncryptMethod.CENC;
|
||||||
|
|
||||||
@ -37,15 +32,12 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
private void SetInitUrl()
|
private void SetInitUrl()
|
||||||
{
|
{
|
||||||
this.MpdUrl = ParserConfig.Url ?? string.Empty;
|
this.MpdUrl = ParserConfig.Url ?? string.Empty;
|
||||||
if (!string.IsNullOrEmpty(ParserConfig.BaseUrl))
|
this.BaseUrl = !string.IsNullOrEmpty(ParserConfig.BaseUrl) ? ParserConfig.BaseUrl : this.MpdUrl;
|
||||||
this.BaseUrl = ParserConfig.BaseUrl;
|
|
||||||
else
|
|
||||||
this.BaseUrl = this.MpdUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ExtendBaseUrl(XElement element, string oriBaseUrl)
|
private string ExtendBaseUrl(XElement element, string oriBaseUrl)
|
||||||
{
|
{
|
||||||
var target = element.Elements().Where(e => e.Name.LocalName == "BaseURL").FirstOrDefault();
|
var target = element.Elements().FirstOrDefault(e => e.Name.LocalName == "BaseURL");
|
||||||
if (target != null)
|
if (target != null)
|
||||||
{
|
{
|
||||||
oriBaseUrl = ParserUtil.CombineURL(oriBaseUrl, target.Value);
|
oriBaseUrl = ParserUtil.CombineURL(oriBaseUrl, target.Value);
|
||||||
@ -57,16 +49,14 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
private double? GetFrameRate(XElement element)
|
private double? GetFrameRate(XElement element)
|
||||||
{
|
{
|
||||||
var frameRate = element.Attribute("frameRate")?.Value;
|
var frameRate = element.Attribute("frameRate")?.Value;
|
||||||
if (frameRate != null && frameRate.Contains("/"))
|
if (frameRate == null || !frameRate.Contains('/')) return null;
|
||||||
{
|
|
||||||
var d = Convert.ToDouble(frameRate.Split('/')[0]) / Convert.ToDouble(frameRate.Split('/')[1]);
|
var d = Convert.ToDouble(frameRate.Split('/')[0]) / Convert.ToDouble(frameRate.Split('/')[1]);
|
||||||
frameRate = d.ToString("0.000");
|
frameRate = d.ToString("0.000");
|
||||||
return Convert.ToDouble(frameRate);
|
return Convert.ToDouble(frameRate);
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
public Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
||||||
{
|
{
|
||||||
var streamList = new List<StreamSpec>();
|
var streamList = new List<StreamSpec>();
|
||||||
|
|
||||||
@ -100,7 +90,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
var mediaPresentationDuration = mpdElement.Attribute("mediaPresentationDuration")?.Value;
|
var mediaPresentationDuration = mpdElement.Attribute("mediaPresentationDuration")?.Value;
|
||||||
|
|
||||||
// 读取在MPD开头定义的<BaseURL>,并替换本身的URL
|
// 读取在MPD开头定义的<BaseURL>,并替换本身的URL
|
||||||
var baseUrlElement = mpdElement.Elements().Where(e => e.Name.LocalName == "BaseURL").FirstOrDefault();
|
var baseUrlElement = mpdElement.Elements().FirstOrDefault(e => e.Name.LocalName == "BaseURL");
|
||||||
if (baseUrlElement != null)
|
if (baseUrlElement != null)
|
||||||
{
|
{
|
||||||
var baseUrl = baseUrlElement.Value;
|
var baseUrl = baseUrlElement.Value;
|
||||||
@ -180,16 +170,16 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
streamSpec.Extension = mTypeSplit.Length == 2 ? mTypeSplit[1] : null;
|
streamSpec.Extension = mTypeSplit.Length == 2 ? mTypeSplit[1] : null;
|
||||||
}
|
}
|
||||||
// 优化字幕场景识别
|
// 优化字幕场景识别
|
||||||
if (streamSpec.Codecs == "stpp" || streamSpec.Codecs == "wvtt")
|
if (streamSpec.Codecs is "stpp" or "wvtt")
|
||||||
{
|
{
|
||||||
streamSpec.MediaType = MediaType.SUBTITLES;
|
streamSpec.MediaType = MediaType.SUBTITLES;
|
||||||
}
|
}
|
||||||
// 优化字幕场景识别
|
// 优化字幕场景识别
|
||||||
var role = representation.Elements().Where(e => e.Name.LocalName == "Role").FirstOrDefault() ?? adaptationSet.Elements().Where(e => e.Name.LocalName == "Role").FirstOrDefault();
|
var role = representation.Elements().FirstOrDefault(e => e.Name.LocalName == "Role") ?? adaptationSet.Elements().FirstOrDefault(e => e.Name.LocalName == "Role");
|
||||||
if (role != null)
|
if (role != null)
|
||||||
{
|
{
|
||||||
var v = role.Attribute("value")?.Value;
|
var roleValue = role.Attribute("value")?.Value;
|
||||||
if (Enum.TryParse(v, true, out RoleType roleType))
|
if (Enum.TryParse(roleValue, true, out RoleType roleType))
|
||||||
{
|
{
|
||||||
streamSpec.Role = roleType;
|
streamSpec.Role = roleType;
|
||||||
|
|
||||||
@ -200,6 +190,21 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
streamSpec.Extension = "ttml";
|
streamSpec.Extension = "ttml";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (roleValue != null && roleValue.Contains('-'))
|
||||||
|
{
|
||||||
|
roleValue = roleValue.Replace("-", "");
|
||||||
|
if (Enum.TryParse(roleValue, true, out RoleType roleType_))
|
||||||
|
{
|
||||||
|
streamSpec.Role = roleType_;
|
||||||
|
|
||||||
|
if (roleType_ == RoleType.ForcedSubtitle)
|
||||||
|
{
|
||||||
|
streamSpec.MediaType = MediaType.SUBTITLES; // or maybe MediaType.CLOSED_CAPTIONS?
|
||||||
|
if (mType != null && mType.Contains("ttml"))
|
||||||
|
streamSpec.Extension = "ttml";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
streamSpec.Playlist.IsLive = isLive;
|
streamSpec.Playlist.IsLive = isLive;
|
||||||
// 设置刷新间隔 timeShiftBufferDepth / 2
|
// 设置刷新间隔 timeShiftBufferDepth / 2
|
||||||
@ -209,7 +214,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 读取声道数量
|
// 读取声道数量
|
||||||
var audioChannelConfiguration = adaptationSet.Elements().Concat(representation.Elements()).Where(e => e.Name.LocalName == "AudioChannelConfiguration").FirstOrDefault();
|
var audioChannelConfiguration = adaptationSet.Elements().Concat(representation.Elements()).FirstOrDefault(e => e.Name.LocalName == "AudioChannelConfiguration");
|
||||||
if (audioChannelConfiguration != null)
|
if (audioChannelConfiguration != null)
|
||||||
{
|
{
|
||||||
streamSpec.Channels = audioChannelConfiguration.Attribute("value")?.Value;
|
streamSpec.Channels = audioChannelConfiguration.Attribute("value")?.Value;
|
||||||
@ -223,11 +228,11 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
|
|
||||||
|
|
||||||
// 第一种形式 SegmentBase
|
// 第一种形式 SegmentBase
|
||||||
var segmentBaseElement = representation.Elements().Where(e => e.Name.LocalName == "SegmentBase").FirstOrDefault();
|
var segmentBaseElement = representation.Elements().FirstOrDefault(e => e.Name.LocalName == "SegmentBase");
|
||||||
if (segmentBaseElement != null)
|
if (segmentBaseElement != null)
|
||||||
{
|
{
|
||||||
// 处理init url
|
// 处理init url
|
||||||
var initialization = segmentBaseElement.Elements().Where(e => e.Name.LocalName == "Initialization").FirstOrDefault();
|
var initialization = segmentBaseElement.Elements().FirstOrDefault(e => e.Name.LocalName == "Initialization");
|
||||||
if (initialization != null)
|
if (initialization != null)
|
||||||
{
|
{
|
||||||
var sourceURL = initialization.Attribute("sourceURL")?.Value;
|
var sourceURL = initialization.Attribute("sourceURL")?.Value;
|
||||||
@ -261,12 +266,12 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 第二种形式 SegmentList.SegmentList
|
// 第二种形式 SegmentList.SegmentList
|
||||||
var segmentList = representation.Elements().Where(e => e.Name.LocalName == "SegmentList").FirstOrDefault();
|
var segmentList = representation.Elements().FirstOrDefault(e => e.Name.LocalName == "SegmentList");
|
||||||
if (segmentList != null)
|
if (segmentList != null)
|
||||||
{
|
{
|
||||||
var durationStr = segmentList.Attribute("duration")?.Value;
|
var durationStr = segmentList.Attribute("duration")?.Value;
|
||||||
// 处理init url
|
// 处理init url
|
||||||
var initialization = segmentList.Elements().Where(e => e.Name.LocalName == "Initialization").FirstOrDefault();
|
var initialization = segmentList.Elements().FirstOrDefault(e => e.Name.LocalName == "Initialization");
|
||||||
if (initialization != null)
|
if (initialization != null)
|
||||||
{
|
{
|
||||||
var initUrl = ParserUtil.CombineURL(segBaseUrl, initialization.Attribute("sourceURL")?.Value!);
|
var initUrl = ParserUtil.CombineURL(segBaseUrl, initialization.Attribute("sourceURL")?.Value!);
|
||||||
@ -282,9 +287,9 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 处理分片
|
// 处理分片
|
||||||
var segmentURLs = segmentList.Elements().Where(e => e.Name.LocalName == "SegmentURL");
|
var segmentURLs = segmentList.Elements().Where(e => e.Name.LocalName == "SegmentURL").ToList();
|
||||||
var timescaleStr = segmentList.Attribute("timescale")?.Value ?? "1";
|
var timescaleStr = segmentList.Attribute("timescale")?.Value ?? "1";
|
||||||
for (int segmentIndex = 0; segmentIndex < segmentURLs.Count(); segmentIndex++)
|
for (int segmentIndex = 0; segmentIndex < segmentURLs.Count; segmentIndex++)
|
||||||
{
|
{
|
||||||
var segmentURL = segmentURLs.ElementAt(segmentIndex);
|
var segmentURL = segmentURLs.ElementAt(segmentIndex);
|
||||||
var mediaUrl = ParserUtil.CombineURL(segBaseUrl, segmentURL.Attribute("media")?.Value!);
|
var mediaUrl = ParserUtil.CombineURL(segBaseUrl, segmentURL.Attribute("media")?.Value!);
|
||||||
@ -338,7 +343,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
}
|
}
|
||||||
// 处理分片
|
// 处理分片
|
||||||
var mediaTemplate = segmentTemplate.Attribute("media")?.Value ?? segmentTemplateOuter.Attribute("media")?.Value;
|
var mediaTemplate = segmentTemplate.Attribute("media")?.Value ?? segmentTemplateOuter.Attribute("media")?.Value;
|
||||||
var segmentTimeline = segmentTemplate.Elements().Where(e => e.Name.LocalName == "SegmentTimeline").FirstOrDefault();
|
var segmentTimeline = segmentTemplate.Elements().FirstOrDefault(e => e.Name.LocalName == "SegmentTimeline");
|
||||||
if (segmentTimeline != null)
|
if (segmentTimeline != null)
|
||||||
{
|
{
|
||||||
// 使用了SegmentTimeline 结果精确
|
// 使用了SegmentTimeline 结果精确
|
||||||
@ -493,7 +498,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 修复mp4类型字幕
|
// 修复mp4类型字幕
|
||||||
if (streamSpec.MediaType == MediaType.SUBTITLES && streamSpec.Extension == "mp4")
|
if (streamSpec is { MediaType: MediaType.SUBTITLES, Extension: "mp4" })
|
||||||
{
|
{
|
||||||
streamSpec.Extension = "m4s";
|
streamSpec.Extension = "m4s";
|
||||||
}
|
}
|
||||||
@ -513,24 +518,21 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 为视频设置默认轨道
|
// 为视频设置默认轨道
|
||||||
var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO);
|
var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO).ToList();
|
||||||
var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES);
|
var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES).ToList();
|
||||||
foreach (var item in streamList)
|
foreach (var item in streamList.Where(item => !string.IsNullOrEmpty(item.Resolution)))
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(item.Resolution))
|
if (aL.Count != 0)
|
||||||
{
|
|
||||||
if (aL.Any())
|
|
||||||
{
|
{
|
||||||
item.AudioId = aL.OrderByDescending(x => x.Bandwidth).First().GroupId;
|
item.AudioId = aL.OrderByDescending(x => x.Bandwidth).First().GroupId;
|
||||||
}
|
}
|
||||||
if (sL.Any())
|
if (sL.Count != 0)
|
||||||
{
|
{
|
||||||
item.SubtitleId = sL.OrderByDescending(x => x.Bandwidth).First().GroupId;
|
item.SubtitleId = sL.OrderByDescending(x => x.Bandwidth).First().GroupId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return streamList;
|
return Task.FromResult(streamList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -541,8 +543,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
private string? FilterLanguage(string? v)
|
private string? FilterLanguage(string? v)
|
||||||
{
|
{
|
||||||
if (v == null) return null;
|
if (v == null) return null;
|
||||||
if (Regex.IsMatch(v, "^[\\w_\\-\\d]+$")) return v;
|
return LangCodeRegex().IsMatch(v) ? v : "und";
|
||||||
return "und";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RefreshPlayListAsync(List<StreamSpec> streamSpecs)
|
public async Task RefreshPlayListAsync(List<StreamSpec> streamSpecs)
|
||||||
@ -579,27 +580,28 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
await ProcessUrlAsync(streamSpecs);
|
await ProcessUrlAsync(streamSpecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessUrlAsync(List<StreamSpec> streamSpecs)
|
private Task ProcessUrlAsync(List<StreamSpec> streamSpecs)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < streamSpecs.Count; i++)
|
foreach (var streamSpec in streamSpecs)
|
||||||
{
|
|
||||||
var playlist = streamSpecs[i].Playlist;
|
|
||||||
if (playlist != null)
|
|
||||||
{
|
{
|
||||||
|
var playlist = streamSpec.Playlist;
|
||||||
|
if (playlist == null) continue;
|
||||||
|
|
||||||
if (playlist.MediaInit != null)
|
if (playlist.MediaInit != null)
|
||||||
{
|
{
|
||||||
playlist.MediaInit!.Url = PreProcessUrl(playlist.MediaInit!.Url);
|
playlist.MediaInit!.Url = PreProcessUrl(playlist.MediaInit!.Url);
|
||||||
}
|
}
|
||||||
for (int ii = 0; ii < playlist!.MediaParts.Count; ii++)
|
for (var ii = 0; ii < playlist!.MediaParts.Count; ii++)
|
||||||
{
|
{
|
||||||
var part = playlist.MediaParts[ii];
|
var part = playlist.MediaParts[ii];
|
||||||
for (int iii = 0; iii < part.MediaSegments.Count; iii++)
|
foreach (var mediaSegment in part.MediaSegments)
|
||||||
{
|
{
|
||||||
part.MediaSegments[iii].Url = PreProcessUrl(part.MediaSegments[iii].Url);
|
mediaSegment.Url = PreProcessUrl(mediaSegment.Url);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
public async Task FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
||||||
@ -631,5 +633,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
[GeneratedRegex(@"^[\w_\-\d]+$")]
|
||||||
|
private static partial Regex LangCodeRegex();
|
||||||
}
|
}
|
@ -5,16 +5,10 @@ using N_m3u8DL_RE.Common.Log;
|
|||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Parser.Util;
|
using N_m3u8DL_RE.Parser.Util;
|
||||||
using N_m3u8DL_RE.Parser.Constants;
|
using N_m3u8DL_RE.Parser.Constants;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Extractor
|
namespace N_m3u8DL_RE.Parser.Extractor;
|
||||||
{
|
|
||||||
internal class HLSExtractor : IExtractor
|
internal class HLSExtractor : IExtractor
|
||||||
{
|
{
|
||||||
public ExtractorType ExtractorType => ExtractorType.HLS;
|
public ExtractorType ExtractorType => ExtractorType.HLS;
|
||||||
@ -26,8 +20,6 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
|
|
||||||
public ParserConfig ParserConfig { get; set; }
|
public ParserConfig ParserConfig { get; set; }
|
||||||
|
|
||||||
private HLSExtractor() { }
|
|
||||||
|
|
||||||
public HLSExtractor(ParserConfig parserConfig)
|
public HLSExtractor(ParserConfig parserConfig)
|
||||||
{
|
{
|
||||||
this.ParserConfig = parserConfig;
|
this.ParserConfig = parserConfig;
|
||||||
@ -37,14 +29,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
|
|
||||||
private void SetBaseUrl()
|
private void SetBaseUrl()
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(ParserConfig.BaseUrl))
|
this.BaseUrl = !string.IsNullOrEmpty(ParserConfig.BaseUrl) ? ParserConfig.BaseUrl : this.M3u8Url;
|
||||||
{
|
|
||||||
this.BaseUrl = ParserConfig.BaseUrl;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.BaseUrl = this.M3u8Url;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -83,11 +68,11 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<StreamSpec>> ParseMasterListAsync()
|
private Task<List<StreamSpec>> ParseMasterListAsync()
|
||||||
{
|
{
|
||||||
MasterM3u8Flag = true;
|
MasterM3u8Flag = true;
|
||||||
|
|
||||||
List<StreamSpec> streams = new List<StreamSpec>();
|
List<StreamSpec> streams = [];
|
||||||
|
|
||||||
using StringReader sr = new StringReader(M3u8Content);
|
using StringReader sr = new StringReader(M3u8Content);
|
||||||
string? line;
|
string? line;
|
||||||
@ -201,7 +186,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
|
|
||||||
streams.Add(streamSpec);
|
streams.Add(streamSpec);
|
||||||
}
|
}
|
||||||
else if (line.StartsWith("#"))
|
else if (line.StartsWith('#'))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -214,13 +199,19 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return streams;
|
return Task.FromResult(streams);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Playlist> ParseListAsync()
|
private Task<Playlist> ParseListAsync()
|
||||||
{
|
{
|
||||||
//标记是否已清除优酷广告分片
|
// 标记是否已清除广告分片
|
||||||
bool hasAd = false;
|
bool hasAd = false;
|
||||||
|
;
|
||||||
|
bool allowHlsMultiExtMap = ParserConfig.CustomParserArgs.TryGetValue("AllowHlsMultiExtMap", out var allMultiExtMap) && allMultiExtMap == "true";
|
||||||
|
if (allowHlsMultiExtMap)
|
||||||
|
{
|
||||||
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.allowHlsMultiExtMap}[/]");
|
||||||
|
}
|
||||||
|
|
||||||
using StringReader sr = new StringReader(M3u8Content);
|
using StringReader sr = new StringReader(M3u8Content);
|
||||||
string? line;
|
string? line;
|
||||||
@ -231,22 +222,22 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
long startIndex;
|
long startIndex;
|
||||||
|
|
||||||
Playlist playlist = new();
|
Playlist playlist = new();
|
||||||
List<MediaPart> mediaParts = new();
|
List<MediaPart> mediaParts = [];
|
||||||
|
|
||||||
// 当前的加密信息
|
// 当前的加密信息
|
||||||
EncryptInfo currentEncryptInfo = new();
|
EncryptInfo currentEncryptInfo = new();
|
||||||
if (ParserConfig.CustomMethod != null)
|
if (ParserConfig.CustomMethod != null)
|
||||||
currentEncryptInfo.Method = ParserConfig.CustomMethod.Value;
|
currentEncryptInfo.Method = ParserConfig.CustomMethod.Value;
|
||||||
if (ParserConfig.CustomeKey != null && ParserConfig.CustomeKey.Length > 0)
|
if (ParserConfig.CustomeKey is { Length: > 0 })
|
||||||
currentEncryptInfo.Key = ParserConfig.CustomeKey;
|
currentEncryptInfo.Key = ParserConfig.CustomeKey;
|
||||||
if (ParserConfig.CustomeIV != null && ParserConfig.CustomeIV.Length > 0)
|
if (ParserConfig.CustomeIV is { Length: > 0 })
|
||||||
currentEncryptInfo.IV = ParserConfig.CustomeIV;
|
currentEncryptInfo.IV = ParserConfig.CustomeIV;
|
||||||
// 上次读取到的加密行,#EXT-X-KEY:……
|
// 上次读取到的加密行,#EXT-X-KEY:……
|
||||||
string lastKeyLine = "";
|
string lastKeyLine = "";
|
||||||
|
|
||||||
MediaPart mediaPart = new();
|
MediaPart mediaPart = new();
|
||||||
MediaSegment segment = new();
|
MediaSegment segment = new();
|
||||||
List<MediaSegment> segments = new();
|
List<MediaSegment> segments = [];
|
||||||
|
|
||||||
|
|
||||||
while ((line = sr.ReadLine()) != null)
|
while ((line = sr.ReadLine()) != null)
|
||||||
@ -295,24 +286,23 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
// 解析不连续标记,需要单独合并(timestamp不同)
|
// 解析不连续标记,需要单独合并(timestamp不同)
|
||||||
else if (line.StartsWith(HLSTags.ext_x_discontinuity))
|
else if (line.StartsWith(HLSTags.ext_x_discontinuity))
|
||||||
{
|
{
|
||||||
//修复优酷去除广告后的遗留问题
|
// 修复YK去除广告后的遗留问题
|
||||||
if (hasAd && mediaParts.Count > 0)
|
if (hasAd && mediaParts.Count > 0)
|
||||||
{
|
{
|
||||||
segments = mediaParts[mediaParts.Count - 1].MediaSegments;
|
segments = mediaParts[^1].MediaSegments;
|
||||||
mediaParts.RemoveAt(mediaParts.Count - 1);
|
mediaParts.RemoveAt(mediaParts.Count - 1);
|
||||||
hasAd = false;
|
hasAd = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// 常规情况的#EXT-X-DISCONTINUITY标记,新建part
|
// 常规情况的#EXT-X-DISCONTINUITY标记,新建part
|
||||||
if (!hasAd && segments.Count >= 1)
|
if (hasAd || segments.Count < 1) continue;
|
||||||
{
|
|
||||||
mediaParts.Add(new MediaPart()
|
mediaParts.Add(new MediaPart
|
||||||
{
|
{
|
||||||
MediaSegments = segments,
|
MediaSegments = segments,
|
||||||
});
|
});
|
||||||
segments = new();
|
segments = new();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// 解析KEY
|
// 解析KEY
|
||||||
else if (line.StartsWith(HLSTags.ext_x_key))
|
else if (line.StartsWith(HLSTags.ext_x_key))
|
||||||
{
|
{
|
||||||
@ -362,7 +352,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
// #EXT-X-MAP
|
// #EXT-X-MAP
|
||||||
else if (line.StartsWith(HLSTags.ext_x_map))
|
else if (line.StartsWith(HLSTags.ext_x_map))
|
||||||
{
|
{
|
||||||
if (playlist.MediaInit == null)
|
if (playlist.MediaInit == null || hasAd)
|
||||||
{
|
{
|
||||||
playlist.MediaInit = new MediaSegment()
|
playlist.MediaInit = new MediaSegment()
|
||||||
{
|
{
|
||||||
@ -376,14 +366,12 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
playlist.MediaInit.ExpectLength = n;
|
playlist.MediaInit.ExpectLength = n;
|
||||||
playlist.MediaInit.StartRange = o ?? 0L;
|
playlist.MediaInit.StartRange = o ?? 0L;
|
||||||
}
|
}
|
||||||
//是否有加密,有的话写入KEY和IV
|
if (currentEncryptInfo.Method == EncryptMethod.NONE) continue;
|
||||||
if (currentEncryptInfo.Method != EncryptMethod.NONE)
|
// 有加密的话写入KEY和IV
|
||||||
{
|
|
||||||
playlist.MediaInit.EncryptInfo.Method = currentEncryptInfo.Method;
|
playlist.MediaInit.EncryptInfo.Method = currentEncryptInfo.Method;
|
||||||
playlist.MediaInit.EncryptInfo.Key = currentEncryptInfo.Key;
|
playlist.MediaInit.EncryptInfo.Key = currentEncryptInfo.Key;
|
||||||
playlist.MediaInit.EncryptInfo.IV = currentEncryptInfo.IV ?? HexUtil.HexToBytes(Convert.ToString(segIndex, 16).PadLeft(32, '0'));
|
playlist.MediaInit.EncryptInfo.IV = currentEncryptInfo.IV ?? HexUtil.HexToBytes(Convert.ToString(segIndex, 16).PadLeft(32, '0'));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// 遇到了其他的map,说明已经不是一个视频了,全部丢弃即可
|
// 遇到了其他的map,说明已经不是一个视频了,全部丢弃即可
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -395,12 +383,15 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
segments = new();
|
segments = new();
|
||||||
|
if (!allowHlsMultiExtMap)
|
||||||
|
{
|
||||||
isEndlist = true;
|
isEndlist = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 评论行不解析
|
// 评论行不解析
|
||||||
else if (line.StartsWith("#")) continue;
|
else if (line.StartsWith('#')) continue;
|
||||||
// 空白行不解析
|
// 空白行不解析
|
||||||
else if (line.StartsWith("\r\n")) continue;
|
else if (line.StartsWith("\r\n")) continue;
|
||||||
// 解析分片的地址
|
// 解析分片的地址
|
||||||
@ -410,7 +401,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
segment.Url = segUrl;
|
segment.Url = segUrl;
|
||||||
segments.Add(segment);
|
segments.Add(segment);
|
||||||
segment = new();
|
segment = new();
|
||||||
//优酷的广告分段则清除此分片
|
// YK的广告分段则清除此分片
|
||||||
// 需要注意,遇到广告说明程序对上文的#EXT-X-DISCONTINUITY做出的动作是不必要的,
|
// 需要注意,遇到广告说明程序对上文的#EXT-X-DISCONTINUITY做出的动作是不必要的,
|
||||||
// 其实上下文是同一种编码,需要恢复到原先的part上
|
// 其实上下文是同一种编码,需要恢复到原先的part上
|
||||||
if (segUrl.Contains("ccode=") && segUrl.Contains("/ad/") && segUrl.Contains("duration="))
|
if (segUrl.Contains("ccode=") && segUrl.Contains("/ad/") && segUrl.Contains("duration="))
|
||||||
@ -419,7 +410,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
segIndex--;
|
segIndex--;
|
||||||
hasAd = true;
|
hasAd = true;
|
||||||
}
|
}
|
||||||
//优酷广告(4K分辨率测试)
|
// YK广告(4K分辨率测试)
|
||||||
if (segUrl.Contains("ccode=0902") && segUrl.Contains("duration="))
|
if (segUrl.Contains("ccode=0902") && segUrl.Contains("duration="))
|
||||||
{
|
{
|
||||||
segments.RemoveAt(segments.Count - 1);
|
segments.RemoveAt(segments.Count - 1);
|
||||||
@ -449,7 +440,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
playlist.RefreshIntervalMs = (int)((playlist.TargetDuration ?? 5) * 2 * 1000);
|
playlist.RefreshIntervalMs = (int)((playlist.TargetDuration ?? 5) * 2 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
return playlist;
|
return Task.FromResult(playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EncryptInfo ParseKey(string keyLine)
|
private EncryptInfo ParseKey(string keyLine)
|
||||||
@ -477,19 +468,17 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
lists = lists.DistinctBy(p => p.Url).ToList();
|
lists = lists.DistinctBy(p => p.Url).ToList();
|
||||||
return lists;
|
return lists;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var playlist = await ParseListAsync();
|
var playlist = await ParseListAsync();
|
||||||
return new List<StreamSpec>()
|
return
|
||||||
{
|
[
|
||||||
new StreamSpec()
|
new()
|
||||||
{
|
{
|
||||||
Url = ParserConfig.Url,
|
Url = ParserConfig.Url,
|
||||||
Playlist = playlist,
|
Playlist = playlist,
|
||||||
Extension = playlist.MediaInit != null ? "mp4" : "ts"
|
Extension = playlist.MediaInit != null ? "mp4" : "ts"
|
||||||
}
|
}
|
||||||
};
|
];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadM3u8FromUrlAsync(string url)
|
private async Task LoadM3u8FromUrlAsync(string url)
|
||||||
@ -531,14 +520,13 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
newStreams = newStreams.DistinctBy(p => p.Url).ToList();
|
newStreams = newStreams.DistinctBy(p => p.Url).ToList();
|
||||||
foreach (var l in lists)
|
foreach (var l in lists)
|
||||||
{
|
{
|
||||||
var match = newStreams.Where(n => n.ToShortString() == l.ToShortString());
|
var match = newStreams.Where(n => n.ToShortString() == l.ToShortString()).ToList();
|
||||||
if (match.Any())
|
if (match.Count == 0) continue;
|
||||||
{
|
|
||||||
Logger.DebugMarkUp($"{l.Url} => {match.First().Url}");
|
Logger.DebugMarkUp($"{l.Url} => {match.First().Url}");
|
||||||
l.Url = match.First().Url;
|
l.Url = match.First().Url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public async Task FetchPlayListAsync(List<StreamSpec> lists)
|
public async Task FetchPlayListAsync(List<StreamSpec> lists)
|
||||||
{
|
{
|
||||||
@ -549,7 +537,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
// 直接重新加载m3u8
|
// 直接重新加载m3u8
|
||||||
await LoadM3u8FromUrlAsync(lists[i].Url!);
|
await LoadM3u8FromUrlAsync(lists[i].Url!);
|
||||||
}
|
}
|
||||||
catch (HttpRequestException) when (MasterM3u8Flag == true)
|
catch (HttpRequestException) when (MasterM3u8Flag)
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp("Can not load m3u8. Try refreshing url from master url...");
|
Logger.WarnMarkUp("Can not load m3u8. Try refreshing url from master url...");
|
||||||
// 当前URL无法加载 尝试从Master链接中刷新URL
|
// 当前URL无法加载 尝试从Master链接中刷新URL
|
||||||
@ -582,4 +570,3 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
await FetchPlayListAsync(streamSpecs);
|
await FetchPlayListAsync(streamSpecs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Extractor
|
namespace N_m3u8DL_RE.Parser.Extractor;
|
||||||
{
|
|
||||||
public interface IExtractor
|
public interface IExtractor
|
||||||
{
|
{
|
||||||
ExtractorType ExtractorType { get; }
|
ExtractorType ExtractorType { get; }
|
||||||
@ -24,4 +19,3 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
|
|
||||||
void PreProcessContent();
|
void PreProcessContent();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -3,8 +3,8 @@ using N_m3u8DL_RE.Common.Enum;
|
|||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Extractor
|
namespace N_m3u8DL_RE.Parser.Extractor;
|
||||||
{
|
|
||||||
internal class LiveTSExtractor : IExtractor
|
internal class LiveTSExtractor : IExtractor
|
||||||
{
|
{
|
||||||
public ExtractorType ExtractorType => ExtractorType.HTTP_LIVE;
|
public ExtractorType ExtractorType => ExtractorType.HTTP_LIVE;
|
||||||
@ -16,26 +16,26 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
this.ParserConfig = parserConfig;
|
this.ParserConfig = parserConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
public Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
||||||
{
|
{
|
||||||
return new List<StreamSpec>()
|
return Task.FromResult(new List<StreamSpec>
|
||||||
{
|
{
|
||||||
new StreamSpec()
|
new()
|
||||||
{
|
{
|
||||||
OriginalUrl = ParserConfig.OriginalUrl,
|
OriginalUrl = ParserConfig.OriginalUrl,
|
||||||
Url = ParserConfig.Url,
|
Url = ParserConfig.Url,
|
||||||
Playlist = new Playlist(),
|
Playlist = new Playlist(),
|
||||||
GroupId = ResString.ReLiveTs
|
GroupId = ResString.ReLiveTs
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
public Task FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void PreProcessContent()
|
public void PreProcessContent()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
@ -50,4 +50,3 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -6,18 +6,11 @@ using N_m3u8DL_RE.Parser.Config;
|
|||||||
using N_m3u8DL_RE.Parser.Constants;
|
using N_m3u8DL_RE.Parser.Constants;
|
||||||
using N_m3u8DL_RE.Parser.Mp4;
|
using N_m3u8DL_RE.Parser.Mp4;
|
||||||
using N_m3u8DL_RE.Parser.Util;
|
using N_m3u8DL_RE.Parser.Util;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Xml;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Extractor
|
namespace N_m3u8DL_RE.Parser.Extractor;
|
||||||
{
|
|
||||||
// Microsoft Smooth Streaming
|
// Microsoft Smooth Streaming
|
||||||
// https://test.playready.microsoft.com/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/manifest
|
// https://test.playready.microsoft.com/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/manifest
|
||||||
// file:///C:/Users/nilaoda/Downloads/[MS-SSTR]-180316.pdf
|
// file:///C:/Users/nilaoda/Downloads/[MS-SSTR]-180316.pdf
|
||||||
@ -46,13 +39,10 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
private void SetInitUrl()
|
private void SetInitUrl()
|
||||||
{
|
{
|
||||||
this.IsmUrl = ParserConfig.Url ?? string.Empty;
|
this.IsmUrl = ParserConfig.Url ?? string.Empty;
|
||||||
if (!string.IsNullOrEmpty(ParserConfig.BaseUrl))
|
this.BaseUrl = !string.IsNullOrEmpty(ParserConfig.BaseUrl) ? ParserConfig.BaseUrl : this.IsmUrl;
|
||||||
this.BaseUrl = ParserConfig.BaseUrl;
|
|
||||||
else
|
|
||||||
this.BaseUrl = this.IsmUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
public Task<List<StreamSpec>> ExtractStreamsAsync(string rawText)
|
||||||
{
|
{
|
||||||
var streamList = new List<StreamSpec>();
|
var streamList = new List<StreamSpec>();
|
||||||
this.IsmContent = rawText;
|
this.IsmContent = rawText;
|
||||||
@ -243,30 +233,26 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp($"[green]{fourCC}[/] not supported! Skiped.");
|
Logger.WarnMarkUp($"[green]{fourCC}[/] not supported! Skiped.");
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为视频设置默认轨道
|
// 为视频设置默认轨道
|
||||||
var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO);
|
var aL = streamList.Where(s => s.MediaType == MediaType.AUDIO).ToList();
|
||||||
var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES);
|
var sL = streamList.Where(s => s.MediaType == MediaType.SUBTITLES).ToList();
|
||||||
foreach (var item in streamList)
|
foreach (var item in streamList.Where(item => !string.IsNullOrEmpty(item.Resolution)))
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(item.Resolution))
|
if (aL.Count != 0)
|
||||||
{
|
|
||||||
if (aL.Any())
|
|
||||||
{
|
{
|
||||||
item.AudioId = aL.First().GroupId;
|
item.AudioId = aL.First().GroupId;
|
||||||
}
|
}
|
||||||
if (sL.Any())
|
if (sL.Count != 0)
|
||||||
{
|
{
|
||||||
item.SubtitleId = sL.First().GroupId;
|
item.SubtitleId = sL.First().GroupId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return streamList;
|
return Task.FromResult(streamList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -317,27 +303,28 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
await ProcessUrlAsync(streamSpecs);
|
await ProcessUrlAsync(streamSpecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ProcessUrlAsync(List<StreamSpec> streamSpecs)
|
private Task ProcessUrlAsync(List<StreamSpec> streamSpecs)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < streamSpecs.Count; i++)
|
foreach (var streamSpec in streamSpecs)
|
||||||
{
|
|
||||||
var playlist = streamSpecs[i].Playlist;
|
|
||||||
if (playlist != null)
|
|
||||||
{
|
{
|
||||||
|
var playlist = streamSpec.Playlist;
|
||||||
|
if (playlist == null) continue;
|
||||||
|
|
||||||
if (playlist.MediaInit != null)
|
if (playlist.MediaInit != null)
|
||||||
{
|
{
|
||||||
playlist.MediaInit!.Url = PreProcessUrl(playlist.MediaInit!.Url);
|
playlist.MediaInit!.Url = PreProcessUrl(playlist.MediaInit!.Url);
|
||||||
}
|
}
|
||||||
for (int ii = 0; ii < playlist!.MediaParts.Count; ii++)
|
for (var ii = 0; ii < playlist!.MediaParts.Count; ii++)
|
||||||
{
|
{
|
||||||
var part = playlist.MediaParts[ii];
|
var part = playlist.MediaParts[ii];
|
||||||
for (int iii = 0; iii < part.MediaSegments.Count; iii++)
|
foreach (var segment in part.MediaSegments)
|
||||||
{
|
{
|
||||||
part.MediaSegments[iii].Url = PreProcessUrl(part.MediaSegments[iii].Url);
|
segment.Url = PreProcessUrl(segment.Url);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string PreProcessUrl(string url)
|
public string PreProcessUrl(string url)
|
||||||
@ -398,4 +385,3 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||||||
await ProcessUrlAsync(streamSpecs);
|
await ProcessUrlAsync(streamSpecs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,12 +1,5 @@
|
|||||||
using System;
|
namespace Mp4SubtitleParser;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Mp4SubtitleParser
|
|
||||||
{
|
|
||||||
// make BinaryReader in Big Endian
|
// make BinaryReader in Big Endian
|
||||||
class BinaryReader2 : BinaryReader
|
class BinaryReader2 : BinaryReader
|
||||||
{
|
{
|
||||||
@ -67,4 +60,3 @@ namespace Mp4SubtitleParser
|
|||||||
return BitConverter.ToUInt64(data, 0);
|
return BitConverter.ToUInt64(data, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
using System;
|
using System.Text;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
namespace Mp4SubtitleParser;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Mp4SubtitleParser
|
|
||||||
{
|
|
||||||
// make BinaryWriter in Big Endian
|
// make BinaryWriter in Big Endian
|
||||||
class BinaryWriter2 : BinaryWriter
|
class BinaryWriter2 : BinaryWriter
|
||||||
{
|
{
|
||||||
@ -86,4 +81,3 @@ namespace Mp4SubtitleParser
|
|||||||
BaseStream.Write(arr);
|
BaseStream.Write(arr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace Mp4SubtitleParser
|
namespace Mp4SubtitleParser
|
||||||
{
|
{
|
||||||
@ -11,10 +10,10 @@ namespace Mp4SubtitleParser
|
|||||||
public bool isMultiDRM;
|
public bool isMultiDRM;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MP4InitUtil
|
public static class MP4InitUtil
|
||||||
{
|
{
|
||||||
private static readonly byte[] SYSTEM_ID_WIDEVINE = { 0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED };
|
private static readonly byte[] SYSTEM_ID_WIDEVINE = [0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED];
|
||||||
private static readonly byte[] SYSTEM_ID_PLAYREADY = { 0x9A, 0x04, 0xF0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xAB, 0x92, 0xE6, 0x5B, 0xE0, 0x88, 0x5F, 0x95 };
|
private static readonly byte[] SYSTEM_ID_PLAYREADY = [0x9A, 0x04, 0xF0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xAB, 0x92, 0xE6, 0x5B, 0xE0, 0x88, 0x5F, 0x95];
|
||||||
|
|
||||||
public static ParsedMP4Info ReadInit(byte[] data)
|
public static ParsedMP4Info ReadInit(byte[] data)
|
||||||
{
|
{
|
||||||
@ -28,22 +27,20 @@ namespace Mp4SubtitleParser
|
|||||||
.Box("minf", MP4Parser.Children)
|
.Box("minf", MP4Parser.Children)
|
||||||
.Box("stbl", MP4Parser.Children)
|
.Box("stbl", MP4Parser.Children)
|
||||||
.FullBox("stsd", MP4Parser.SampleDescription)
|
.FullBox("stsd", MP4Parser.SampleDescription)
|
||||||
.FullBox("pssh", (box) =>
|
.FullBox("pssh", box =>
|
||||||
{
|
{
|
||||||
if (!(box.Version == 0 || box.Version == 1))
|
if (box.Version is not (0 or 1))
|
||||||
throw new Exception("PSSH version can only be 0 or 1");
|
throw new Exception("PSSH version can only be 0 or 1");
|
||||||
var systemId = box.Reader.ReadBytes(16);
|
var systemId = box.Reader.ReadBytes(16);
|
||||||
if (SYSTEM_ID_WIDEVINE.SequenceEqual(systemId))
|
if (!SYSTEM_ID_WIDEVINE.SequenceEqual(systemId)) return;
|
||||||
{
|
|
||||||
var dataSize = box.Reader.ReadUInt32();
|
var dataSize = box.Reader.ReadUInt32();
|
||||||
var psshData = box.Reader.ReadBytes((int)dataSize);
|
var psshData = box.Reader.ReadBytes((int)dataSize);
|
||||||
info.PSSH = Convert.ToBase64String(psshData);
|
info.PSSH = Convert.ToBase64String(psshData);
|
||||||
if (info.KID == "00000000000000000000000000000000")
|
if (info.KID != "00000000000000000000000000000000") return;
|
||||||
{
|
|
||||||
info.KID = HexUtil.BytesToHex(psshData[2..18]).ToLower();
|
info.KID = HexUtil.BytesToHex(psshData[2..18]).ToLower();
|
||||||
info.isMultiDRM = true;
|
info.isMultiDRM = true;
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.FullBox("encv", MP4Parser.AllData(data => ReadBox(data, info)))
|
.FullBox("encv", MP4Parser.AllData(data => ReadBox(data, info)))
|
||||||
.FullBox("enca", MP4Parser.AllData(data => ReadBox(data, info)))
|
.FullBox("enca", MP4Parser.AllData(data => ReadBox(data, info)))
|
||||||
@ -57,11 +54,11 @@ namespace Mp4SubtitleParser
|
|||||||
private static void ReadBox(byte[] data, ParsedMP4Info info)
|
private static void ReadBox(byte[] data, ParsedMP4Info info)
|
||||||
{
|
{
|
||||||
// find schm
|
// find schm
|
||||||
var schmBytes = new byte[4] { 0x73, 0x63, 0x68, 0x6d };
|
byte[] schmBytes = [0x73, 0x63, 0x68, 0x6d];
|
||||||
var schmIndex = 0;
|
var schmIndex = 0;
|
||||||
for (int i = 0; i < data.Length - 4; i++)
|
for (var i = 0; i < data.Length - 4; i++)
|
||||||
{
|
{
|
||||||
if (new byte[4] { data[i], data[i + 1], data[i + 2], data[i + 3] }.SequenceEqual(schmBytes))
|
if (new[] { data[i], data[i + 1], data[i + 2], data[i + 3] }.SequenceEqual(schmBytes))
|
||||||
{
|
{
|
||||||
schmIndex = i;
|
schmIndex = i;
|
||||||
break;
|
break;
|
||||||
@ -75,11 +72,11 @@ namespace Mp4SubtitleParser
|
|||||||
// if (info.Scheme != "cenc") return;
|
// if (info.Scheme != "cenc") return;
|
||||||
|
|
||||||
// find KID
|
// find KID
|
||||||
var tencBytes = new byte[4] { 0x74, 0x65, 0x6E, 0x63 };
|
byte[] tencBytes = [0x74, 0x65, 0x6E, 0x63];
|
||||||
var tencIndex = -1;
|
var tencIndex = -1;
|
||||||
for (int i = 0; i < data.Length - 4; i++)
|
for (int i = 0; i < data.Length - 4; i++)
|
||||||
{
|
{
|
||||||
if (new byte[4] { data[i], data[i + 1], data[i + 2], data[i + 3] }.SequenceEqual(tencBytes))
|
if (new[] { data[i], data[i + 1], data[i + 2], data[i + 3] }.SequenceEqual(tencBytes))
|
||||||
{
|
{
|
||||||
tencIndex = i;
|
tencIndex = i;
|
||||||
break;
|
break;
|
||||||
|
@ -9,12 +9,12 @@ namespace Mp4SubtitleParser
|
|||||||
{
|
{
|
||||||
class ParsedBox
|
class ParsedBox
|
||||||
{
|
{
|
||||||
public MP4Parser Parser { get; set; }
|
public required MP4Parser Parser { get; set; }
|
||||||
public bool PartialOkay { get; set; }
|
public bool PartialOkay { get; set; }
|
||||||
public long Start { get; set; }
|
public long Start { get; set; }
|
||||||
public uint Version { get; set; } = 1000;
|
public uint Version { get; set; } = 1000;
|
||||||
public uint Flags { get; set; } = 1000;
|
public uint Flags { get; set; } = 1000;
|
||||||
public BinaryReader2 Reader { get; set; }
|
public required BinaryReader2 Reader { get; set; }
|
||||||
public bool Has64BitSize { get; set; }
|
public bool Has64BitSize { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ namespace Mp4SubtitleParser
|
|||||||
class TRUN
|
class TRUN
|
||||||
{
|
{
|
||||||
public uint SampleCount { get; set; }
|
public uint SampleCount { get; set; }
|
||||||
public List<Sample> SampleData { get; set; } = new List<Sample>();
|
public List<Sample> SampleData { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
class Sample
|
class Sample
|
||||||
@ -55,7 +55,7 @@ namespace Mp4SubtitleParser
|
|||||||
|
|
||||||
public static BoxHandler AllData(DataHandler handler)
|
public static BoxHandler AllData(DataHandler handler)
|
||||||
{
|
{
|
||||||
return (box) =>
|
return box =>
|
||||||
{
|
{
|
||||||
var all = box.Reader.GetLength() - box.Reader.GetPosition();
|
var all = box.Reader.GetLength() - box.Reader.GetPosition();
|
||||||
handler(box.Reader.ReadBytes((int)all));
|
handler(box.Reader.ReadBytes((int)all));
|
||||||
@ -129,8 +129,7 @@ namespace Mp4SubtitleParser
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
BoxHandler boxDefinition = null;
|
this.BoxDefinitions.TryGetValue(type, out BoxHandler? boxDefinition);
|
||||||
this.BoxDefinitions.TryGetValue(type, out boxDefinition);
|
|
||||||
|
|
||||||
if (boxDefinition != null)
|
if (boxDefinition != null)
|
||||||
{
|
{
|
||||||
@ -162,7 +161,7 @@ namespace Mp4SubtitleParser
|
|||||||
}
|
}
|
||||||
|
|
||||||
int payloadSize = (int)(end - reader.GetPosition());
|
int payloadSize = (int)(end - reader.GetPosition());
|
||||||
var payload = (payloadSize > 0) ? reader.ReadBytes(payloadSize) : new byte[0];
|
var payload = (payloadSize > 0) ? reader.ReadBytes(payloadSize) : [];
|
||||||
var box = new ParsedBox()
|
var box = new ParsedBox()
|
||||||
{
|
{
|
||||||
Parser = this,
|
Parser = this,
|
||||||
|
@ -3,15 +3,15 @@ using System.Text;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
|
||||||
namespace Mp4SubtitleParser
|
namespace Mp4SubtitleParser;
|
||||||
{
|
|
||||||
class SubEntity
|
class SubEntity
|
||||||
{
|
{
|
||||||
public string Begin { get; set; }
|
public required string Begin { get; set; }
|
||||||
public string End { get; set; }
|
public required string End { get; set; }
|
||||||
public string Region { get; set; }
|
public required string Region { get; set; }
|
||||||
public List<XmlElement> Contents { get; set; } = new List<XmlElement>();
|
public List<XmlElement> Contents { get; set; } = [];
|
||||||
public List<string> ContentStrings { get; set; } = new List<string>();
|
public List<string> ContentStrings { get; set; } = [];
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
@ -28,13 +28,13 @@ namespace Mp4SubtitleParser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class MP4TtmlUtil
|
public static partial class MP4TtmlUtil
|
||||||
{
|
{
|
||||||
[GeneratedRegex(" \\w+:\\w+=\\\"[^\\\"]*\\\"")]
|
[GeneratedRegex(" \\w+:\\w+=\\\"[^\\\"]*\\\"")]
|
||||||
private static partial Regex AttrRegex();
|
private static partial Regex AttrRegex();
|
||||||
[GeneratedRegex("<p.*?>(.+?)<\\/p>")]
|
[GeneratedRegex("<p.*?>(.+?)<\\/p>")]
|
||||||
private static partial Regex LabelFixRegex();
|
private static partial Regex LabelFixRegex();
|
||||||
[GeneratedRegex("\\<tt[\\s\\S]*?\\<\\/tt\\>")]
|
[GeneratedRegex(@"\<tt[\s\S]*?\<\/tt\>")]
|
||||||
private static partial Regex MultiElementsFixRegex();
|
private static partial Regex MultiElementsFixRegex();
|
||||||
[GeneratedRegex("\\<smpte:image.*xml:id=\\\"(.*?)\\\".*\\>([\\s\\S]*?)<\\/smpte:image>")]
|
[GeneratedRegex("\\<smpte:image.*xml:id=\\\"(.*?)\\\".*\\>([\\s\\S]*?)<\\/smpte:image>")]
|
||||||
private static partial Regex ImageRegex();
|
private static partial Regex ImageRegex();
|
||||||
@ -51,7 +51,7 @@ namespace Mp4SubtitleParser
|
|||||||
.Box("minf", MP4Parser.Children)
|
.Box("minf", MP4Parser.Children)
|
||||||
.Box("stbl", MP4Parser.Children)
|
.Box("stbl", MP4Parser.Children)
|
||||||
.FullBox("stsd", MP4Parser.SampleDescription)
|
.FullBox("stsd", MP4Parser.SampleDescription)
|
||||||
.Box("stpp", (box) => {
|
.Box("stpp", box => {
|
||||||
sawSTPP = true;
|
sawSTPP = true;
|
||||||
})
|
})
|
||||||
.Parse(data);
|
.Parse(data);
|
||||||
@ -65,7 +65,7 @@ namespace Mp4SubtitleParser
|
|||||||
{
|
{
|
||||||
var dt = DateTime.ParseExact(xmlTime, "HH:mm:ss.fff", System.Globalization.CultureInfo.InvariantCulture);
|
var dt = DateTime.ParseExact(xmlTime, "HH:mm:ss.fff", System.Globalization.CultureInfo.InvariantCulture);
|
||||||
var ts = TimeSpan.FromMilliseconds(dt.TimeOfDay.TotalMilliseconds + segTimeMs * index);
|
var ts = TimeSpan.FromMilliseconds(dt.TimeOfDay.TotalMilliseconds + segTimeMs * index);
|
||||||
return string.Format("{0:00}:{1:00}:{2:00}.{3:000}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds);
|
return $"{ts.Hours:00}:{ts.Minutes:00}:{ts.Seconds:00}.{ts.Milliseconds:000}";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!xmlSrc.Contains("<tt") || !xmlSrc.Contains("<head>")) return xmlSrc;
|
if (!xmlSrc.Contains("<tt") || !xmlSrc.Contains("<head>")) return xmlSrc;
|
||||||
@ -114,7 +114,7 @@ namespace Mp4SubtitleParser
|
|||||||
{
|
{
|
||||||
sb.Append(item.InnerText.Trim());
|
sb.Append(item.InnerText.Trim());
|
||||||
}
|
}
|
||||||
else if(item.NodeType == XmlNodeType.Element && item.Name == "br")
|
else if(item is { NodeType: XmlNodeType.Element, Name: "br" })
|
||||||
{
|
{
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
}
|
}
|
||||||
@ -122,21 +122,20 @@ namespace Mp4SubtitleParser
|
|||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<string> SplitMultipleRootElements(string xml)
|
private static List<string> SplitMultipleRootElements(string xml)
|
||||||
{
|
{
|
||||||
if (!MultiElementsFixRegex().IsMatch(xml)) return new List<string>();
|
return !MultiElementsFixRegex().IsMatch(xml) ? [] : MultiElementsFixRegex().Matches(xml).Select(m => m.Value).ToList();
|
||||||
return MultiElementsFixRegex().Matches(xml).Select(m => m.Value).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WebVttSub ExtractFromMp4(string item, long segTimeMs, long baseTimestamp = 0L)
|
public static WebVttSub ExtractFromMp4(string item, long segTimeMs, long baseTimestamp = 0L)
|
||||||
{
|
{
|
||||||
return ExtractFromMp4s(new string[] { item }, segTimeMs, baseTimestamp);
|
return ExtractFromMp4s([item], segTimeMs, baseTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WebVttSub ExtractFromMp4s(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
|
private static WebVttSub ExtractFromMp4s(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
|
||||||
{
|
{
|
||||||
// read ttmls
|
// read ttmls
|
||||||
List<string> xmls = new List<string>();
|
List<string> xmls = [];
|
||||||
int segIndex = 0;
|
int segIndex = 0;
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
@ -145,7 +144,7 @@ namespace Mp4SubtitleParser
|
|||||||
var sawMDAT = false;
|
var sawMDAT = false;
|
||||||
// parse media
|
// parse media
|
||||||
new MP4Parser()
|
new MP4Parser()
|
||||||
.Box("mdat", MP4Parser.AllData((data) =>
|
.Box("mdat", MP4Parser.AllData(data =>
|
||||||
{
|
{
|
||||||
sawMDAT = true;
|
sawMDAT = true;
|
||||||
// Join this to any previous payload, in case the mp4 has multiple
|
// Join this to any previous payload, in case the mp4 has multiple
|
||||||
@ -161,10 +160,7 @@ namespace Mp4SubtitleParser
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var datas = SplitMultipleRootElements(Encoding.UTF8.GetString(data));
|
var datas = SplitMultipleRootElements(Encoding.UTF8.GetString(data));
|
||||||
foreach (var item in datas)
|
xmls.AddRange(datas);
|
||||||
{
|
|
||||||
xmls.Add(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.Parse(dataSeg,/* partialOkay= */ false);
|
.Parse(dataSeg,/* partialOkay= */ false);
|
||||||
@ -176,25 +172,18 @@ namespace Mp4SubtitleParser
|
|||||||
|
|
||||||
public static WebVttSub ExtractFromTTML(string item, long segTimeMs, long baseTimestamp = 0L)
|
public static WebVttSub ExtractFromTTML(string item, long segTimeMs, long baseTimestamp = 0L)
|
||||||
{
|
{
|
||||||
return ExtractFromTTMLs(new string[] { item }, segTimeMs, baseTimestamp);
|
return ExtractFromTTMLs([item], segTimeMs, baseTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WebVttSub ExtractFromTTMLs(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
|
public static WebVttSub ExtractFromTTMLs(IEnumerable<string> items, long segTimeMs, long baseTimestamp = 0L)
|
||||||
{
|
{
|
||||||
// read ttmls
|
// read ttmls
|
||||||
List<string> xmls = new List<string>();
|
List<string> xmls = [];
|
||||||
int segIndex = 0;
|
int segIndex = 0;
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
var xml = File.ReadAllText(item);
|
var xml = File.ReadAllText(item);
|
||||||
if (segTimeMs != 0)
|
xmls.Add(segTimeMs != 0 ? ShiftTime(xml, segTimeMs, segIndex) : xml);
|
||||||
{
|
|
||||||
xmls.Add(ShiftTime(xml, segTimeMs, segIndex));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
xmls.Add(xml);
|
|
||||||
}
|
|
||||||
segIndex++;
|
segIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,10 +313,10 @@ namespace Mp4SubtitleParser
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var id = _bgImg.Replace("#", "");
|
var id = _bgImg.Replace("#", "");
|
||||||
if (imageDic.ContainsKey(id))
|
if (imageDic.TryGetValue(id, out var value))
|
||||||
{
|
{
|
||||||
var _span = new XmlDocument().CreateElement("span");
|
var _span = new XmlDocument().CreateElement("span");
|
||||||
_span.InnerText = $"Base64::{imageDic[id]}";
|
_span.InnerText = $"Base64::{value}";
|
||||||
sub.Contents.Add(_span);
|
sub.Contents.Add(_span);
|
||||||
sub.ContentStrings.Add(_span.OuterXml);
|
sub.ContentStrings.Add(_span.OuterXml);
|
||||||
}
|
}
|
||||||
@ -336,8 +325,8 @@ namespace Mp4SubtitleParser
|
|||||||
// Check if one <p> has been splitted
|
// Check if one <p> has been splitted
|
||||||
var index = finalSubs.FindLastIndex(s => s.End == _begin && s.Region == _region && s.ContentStrings.SequenceEqual(sub.ContentStrings));
|
var index = finalSubs.FindLastIndex(s => s.End == _begin && s.Region == _region && s.ContentStrings.SequenceEqual(sub.ContentStrings));
|
||||||
// Skip empty lines
|
// Skip empty lines
|
||||||
if (sub.ContentStrings.Count > 0)
|
if (sub.ContentStrings.Count <= 0)
|
||||||
{
|
continue;
|
||||||
// Extend <p> duration
|
// Extend <p> duration
|
||||||
if (index != -1)
|
if (index != -1)
|
||||||
finalSubs[index].End = sub.End;
|
finalSubs[index].End = sub.End;
|
||||||
@ -345,7 +334,6 @@ namespace Mp4SubtitleParser
|
|||||||
finalSubs.Add(sub);
|
finalSubs.Add(sub);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var dic = new Dictionary<string, string>();
|
var dic = new Dictionary<string, string>();
|
||||||
@ -372,7 +360,7 @@ namespace Mp4SubtitleParser
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StringBuilder vtt = new StringBuilder();
|
var vtt = new StringBuilder();
|
||||||
vtt.AppendLine("WEBVTT");
|
vtt.AppendLine("WEBVTT");
|
||||||
foreach (var item in dic)
|
foreach (var item in dic)
|
||||||
{
|
{
|
||||||
@ -384,4 +372,3 @@ namespace Mp4SubtitleParser
|
|||||||
return WebVttSub.Parse(vtt.ToString(), baseTimestamp);
|
return WebVttSub.Parse(vtt.ToString(), baseTimestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Mp4SubtitleParser
|
namespace Mp4SubtitleParser;
|
||||||
{
|
|
||||||
public class MP4VttUtil
|
public static class MP4VttUtil
|
||||||
{
|
{
|
||||||
public static (bool, uint) CheckInit(byte[] data)
|
public static (bool, uint) CheckInit(byte[] data)
|
||||||
{
|
{
|
||||||
@ -15,16 +15,16 @@ namespace Mp4SubtitleParser
|
|||||||
.Box("moov", MP4Parser.Children)
|
.Box("moov", MP4Parser.Children)
|
||||||
.Box("trak", MP4Parser.Children)
|
.Box("trak", MP4Parser.Children)
|
||||||
.Box("mdia", MP4Parser.Children)
|
.Box("mdia", MP4Parser.Children)
|
||||||
.FullBox("mdhd", (box) =>
|
.FullBox("mdhd", box =>
|
||||||
{
|
{
|
||||||
if (!(box.Version == 0 || box.Version == 1))
|
if (box.Version is not (0 or 1))
|
||||||
throw new Exception("MDHD version can only be 0 or 1");
|
throw new Exception("MDHD version can only be 0 or 1");
|
||||||
timescale = MP4Parser.ParseMDHD(box.Reader, box.Version);
|
timescale = MP4Parser.ParseMDHD(box.Reader, box.Version);
|
||||||
})
|
})
|
||||||
.Box("minf", MP4Parser.Children)
|
.Box("minf", MP4Parser.Children)
|
||||||
.Box("stbl", MP4Parser.Children)
|
.Box("stbl", MP4Parser.Children)
|
||||||
.FullBox("stsd", MP4Parser.SampleDescription)
|
.FullBox("stsd", MP4Parser.SampleDescription)
|
||||||
.Box("wvtt", (box) => {
|
.Box("wvtt", _ => {
|
||||||
// A valid vtt init segment, though we have no actual subtitles yet.
|
// A valid vtt init segment, though we have no actual subtitles yet.
|
||||||
sawWVTT = true;
|
sawWVTT = true;
|
||||||
})
|
})
|
||||||
@ -38,7 +38,7 @@ namespace Mp4SubtitleParser
|
|||||||
if (timescale == 0)
|
if (timescale == 0)
|
||||||
throw new Exception("Missing timescale for VTT content!");
|
throw new Exception("Missing timescale for VTT content!");
|
||||||
|
|
||||||
List<SubCue> cues = new();
|
List<SubCue> cues = [];
|
||||||
|
|
||||||
foreach (var item in files)
|
foreach (var item in files)
|
||||||
{
|
{
|
||||||
@ -50,27 +50,27 @@ namespace Mp4SubtitleParser
|
|||||||
byte[]? rawPayload = null;
|
byte[]? rawPayload = null;
|
||||||
ulong baseTime = 0;
|
ulong baseTime = 0;
|
||||||
ulong defaultDuration = 0;
|
ulong defaultDuration = 0;
|
||||||
List<Sample> presentations = new();
|
List<Sample> presentations = [];
|
||||||
|
|
||||||
|
|
||||||
// parse media
|
// parse media
|
||||||
new MP4Parser()
|
new MP4Parser()
|
||||||
.Box("moof", MP4Parser.Children)
|
.Box("moof", MP4Parser.Children)
|
||||||
.Box("traf", MP4Parser.Children)
|
.Box("traf", MP4Parser.Children)
|
||||||
.FullBox("tfdt", (box) =>
|
.FullBox("tfdt", box =>
|
||||||
{
|
{
|
||||||
sawTFDT = true;
|
sawTFDT = true;
|
||||||
if (!(box.Version == 0 || box.Version == 1))
|
if (box.Version is not (0 or 1))
|
||||||
throw new Exception("TFDT version can only be 0 or 1");
|
throw new Exception("TFDT version can only be 0 or 1");
|
||||||
baseTime = MP4Parser.ParseTFDT(box.Reader, box.Version);
|
baseTime = MP4Parser.ParseTFDT(box.Reader, box.Version);
|
||||||
})
|
})
|
||||||
.FullBox("tfhd", (box) =>
|
.FullBox("tfhd", box =>
|
||||||
{
|
{
|
||||||
if (box.Flags == 1000)
|
if (box.Flags == 1000)
|
||||||
throw new Exception("A TFHD box should have a valid flags value");
|
throw new Exception("A TFHD box should have a valid flags value");
|
||||||
defaultDuration = MP4Parser.ParseTFHD(box.Reader, box.Flags).DefaultSampleDuration;
|
defaultDuration = MP4Parser.ParseTFHD(box.Reader, box.Flags).DefaultSampleDuration;
|
||||||
})
|
})
|
||||||
.FullBox("trun", (box) =>
|
.FullBox("trun", box =>
|
||||||
{
|
{
|
||||||
sawTRUN = true;
|
sawTRUN = true;
|
||||||
if (box.Version == 1000)
|
if (box.Version == 1000)
|
||||||
@ -79,7 +79,7 @@ namespace Mp4SubtitleParser
|
|||||||
throw new Exception("A TRUN box should have a valid flags value");
|
throw new Exception("A TRUN box should have a valid flags value");
|
||||||
presentations = MP4Parser.ParseTRUN(box.Reader, box.Version, box.Flags).SampleData;
|
presentations = MP4Parser.ParseTRUN(box.Reader, box.Version, box.Flags).SampleData;
|
||||||
})
|
})
|
||||||
.Box("mdat", MP4Parser.AllData((data) =>
|
.Box("mdat", MP4Parser.AllData(data =>
|
||||||
{
|
{
|
||||||
if (sawMDAT)
|
if (sawMDAT)
|
||||||
throw new Exception("VTT cues in mp4 with multiple MDAT are not currently supported");
|
throw new Exception("VTT cues in mp4 with multiple MDAT are not currently supported");
|
||||||
@ -139,8 +139,6 @@ namespace Mp4SubtitleParser
|
|||||||
{
|
{
|
||||||
if (payload != null)
|
if (payload != null)
|
||||||
{
|
{
|
||||||
if (timescale == 0)
|
|
||||||
throw new Exception("Timescale should not be zero!");
|
|
||||||
var cue = ParseVTTC(
|
var cue = ParseVTTC(
|
||||||
payload,
|
payload,
|
||||||
0 + (double)startTime / timescale,
|
0 + (double)startTime / timescale,
|
||||||
@ -192,15 +190,15 @@ namespace Mp4SubtitleParser
|
|||||||
string id = string.Empty;
|
string id = string.Empty;
|
||||||
string settings = string.Empty;
|
string settings = string.Empty;
|
||||||
new MP4Parser()
|
new MP4Parser()
|
||||||
.Box("payl", MP4Parser.AllData((data) =>
|
.Box("payl", MP4Parser.AllData(data =>
|
||||||
{
|
{
|
||||||
payload = Encoding.UTF8.GetString(data);
|
payload = Encoding.UTF8.GetString(data);
|
||||||
}))
|
}))
|
||||||
.Box("iden", MP4Parser.AllData((data) =>
|
.Box("iden", MP4Parser.AllData(data =>
|
||||||
{
|
{
|
||||||
id = Encoding.UTF8.GetString(data);
|
id = Encoding.UTF8.GetString(data);
|
||||||
}))
|
}))
|
||||||
.Box("sttg", MP4Parser.AllData((data) =>
|
.Box("sttg", MP4Parser.AllData(data =>
|
||||||
{
|
{
|
||||||
settings = Encoding.UTF8.GetString(data);
|
settings = Encoding.UTF8.GetString(data);
|
||||||
}))
|
}))
|
||||||
@ -213,4 +211,3 @@ namespace Mp4SubtitleParser
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -9,11 +9,11 @@ using System.Text.RegularExpressions;
|
|||||||
// https://github.com/yt-dlp/yt-dlp/blob/3639df54c3298e35b5ae2a96a25bc4d3c38950d0/yt_dlp/downloader/ism.py
|
// https://github.com/yt-dlp/yt-dlp/blob/3639df54c3298e35b5ae2a96a25bc4d3c38950d0/yt_dlp/downloader/ism.py
|
||||||
// https://github.com/google/ExoPlayer/blob/a9444c880230d2c2c79097e89259ce0b9f80b87d/library/extractor/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java#L38
|
// https://github.com/google/ExoPlayer/blob/a9444c880230d2c2c79097e89259ce0b9f80b87d/library/extractor/src/main/java/com/google/android/exoplayer2/video/HevcConfig.java#L38
|
||||||
// https://github.com/sannies/mp4parser/blob/master/isoparser/src/main/java/org/mp4parser/boxes/iso14496/part15/HevcDecoderConfigurationRecord.java
|
// https://github.com/sannies/mp4parser/blob/master/isoparser/src/main/java/org/mp4parser/boxes/iso14496/part15/HevcDecoderConfigurationRecord.java
|
||||||
namespace N_m3u8DL_RE.Parser.Mp4
|
namespace N_m3u8DL_RE.Parser.Mp4;
|
||||||
{
|
|
||||||
public partial class MSSMoovProcessor
|
public partial class MSSMoovProcessor
|
||||||
{
|
{
|
||||||
[GeneratedRegex("\\<KID\\>(.*?)\\<")]
|
[GeneratedRegex(@"\<KID\>(.*?)\<")]
|
||||||
private static partial Regex KIDRegex();
|
private static partial Regex KIDRegex();
|
||||||
|
|
||||||
private static string StartCode = "00000001";
|
private static string StartCode = "00000001";
|
||||||
@ -23,9 +23,9 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
private string CodecPrivateData;
|
private string CodecPrivateData;
|
||||||
private int Timesacle;
|
private int Timesacle;
|
||||||
private long Duration;
|
private long Duration;
|
||||||
private string Language { get => StreamSpec.Language ?? "und"; }
|
private string Language => StreamSpec.Language ?? "und";
|
||||||
private int Width { get => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').First()); }
|
private int Width => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').First());
|
||||||
private int Height { get => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').Last()); }
|
private int Height => int.Parse((StreamSpec.Resolution ?? "0x0").Split('x').Last());
|
||||||
private string StreamType;
|
private string StreamType;
|
||||||
private int Channels;
|
private int Channels;
|
||||||
private int BitsPerSample;
|
private int BitsPerSample;
|
||||||
@ -36,8 +36,8 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
private bool IsProtection;
|
private bool IsProtection;
|
||||||
private string ProtectionSystemId;
|
private string ProtectionSystemId;
|
||||||
private string ProtectionData;
|
private string ProtectionData;
|
||||||
private string ProtecitonKID;
|
private string? ProtectionKID;
|
||||||
private string ProtecitonKID_PR;
|
private string? ProtectionKID_PR;
|
||||||
private byte[] UnityMatrix
|
private byte[] UnityMatrix
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -60,10 +60,9 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
private static byte TRACK_IN_MOVIE = 0x2;
|
private static byte TRACK_IN_MOVIE = 0x2;
|
||||||
private static byte TRACK_IN_PREVIEW = 0x4;
|
private static byte TRACK_IN_PREVIEW = 0x4;
|
||||||
private static byte SELF_CONTAINED = 0x1;
|
private static byte SELF_CONTAINED = 0x1;
|
||||||
private static List<string> SupportedFourCC = new List<string>()
|
|
||||||
{
|
private static List<string> SupportedFourCC =
|
||||||
"HVC1","HEV1","AACL","AACH","EC-3","H264","AVC1","DAVC","AVC1","TTML","DVHE","DVH1"
|
["HVC1", "HEV1", "AACL", "AACH", "EC-3", "H264", "AVC1", "DAVC", "AVC1", "TTML", "DVHE", "DVH1"];
|
||||||
};
|
|
||||||
|
|
||||||
public MSSMoovProcessor(StreamSpec streamSpec)
|
public MSSMoovProcessor(StreamSpec streamSpec)
|
||||||
{
|
{
|
||||||
@ -95,7 +94,7 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string[] HEVC_GENERAL_PROFILE_SPACE_STRINGS = new string[] { "", "A", "B", "C" };
|
private static string[] HEVC_GENERAL_PROFILE_SPACE_STRINGS = ["", "A", "B", "C"];
|
||||||
private int SamplingFrequencyIndex(int samplingRate) => samplingRate switch
|
private int SamplingFrequencyIndex(int samplingRate) => samplingRate switch
|
||||||
{
|
{
|
||||||
96000 => 0x0,
|
96000 => 0x0,
|
||||||
@ -167,13 +166,13 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
var text = Encoding.ASCII.GetString(bytes);
|
var text = Encoding.ASCII.GetString(bytes);
|
||||||
var kidBytes = Convert.FromBase64String(KIDRegex().Match(text).Groups[1].Value);
|
var kidBytes = Convert.FromBase64String(KIDRegex().Match(text).Groups[1].Value);
|
||||||
// save kid for playready
|
// save kid for playready
|
||||||
this.ProtecitonKID_PR = HexUtil.BytesToHex(kidBytes);
|
this.ProtectionKID_PR = HexUtil.BytesToHex(kidBytes);
|
||||||
// fix byte order
|
// fix byte order
|
||||||
var reverse1 = new byte[4] { kidBytes[3], kidBytes[2], kidBytes[1], kidBytes[0] };
|
var reverse1 = new[] { kidBytes[3], kidBytes[2], kidBytes[1], kidBytes[0] };
|
||||||
var reverse2 = new byte[4] { kidBytes[5], kidBytes[4], kidBytes[7], kidBytes[6] };
|
var reverse2 = new[] { kidBytes[5], kidBytes[4], kidBytes[7], kidBytes[6] };
|
||||||
Array.Copy(reverse1, 0, kidBytes, 0, reverse1.Length);
|
Array.Copy(reverse1, 0, kidBytes, 0, reverse1.Length);
|
||||||
Array.Copy(reverse2, 0, kidBytes, 4, reverse1.Length);
|
Array.Copy(reverse2, 0, kidBytes, 4, reverse1.Length);
|
||||||
this.ProtecitonKID = HexUtil.BytesToHex(kidBytes);
|
this.ProtectionKID = HexUtil.BytesToHex(kidBytes);
|
||||||
}
|
}
|
||||||
// widevine
|
// widevine
|
||||||
else if (ProtectionSystemId.ToUpper() == "EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED")
|
else if (ProtectionSystemId.ToUpper() == "EDEF8BA9-79D6-4ACE-A3C8-27DCD51D21ED")
|
||||||
@ -217,16 +216,16 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
|
|
||||||
var schmPayload = new List<byte>();
|
var schmPayload = new List<byte>();
|
||||||
schmPayload.AddRange(Encoding.ASCII.GetBytes("cenc")); // scheme_type 'cenc' => common encryption
|
schmPayload.AddRange(Encoding.ASCII.GetBytes("cenc")); // scheme_type 'cenc' => common encryption
|
||||||
schmPayload.AddRange(new byte[] { 0, 1, 0, 0 }); //scheme_version Major version 1, Minor version 0
|
schmPayload.AddRange([0, 1, 0, 0]); // scheme_version Major version 1, Minor version 0
|
||||||
var schmBox = FullBox("schm", 0, 0, schmPayload.ToArray());
|
var schmBox = FullBox("schm", 0, 0, schmPayload.ToArray());
|
||||||
|
|
||||||
sinfPayload.AddRange(schmBox);
|
sinfPayload.AddRange(schmBox);
|
||||||
|
|
||||||
var tencPayload = new List<byte>();
|
var tencPayload = new List<byte>();
|
||||||
tencPayload.AddRange(new byte[] { 0, 0 });
|
tencPayload.AddRange([0, 0]);
|
||||||
tencPayload.Add(0x1); // default_IsProtected
|
tencPayload.Add(0x1); // default_IsProtected
|
||||||
tencPayload.Add(0x8); // default_Per_Sample_IV_size
|
tencPayload.Add(0x8); // default_Per_Sample_IV_size
|
||||||
tencPayload.AddRange(HexUtil.HexToBytes(ProtecitonKID)); //default_KID
|
tencPayload.AddRange(HexUtil.HexToBytes(ProtectionKID)); // default_KID
|
||||||
// tencPayload.Add(0x8);// default_constant_IV_size
|
// tencPayload.Add(0x8);// default_constant_IV_size
|
||||||
// tencPayload.AddRange(new byte[8]);// default_constant_IV
|
// tencPayload.AddRange(new byte[8]);// default_constant_IV
|
||||||
var tencBox = FullBox("tenc", 0, 0, tencPayload.ToArray());
|
var tencBox = FullBox("tenc", 0, 0, tencPayload.ToArray());
|
||||||
@ -368,7 +367,7 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
}
|
}
|
||||||
else if (StreamType == "text")
|
else if (StreamType == "text")
|
||||||
{
|
{
|
||||||
minfPayload.AddRange(FullBox("sthd", 0, 0, new byte[0])); //Subtitle Media Header
|
minfPayload.AddRange(FullBox("sthd", 0, 0, [])); // Subtitle Media Header
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -377,7 +376,7 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
|
|
||||||
var drefPayload = new List<byte>();
|
var drefPayload = new List<byte>();
|
||||||
drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(1); // entry count
|
drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(0); drefPayload.Add(1); // entry count
|
||||||
drefPayload.AddRange(FullBox("url ", 0, SELF_CONTAINED, new byte[0])); //Data Entry URL Box
|
drefPayload.AddRange(FullBox("url ", 0, SELF_CONTAINED, [])); // Data Entry URL Box
|
||||||
|
|
||||||
var dinfPayload = FullBox("dref", 0, 0, drefPayload.ToArray()); // Data Reference Box
|
var dinfPayload = FullBox("dref", 0, 0, drefPayload.ToArray()); // Data Reference Box
|
||||||
minfPayload.AddRange(Box("dinf", dinfPayload.ToArray())); // Data Information Box
|
minfPayload.AddRange(Box("dinf", dinfPayload.ToArray())); // Data Information Box
|
||||||
@ -466,11 +465,8 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
writer.Write(sinfBox);
|
writer.Write(sinfBox);
|
||||||
return Box("enca", stream.ToArray()); // Encrypted Audio
|
return Box("enca", stream.ToArray()); // Encrypted Audio
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return Box("mp4a", stream.ToArray());
|
return Box("mp4a", stream.ToArray());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (FourCC == "EC-3")
|
if (FourCC == "EC-3")
|
||||||
{
|
{
|
||||||
if (IsProtection)
|
if (IsProtection)
|
||||||
@ -479,12 +475,9 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
writer.Write(sinfBox);
|
writer.Write(sinfBox);
|
||||||
return Box("enca", stream.ToArray()); // Encrypted Audio
|
return Box("enca", stream.ToArray()); // Encrypted Audio
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return Box("ec-3", stream.ToArray());
|
return Box("ec-3", stream.ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (StreamType == "video")
|
else if (StreamType == "video")
|
||||||
{
|
{
|
||||||
writer.WriteUShort(0); // pre defined
|
writer.WriteUShort(0); // pre defined
|
||||||
@ -507,11 +500,11 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
|
|
||||||
var codecPrivateData = HexUtil.HexToBytes(CodecPrivateData);
|
var codecPrivateData = HexUtil.HexToBytes(CodecPrivateData);
|
||||||
|
|
||||||
if (FourCC == "H264" || FourCC == "AVC1" || FourCC == "DAVC" || FourCC == "AVC1")
|
if (FourCC is "H264" or "AVC1" or "DAVC")
|
||||||
{
|
{
|
||||||
var arr = CodecPrivateData.Split(new[] { StartCode }, StringSplitOptions.RemoveEmptyEntries);
|
var arr = CodecPrivateData.Split([StartCode], StringSplitOptions.RemoveEmptyEntries);
|
||||||
var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 7).First());
|
var sps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 7));
|
||||||
var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 8).First());
|
var pps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] & 0x1F) == 8));
|
||||||
// make avcC
|
// make avcC
|
||||||
var avcC = GetAvcC(sps, pps);
|
var avcC = GetAvcC(sps, pps);
|
||||||
writer.Write(avcC);
|
writer.Write(avcC);
|
||||||
@ -521,17 +514,14 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
writer.Write(sinfBox);
|
writer.Write(sinfBox);
|
||||||
return Box("encv", stream.ToArray()); // Encrypted Video
|
return Box("encv", stream.ToArray()); // Encrypted Video
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return Box("avc1", stream.ToArray()); // AVC Simple Entry
|
return Box("avc1", stream.ToArray()); // AVC Simple Entry
|
||||||
}
|
}
|
||||||
}
|
if (FourCC is "HVC1" or "HEV1")
|
||||||
else if (FourCC == "HVC1" || FourCC == "HEV1")
|
|
||||||
{
|
{
|
||||||
var arr = CodecPrivateData.Split(new[] { StartCode }, StringSplitOptions.RemoveEmptyEntries);
|
var arr = CodecPrivateData.Split([StartCode], StringSplitOptions.RemoveEmptyEntries);
|
||||||
var vps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20).First());
|
var vps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20));
|
||||||
var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21).First());
|
var sps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21));
|
||||||
var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22).First());
|
var pps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22));
|
||||||
// make hvcC
|
// make hvcC
|
||||||
var hvcC = GetHvcC(sps, pps, vps);
|
var hvcC = GetHvcC(sps, pps, vps);
|
||||||
writer.Write(hvcC);
|
writer.Write(hvcC);
|
||||||
@ -541,18 +531,15 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
writer.Write(sinfBox);
|
writer.Write(sinfBox);
|
||||||
return Box("encv", stream.ToArray()); // Encrypted Video
|
return Box("encv", stream.ToArray()); // Encrypted Video
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return Box("hvc1", stream.ToArray()); // HEVC Simple Entry
|
return Box("hvc1", stream.ToArray()); // HEVC Simple Entry
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// 杜比视界也按照hevc处理
|
// 杜比视界也按照hevc处理
|
||||||
else if (FourCC == "DVHE" || FourCC == "DVH1")
|
if (FourCC is "DVHE" or "DVH1")
|
||||||
{
|
{
|
||||||
var arr = CodecPrivateData.Split(new[] { StartCode }, StringSplitOptions.RemoveEmptyEntries);
|
var arr = CodecPrivateData.Split([StartCode], StringSplitOptions.RemoveEmptyEntries);
|
||||||
var vps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20).First());
|
var vps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x20));
|
||||||
var sps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21).First());
|
var sps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x21));
|
||||||
var pps = HexUtil.HexToBytes(arr.Where(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22).First());
|
var pps = HexUtil.HexToBytes(arr.First(x => (HexUtil.HexToBytes(x[0..2])[0] >> 1) == 0x22));
|
||||||
// make hvcC
|
// make hvcC
|
||||||
var hvcC = GetHvcC(sps, pps, vps, "dvh1");
|
var hvcC = GetHvcC(sps, pps, vps, "dvh1");
|
||||||
writer.Write(hvcC);
|
writer.Write(hvcC);
|
||||||
@ -562,16 +549,11 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
writer.Write(sinfBox);
|
writer.Write(sinfBox);
|
||||||
return Box("encv", stream.ToArray()); // Encrypted Video
|
return Box("encv", stream.ToArray()); // Encrypted Video
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return Box("dvh1", stream.ToArray()); // HEVC Simple Entry
|
return Box("dvh1", stream.ToArray()); // HEVC Simple Entry
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (StreamType == "text")
|
else if (StreamType == "text")
|
||||||
{
|
{
|
||||||
if (FourCC == "TTML")
|
if (FourCC == "TTML")
|
||||||
@ -581,11 +563,8 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
writer.Write("\0"); // auxilary mime types(??)
|
writer.Write("\0"); // auxilary mime types(??)
|
||||||
return Box("stpp", stream.ToArray()); // TTML Simple Entry
|
return Box("stpp", stream.ToArray()); // TTML Simple Entry
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
@ -635,7 +614,7 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
while (_reader.BaseStream.Position < _reader.BaseStream.Length)
|
while (_reader.BaseStream.Position < _reader.BaseStream.Length)
|
||||||
{
|
{
|
||||||
encList.Add(_reader.ReadByte());
|
encList.Add(_reader.ReadByte());
|
||||||
if (encList.Count >= 3 && encList[encList.Count - 3] == 0x00 && encList[encList.Count - 2] == 0x00 && encList[encList.Count - 1] == 0x03)
|
if (encList is [.., 0x00, 0x00, 0x03])
|
||||||
{
|
{
|
||||||
encList.RemoveAt(encList.Count - 1);
|
encList.RemoveAt(encList.Count - 1);
|
||||||
}
|
}
|
||||||
@ -774,10 +753,10 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
using var _stream = new MemoryStream();
|
using var _stream = new MemoryStream();
|
||||||
using var _writer = new BinaryWriter2(_stream);
|
using var _writer = new BinaryWriter2(_stream);
|
||||||
var sysIdData = HexUtil.HexToBytes("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed".Replace("-", ""));
|
var sysIdData = HexUtil.HexToBytes("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed".Replace("-", ""));
|
||||||
//var kid = HexUtil.HexToBytes(ProtecitonKID);
|
// var kid = HexUtil.HexToBytes(ProtectionKID);
|
||||||
|
|
||||||
_writer.Write(sysIdData); // SystemID 16 bytes
|
_writer.Write(sysIdData); // SystemID 16 bytes
|
||||||
var psshData = HexUtil.HexToBytes($"08011210{ProtecitonKID}1A046E647265220400000000");
|
var psshData = HexUtil.HexToBytes($"08011210{ProtectionKID}1A046E647265220400000000");
|
||||||
_writer.WriteUInt(psshData.Length); // Size of Data 4 bytes
|
_writer.WriteUInt(psshData.Length); // Size of Data 4 bytes
|
||||||
_writer.Write(psshData); // Data
|
_writer.Write(psshData); // Data
|
||||||
var psshBox = FullBox("pssh", 0, 0, _stream.ToArray());
|
var psshBox = FullBox("pssh", 0, 0, _stream.ToArray());
|
||||||
@ -805,7 +784,7 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
new MP4Parser()
|
new MP4Parser()
|
||||||
.Box("moof", MP4Parser.Children)
|
.Box("moof", MP4Parser.Children)
|
||||||
.Box("traf", MP4Parser.Children)
|
.Box("traf", MP4Parser.Children)
|
||||||
.FullBox("tfhd", (box) =>
|
.FullBox("tfhd", box =>
|
||||||
{
|
{
|
||||||
TrackId = (int)box.Reader.ReadUInt32();
|
TrackId = (int)box.Reader.ReadUInt32();
|
||||||
})
|
})
|
||||||
@ -888,4 +867,3 @@ namespace N_m3u8DL_RE.Parser.Mp4
|
|||||||
return stream.ToArray();
|
return stream.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>library</OutputType>
|
<OutputType>library</OutputType>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<RootNamespace>N_m3u8DL_RE.Parser</RootNamespace>
|
<RootNamespace>N_m3u8DL_RE.Parser</RootNamespace>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor
|
namespace N_m3u8DL_RE.Parser.Processor;
|
||||||
{
|
|
||||||
public abstract class ContentProcessor
|
public abstract class ContentProcessor
|
||||||
{
|
{
|
||||||
public abstract bool CanProcess(ExtractorType extractorType, string rawText, ParserConfig parserConfig);
|
public abstract bool CanProcess(ExtractorType extractorType, string rawText, ParserConfig parserConfig);
|
||||||
public abstract string Process(string rawText, ParserConfig parserConfig);
|
public abstract string Process(string rawText, ParserConfig parserConfig);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor.DASH
|
namespace N_m3u8DL_RE.Parser.Processor.DASH;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 西瓜视频处理
|
/// XG视频处理
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DefaultDASHContentProcessor : ContentProcessor
|
public class DefaultDASHContentProcessor : ContentProcessor
|
||||||
{
|
{
|
||||||
@ -18,11 +13,7 @@ namespace N_m3u8DL_RE.Parser.Processor.DASH
|
|||||||
{
|
{
|
||||||
if (extractorType != ExtractorType.MPEG_DASH) return false;
|
if (extractorType != ExtractorType.MPEG_DASH) return false;
|
||||||
|
|
||||||
if (mpdContent.Contains("<mas:") && !mpdContent.Contains("xmlns:mas"))
|
return mpdContent.Contains("<mas:") && !mpdContent.Contains("xmlns:mas");
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Process(string mpdContent, ParserConfig parserConfig)
|
public override string Process(string mpdContent, ParserConfig parserConfig)
|
||||||
@ -33,4 +24,3 @@ namespace N_m3u8DL_RE.Parser.Processor.DASH
|
|||||||
return mpdContent;
|
return mpdContent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,24 +1,18 @@
|
|||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Web;
|
using System.Web;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor
|
namespace N_m3u8DL_RE.Parser.Processor;
|
||||||
{
|
|
||||||
public class DefaultUrlProcessor : UrlProcessor
|
public class DefaultUrlProcessor : UrlProcessor
|
||||||
{
|
{
|
||||||
public override bool CanProcess(ExtractorType extractorType, string oriUrl, ParserConfig paserConfig) => paserConfig.AppendUrlParams;
|
public override bool CanProcess(ExtractorType extractorType, string oriUrl, ParserConfig paserConfig) => paserConfig.AppendUrlParams;
|
||||||
|
|
||||||
public override string Process(string oriUrl, ParserConfig paserConfig)
|
public override string Process(string oriUrl, ParserConfig paserConfig)
|
||||||
{
|
{
|
||||||
if (oriUrl.StartsWith("http"))
|
if (!oriUrl.StartsWith("http")) return oriUrl;
|
||||||
{
|
|
||||||
var uriFromConfig = new Uri(paserConfig.Url);
|
var uriFromConfig = new Uri(paserConfig.Url);
|
||||||
var uriFromConfigQuery = HttpUtility.ParseQueryString(uriFromConfig.Query);
|
var uriFromConfigQuery = HttpUtility.ParseQueryString(uriFromConfig.Query);
|
||||||
|
|
||||||
@ -32,15 +26,12 @@ namespace N_m3u8DL_RE.Parser.Processor
|
|||||||
newQuery.Add(item, uriFromConfigQuery.Get(item));
|
newQuery.Add(item, uriFromConfigQuery.Get(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(newQuery.ToString()))
|
if (string.IsNullOrEmpty(newQuery.ToString())) return oriUrl;
|
||||||
{
|
|
||||||
Logger.Debug("Before: " + oriUrl);
|
Logger.Debug("Before: " + oriUrl);
|
||||||
oriUrl = (oldUri.GetLeftPart(UriPartial.Path) + "?" + newQuery.ToString()).TrimEnd('?');
|
oriUrl = (oldUri.GetLeftPart(UriPartial.Path) + "?" + newQuery).TrimEnd('?');
|
||||||
Logger.Debug("After: " + oriUrl);
|
Logger.Debug("After: " + oriUrl);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return oriUrl;
|
return oriUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,28 +1,23 @@
|
|||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Parser.Constants;
|
using N_m3u8DL_RE.Parser.Constants;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor.HLS
|
namespace N_m3u8DL_RE.Parser.Processor.HLS;
|
||||||
{
|
|
||||||
public partial class DefaultHLSContentProcessor : ContentProcessor
|
public partial class DefaultHLSContentProcessor : ContentProcessor
|
||||||
{
|
{
|
||||||
[GeneratedRegex("#EXT-X-DISCONTINUITY\\s+#EXT-X-MAP:URI=\\\"(.*?)\\\",BYTERANGE=\\\"(.*?)\\\"")]
|
[GeneratedRegex("#EXT-X-DISCONTINUITY\\s+#EXT-X-MAP:URI=\\\"(.*?)\\\",BYTERANGE=\\\"(.*?)\\\"")]
|
||||||
private static partial Regex YkDVRegex();
|
private static partial Regex YkDVRegex();
|
||||||
[GeneratedRegex("#EXT-X-MAP:URI=\\\".*?BUMPER/[\\s\\S]+?#EXT-X-DISCONTINUITY")]
|
[GeneratedRegex("#EXT-X-MAP:URI=\\\".*?BUMPER/[\\s\\S]+?#EXT-X-DISCONTINUITY")]
|
||||||
private static partial Regex DNSPRegex();
|
private static partial Regex DNSPRegex();
|
||||||
[GeneratedRegex("#EXTINF:.*?,\\s+.*BUMPER.*\\s+?#EXT-X-DISCONTINUITY")]
|
[GeneratedRegex(@"#EXTINF:.*?,\s+.*BUMPER.*\s+?#EXT-X-DISCONTINUITY")]
|
||||||
private static partial Regex DNSPSubRegex();
|
private static partial Regex DNSPSubRegex();
|
||||||
[GeneratedRegex("(#EXTINF.*)(\\s+)(#EXT-X-KEY.*)")]
|
[GeneratedRegex("(#EXTINF.*)(\\s+)(#EXT-X-KEY.*)")]
|
||||||
private static partial Regex OrderFixRegex();
|
private static partial Regex OrderFixRegex();
|
||||||
[GeneratedRegex("#EXT-X-MAP.*\\.apple\\.com/")]
|
[GeneratedRegex(@"#EXT-X-MAP.*\.apple\.com/")]
|
||||||
private static partial Regex ATVRegex();
|
private static partial Regex ATVRegex();
|
||||||
[GeneratedRegex("(#EXT-X-KEY:[\\s\\S]*?)(#EXT-X-DISCONTINUITY|#EXT-X-ENDLIST)")]
|
[GeneratedRegex(@"(#EXT-X-KEY:[\s\S]*?)(#EXT-X-DISCONTINUITY|#EXT-X-ENDLIST)")]
|
||||||
private static partial Regex ATVRegex2();
|
private static partial Regex ATVRegex2();
|
||||||
|
|
||||||
public override bool CanProcess(ExtractorType extractorType, string rawText, ParserConfig parserConfig) => extractorType == ExtractorType.HLS;
|
public override bool CanProcess(ExtractorType extractorType, string rawText, ParserConfig parserConfig) => extractorType == ExtractorType.HLS;
|
||||||
@ -30,13 +25,13 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
|||||||
public override string Process(string m3u8Content, ParserConfig parserConfig)
|
public override string Process(string m3u8Content, ParserConfig parserConfig)
|
||||||
{
|
{
|
||||||
// 处理content以\r作为换行符的情况
|
// 处理content以\r作为换行符的情况
|
||||||
if (m3u8Content.Contains("\r") && !m3u8Content.Contains("\n"))
|
if (m3u8Content.Contains('\r') && !m3u8Content.Contains('\n'))
|
||||||
{
|
{
|
||||||
m3u8Content = m3u8Content.Replace("\r", Environment.NewLine);
|
m3u8Content = m3u8Content.Replace("\r", Environment.NewLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
var m3u8Url = parserConfig.Url;
|
var m3u8Url = parserConfig.Url;
|
||||||
//央视频回放
|
// YSP回放
|
||||||
if (m3u8Url.Contains("tlivecloud-playback-cdn.ysp.cctv.cn") && m3u8Url.Contains("endtime="))
|
if (m3u8Url.Contains("tlivecloud-playback-cdn.ysp.cctv.cn") && m3u8Url.Contains("endtime="))
|
||||||
{
|
{
|
||||||
m3u8Content += Environment.NewLine + HLSTags.ext_x_endlist;
|
m3u8Content += Environment.NewLine + HLSTags.ext_x_endlist;
|
||||||
@ -48,16 +43,10 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
|||||||
// M3u8Content = DecodeImooc.DecodeM3u8(M3u8Content);
|
// M3u8Content = DecodeImooc.DecodeM3u8(M3u8Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
//iqy
|
// 针对YK #EXT-X-VERSION:7杜比视界片源修正
|
||||||
if (m3u8Content.StartsWith("{\"payload\""))
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
//针对优酷#EXT-X-VERSION:7杜比视界片源修正
|
|
||||||
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && m3u8Content.Contains("ott.cibntv.net") && m3u8Content.Contains("ccode="))
|
if (m3u8Content.Contains("#EXT-X-DISCONTINUITY") && m3u8Content.Contains("#EXT-X-MAP") && m3u8Content.Contains("ott.cibntv.net") && m3u8Content.Contains("ccode="))
|
||||||
{
|
{
|
||||||
Regex ykmap = YkDVRegex();
|
var ykmap = YkDVRegex();
|
||||||
foreach (Match m in ykmap.Matches(m3u8Content))
|
foreach (Match m in ykmap.Matches(m3u8Content))
|
||||||
{
|
{
|
||||||
m3u8Content = m3u8Content.Replace(m.Value, $"#EXTINF:0.000000,\n#EXT-X-BYTERANGE:{m.Groups[2].Value}\n{m.Groups[1].Value}");
|
m3u8Content = m3u8Content.Replace(m.Value, $"#EXTINF:0.000000,\n#EXT-X-BYTERANGE:{m.Groups[2].Value}\n{m.Groups[1].Value}");
|
||||||
@ -105,4 +94,3 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
|||||||
return m3u8Content;
|
return m3u8Content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -6,14 +6,9 @@ using N_m3u8DL_RE.Common.Util;
|
|||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Parser.Util;
|
using N_m3u8DL_RE.Parser.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor.HLS
|
namespace N_m3u8DL_RE.Parser.Processor.HLS;
|
||||||
{
|
|
||||||
public class DefaultHLSKeyProcessor : KeyProcessor
|
public class DefaultHLSKeyProcessor : KeyProcessor
|
||||||
{
|
{
|
||||||
public override bool CanProcess(ExtractorType extractorType, string m3u8Url, string keyLine, string m3u8Content, ParserConfig paserConfig) => extractorType == ExtractorType.HLS;
|
public override bool CanProcess(ExtractorType extractorType, string m3u8Url, string keyLine, string m3u8Content, ParserConfig paserConfig) => extractorType == ExtractorType.HLS;
|
||||||
@ -35,7 +30,7 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
|||||||
encryptInfo.IV = HexUtil.HexToBytes(iv);
|
encryptInfo.IV = HexUtil.HexToBytes(iv);
|
||||||
}
|
}
|
||||||
// 自定义IV
|
// 自定义IV
|
||||||
if (parserConfig.CustomeIV != null && parserConfig.CustomeIV.Length > 0)
|
if (parserConfig.CustomeIV is { Length: > 0 })
|
||||||
{
|
{
|
||||||
encryptInfo.IV = parserConfig.CustomeIV;
|
encryptInfo.IV = parserConfig.CustomeIV;
|
||||||
}
|
}
|
||||||
@ -43,7 +38,7 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
|||||||
// KEY
|
// KEY
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (parserConfig.CustomeKey != null && parserConfig.CustomeKey.Length > 0)
|
if (parserConfig.CustomeKey is { Length: > 0 })
|
||||||
{
|
{
|
||||||
encryptInfo.Key = parserConfig.CustomeKey;
|
encryptInfo.Key = parserConfig.CustomeKey;
|
||||||
}
|
}
|
||||||
@ -78,7 +73,7 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
|||||||
Logger.WarnMarkUp($"[grey]{_ex.Message.EscapeMarkup()} retryCount: {retryCount}[/]");
|
Logger.WarnMarkUp($"[grey]{_ex.Message.EscapeMarkup()} retryCount: {retryCount}[/]");
|
||||||
Thread.Sleep(1000);
|
Thread.Sleep(1000);
|
||||||
if (retryCount-- > 0) goto getHttpKey;
|
if (retryCount-- > 0) goto getHttpKey;
|
||||||
else throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,12 +83,11 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
|||||||
encryptInfo.Method = EncryptMethod.UNKNOWN;
|
encryptInfo.Method = EncryptMethod.UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parserConfig.CustomMethod == null) return encryptInfo;
|
||||||
|
|
||||||
// 处理自定义加密方式
|
// 处理自定义加密方式
|
||||||
if (parserConfig.CustomMethod != null)
|
|
||||||
{
|
|
||||||
encryptInfo.Method = parserConfig.CustomMethod.Value;
|
encryptInfo.Method = parserConfig.CustomMethod.Value;
|
||||||
Logger.Warn("METHOD changed from {} to {}", method, encryptInfo.Method);
|
Logger.Warn("METHOD changed from {} to {}", method, encryptInfo.Method);
|
||||||
}
|
|
||||||
|
|
||||||
return encryptInfo;
|
return encryptInfo;
|
||||||
}
|
}
|
||||||
@ -114,4 +108,3 @@ namespace N_m3u8DL_RE.Parser.Processor.HLS
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,17 +1,11 @@
|
|||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor
|
namespace N_m3u8DL_RE.Parser.Processor;
|
||||||
{
|
|
||||||
public abstract class KeyProcessor
|
public abstract class KeyProcessor
|
||||||
{
|
{
|
||||||
public abstract bool CanProcess(ExtractorType extractorType, string keyLine, string m3u8Url, string m3u8Content, ParserConfig parserConfig);
|
public abstract bool CanProcess(ExtractorType extractorType, string keyLine, string m3u8Url, string m3u8Content, ParserConfig parserConfig);
|
||||||
public abstract EncryptInfo Process(string keyLine, string m3u8Url, string m3u8Content, ParserConfig parserConfig);
|
public abstract EncryptInfo Process(string keyLine, string m3u8Url, string m3u8Content, ParserConfig parserConfig);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Processor
|
namespace N_m3u8DL_RE.Parser.Processor;
|
||||||
{
|
|
||||||
public abstract class UrlProcessor
|
public abstract class UrlProcessor
|
||||||
{
|
{
|
||||||
public abstract bool CanProcess(ExtractorType extractorType, string oriUrl, ParserConfig parserConfig);
|
public abstract bool CanProcess(ExtractorType extractorType, string oriUrl, ParserConfig parserConfig);
|
||||||
public abstract string Process(string oriUrl, ParserConfig parserConfig);
|
public abstract string Process(string oriUrl, ParserConfig parserConfig);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using N_m3u8DL_RE.Parser.Config;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
@ -6,25 +7,19 @@ using N_m3u8DL_RE.Parser.Constants;
|
|||||||
using N_m3u8DL_RE.Parser.Extractor;
|
using N_m3u8DL_RE.Parser.Extractor;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using Spectre.Console;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser
|
namespace N_m3u8DL_RE.Parser;
|
||||||
{
|
|
||||||
public class StreamExtractor
|
public class StreamExtractor
|
||||||
{
|
{
|
||||||
public ExtractorType ExtractorType { get => extractor.ExtractorType; }
|
public ExtractorType ExtractorType => extractor.ExtractorType;
|
||||||
private IExtractor extractor;
|
private IExtractor extractor;
|
||||||
private ParserConfig parserConfig = new ParserConfig();
|
private ParserConfig parserConfig = new();
|
||||||
private string rawText;
|
private string rawText;
|
||||||
private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
|
private static SemaphoreSlim semaphore = new(1, 1);
|
||||||
|
|
||||||
public Dictionary<string, string> RawFiles { get; set; } = new(); // 存储(文件名,文件内容)
|
public Dictionary<string, string> RawFiles { get; set; } = new(); // 存储(文件名,文件内容)
|
||||||
|
|
||||||
public StreamExtractor()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public StreamExtractor(ParserConfig parserConfig)
|
public StreamExtractor(ParserConfig parserConfig)
|
||||||
{
|
{
|
||||||
this.parserConfig = parserConfig;
|
this.parserConfig = parserConfig;
|
||||||
@ -55,7 +50,8 @@ namespace N_m3u8DL_RE.Parser
|
|||||||
LoadSourceFromText(this.rawText);
|
LoadSourceFromText(this.rawText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadSourceFromText(string rawText)
|
[MemberNotNull(nameof(this.rawText), nameof(this.extractor))]
|
||||||
|
private void LoadSourceFromText(string rawText)
|
||||||
{
|
{
|
||||||
var rawType = "txt";
|
var rawType = "txt";
|
||||||
rawText = rawText.Trim();
|
rawText = rawText.Trim();
|
||||||
@ -146,4 +142,3 @@ namespace N_m3u8DL_RE.Parser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
using N_m3u8DL_RE.Parser.Constants;
|
using N_m3u8DL_RE.Parser.Constants;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Parser.Util
|
namespace N_m3u8DL_RE.Parser.Util;
|
||||||
|
|
||||||
|
public static partial class ParserUtil
|
||||||
{
|
{
|
||||||
public partial class ParserUtil
|
[GeneratedRegex(@"\$Number%([^$]+)d\$")]
|
||||||
{
|
|
||||||
[GeneratedRegex("\\$Number%([^$]+)d\\$")]
|
|
||||||
private static partial Regex VarsNumberRegex();
|
private static partial Regex VarsNumberRegex();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -28,18 +23,17 @@ namespace N_m3u8DL_RE.Parser.Util
|
|||||||
|
|
||||||
var index = -1;
|
var index = -1;
|
||||||
var result = string.Empty;
|
var result = string.Empty;
|
||||||
if ((index = line.IndexOf(key + "=\"")) > -1)
|
if ((index = line.IndexOf(key + "=\"", StringComparison.Ordinal)) > -1)
|
||||||
{
|
{
|
||||||
var startIndex = index + (key + "=\"").Length;
|
var startIndex = index + (key + "=\"").Length;
|
||||||
var endIndex = startIndex + line[startIndex..].IndexOf('\"');
|
var endIndex = startIndex + line[startIndex..].IndexOf('\"');
|
||||||
result = line[startIndex..endIndex];
|
result = line[startIndex..endIndex];
|
||||||
}
|
}
|
||||||
else if ((index = line.IndexOf(key + "=")) > -1)
|
else if ((index = line.IndexOf(key + "=", StringComparison.Ordinal)) > -1)
|
||||||
{
|
{
|
||||||
var startIndex = index + (key + "=").Length;
|
var startIndex = index + (key + "=").Length;
|
||||||
var endIndex = startIndex + line[startIndex..].IndexOf(',');
|
var endIndex = startIndex + line[startIndex..].IndexOf(',');
|
||||||
if (endIndex >= startIndex) result = line[startIndex..endIndex];
|
result = endIndex >= startIndex ? line[startIndex..endIndex] : line[startIndex..];
|
||||||
else result = line[startIndex..];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -54,18 +48,13 @@ namespace N_m3u8DL_RE.Parser.Util
|
|||||||
public static (long, long?) GetRange(string input)
|
public static (long, long?) GetRange(string input)
|
||||||
{
|
{
|
||||||
var t = input.Split('@');
|
var t = input.Split('@');
|
||||||
if (t.Length > 0)
|
return t.Length switch
|
||||||
{
|
{
|
||||||
if (t.Length == 1)
|
<= 0 => (0, null),
|
||||||
{
|
1 => (Convert.ToInt64(t[0]), null),
|
||||||
return (Convert.ToInt64(t[0]), null);
|
2 => (Convert.ToInt64(t[0]), Convert.ToInt64(t[1])),
|
||||||
}
|
_ => (0, null)
|
||||||
if (t.Length == 2)
|
};
|
||||||
{
|
|
||||||
return (Convert.ToInt64(t[0]), Convert.ToInt64(t[1]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (0, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -94,11 +83,11 @@ namespace N_m3u8DL_RE.Parser.Util
|
|||||||
|
|
||||||
// 处理特殊形式数字 如 $Number%05d$
|
// 处理特殊形式数字 如 $Number%05d$
|
||||||
var regex = VarsNumberRegex();
|
var regex = VarsNumberRegex();
|
||||||
if (regex.IsMatch(text) && keyValuePairs.ContainsKey(DASHTags.TemplateNumber))
|
if (regex.IsMatch(text) && keyValuePairs.TryGetValue(DASHTags.TemplateNumber, out var keyValuePair))
|
||||||
{
|
{
|
||||||
foreach (Match m in regex.Matches(text))
|
foreach (Match m in regex.Matches(text))
|
||||||
{
|
{
|
||||||
text = text.Replace(m.Value, keyValuePairs[DASHTags.TemplateNumber]?.ToString()?.PadLeft(Convert.ToInt32(m.Groups[1].Value), '0'));
|
text = text.Replace(m.Value, keyValuePair?.ToString()?.PadLeft(Convert.ToInt32(m.Groups[1].Value), '0'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,11 +105,10 @@ namespace N_m3u8DL_RE.Parser.Util
|
|||||||
if (string.IsNullOrEmpty(baseurl))
|
if (string.IsNullOrEmpty(baseurl))
|
||||||
return url;
|
return url;
|
||||||
|
|
||||||
Uri uri1 = new Uri(baseurl); //这里直接传完整的URL即可
|
var uri1 = new Uri(baseurl); // 这里直接传完整的URL即可
|
||||||
Uri uri2 = new Uri(uri1, url);
|
var uri2 = new Uri(uri1, url);
|
||||||
url = uri2.ToString();
|
url = uri2.ToString();
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Entity;
|
||||||
using N_m3u8DL_RE.Entity;
|
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
namespace N_m3u8DL_RE.Column;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Column
|
|
||||||
{
|
|
||||||
internal sealed class DownloadSpeedColumn : ProgressColumn
|
internal sealed class DownloadSpeedColumn : ProgressColumn
|
||||||
{
|
{
|
||||||
private long _stopSpeed = 0;
|
private long _stopSpeed = 0;
|
||||||
@ -32,7 +27,7 @@ namespace N_m3u8DL_RE.Column
|
|||||||
var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
var flag = task.IsFinished || !task.IsStarted;
|
var flag = task.IsFinished || !task.IsStarted;
|
||||||
// 单文件下载汇报进度
|
// 单文件下载汇报进度
|
||||||
if (!flag && speedContainer.SingleSegment && speedContainer.ResponseLength != null)
|
if (!flag && speedContainer is { SingleSegment: true, ResponseLength: not null })
|
||||||
{
|
{
|
||||||
task.MaxValue = (double)speedContainer.ResponseLength;
|
task.MaxValue = (double)speedContainer.ResponseLength;
|
||||||
task.Value = speedContainer.RDownloaded;
|
task.Value = speedContainer.RDownloaded;
|
||||||
@ -48,19 +43,6 @@ namespace N_m3u8DL_RE.Column
|
|||||||
}
|
}
|
||||||
DateTimeStringDic[taskId] = now;
|
DateTimeStringDic[taskId] = now;
|
||||||
var style = flag ? Style.Plain : MyStyle;
|
var style = flag ? Style.Plain : MyStyle;
|
||||||
return flag ? new Text("-", style).Centered() : new Text(FormatFileSize(speedContainer.NowSpeed) + (speedContainer.LowSpeedCount > 0 ? $"({speedContainer.LowSpeedCount})" : ""), style).Centered();
|
return flag ? new Text("-", style).Centered() : new Text(GlobalUtil.FormatFileSize(speedContainer.NowSpeed) + "ps" + (speedContainer.LowSpeedCount > 0 ? $"({speedContainer.LowSpeedCount})" : ""), style).Centered();
|
||||||
}
|
|
||||||
|
|
||||||
private static string FormatFileSize(double fileSize)
|
|
||||||
{
|
|
||||||
return fileSize switch
|
|
||||||
{
|
|
||||||
< 0 => throw new ArgumentOutOfRangeException(nameof(fileSize)),
|
|
||||||
>= 1024 * 1024 * 1024 => string.Format("{0:########0.00}GBps", (double)fileSize / (1024 * 1024 * 1024)),
|
|
||||||
>= 1024 * 1024 => string.Format("{0:####0.00}MBps", (double)fileSize / (1024 * 1024)),
|
|
||||||
>= 1024 => string.Format("{0:####0.00}KBps", (double)fileSize / 1024),
|
|
||||||
_ => string.Format("{0:####0.00}Bps", fileSize)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,10 @@
|
|||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Column
|
namespace N_m3u8DL_RE.Column;
|
||||||
{
|
|
||||||
internal class DownloadStatusColumn : ProgressColumn
|
internal class DownloadStatusColumn : ProgressColumn
|
||||||
{
|
{
|
||||||
private ConcurrentDictionary<int, SpeedContainer> SpeedContainerDic { get; set; }
|
private ConcurrentDictionary<int, SpeedContainer> SpeedContainerDic { get; set; }
|
||||||
@ -46,4 +41,3 @@ namespace N_m3u8DL_RE.Column
|
|||||||
return new Text(sizeStr ?? "-", MyStyle).RightJustified();
|
return new Text(sizeStr ?? "-", MyStyle).RightJustified();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Column
|
namespace N_m3u8DL_RE.Column;
|
||||||
{
|
|
||||||
internal class MyPercentageColumn : ProgressColumn
|
internal class MyPercentageColumn : ProgressColumn
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -28,4 +23,3 @@ namespace N_m3u8DL_RE.Column
|
|||||||
return new Text($"{task.Value}/{task.MaxValue} {percentage:F2}%", style).RightJustified();
|
return new Text($"{task.Value}/{task.MaxValue} {percentage:F2}%", style).RightJustified();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,15 +1,10 @@
|
|||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Column
|
namespace N_m3u8DL_RE.Column;
|
||||||
{
|
|
||||||
internal class RecordingDurationColumn : ProgressColumn
|
internal class RecordingDurationColumn : ProgressColumn
|
||||||
{
|
{
|
||||||
protected override bool NoWrap => true;
|
protected override bool NoWrap => true;
|
||||||
@ -30,10 +25,6 @@ namespace N_m3u8DL_RE.Column
|
|||||||
{
|
{
|
||||||
if (_refreshedDurDic == null)
|
if (_refreshedDurDic == null)
|
||||||
return new Text($"{GlobalUtil.FormatTime(_recodingDurDic[task.Id])}", MyStyle).LeftJustified();
|
return new Text($"{GlobalUtil.FormatTime(_recodingDurDic[task.Id])}", MyStyle).LeftJustified();
|
||||||
else
|
|
||||||
{
|
|
||||||
return new Text($"{GlobalUtil.FormatTime(_recodingDurDic[task.Id])}/{GlobalUtil.FormatTime(_refreshedDurDic[task.Id])}", GreyStyle);
|
return new Text($"{GlobalUtil.FormatTime(_recodingDurDic[task.Id])}/{GlobalUtil.FormatTime(_refreshedDurDic[task.Id])}", GreyStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using N_m3u8DL_RE.Entity;
|
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Column
|
namespace N_m3u8DL_RE.Column;
|
||||||
{
|
|
||||||
internal class RecordingSizeColumn : ProgressColumn
|
internal class RecordingSizeColumn : ProgressColumn
|
||||||
{
|
{
|
||||||
protected override bool NoWrap => true;
|
protected override bool NoWrap => true;
|
||||||
@ -36,4 +30,3 @@ namespace N_m3u8DL_RE.Column
|
|||||||
return new Text(GlobalUtil.FormatFileSize(flag ? size : 0), MyStyle).LeftJustified();
|
return new Text(GlobalUtil.FormatFileSize(flag ? size : 0), MyStyle).LeftJustified();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Column
|
namespace N_m3u8DL_RE.Column;
|
||||||
{
|
|
||||||
internal class RecordingStatusColumn : ProgressColumn
|
internal class RecordingStatusColumn : ProgressColumn
|
||||||
{
|
{
|
||||||
protected override bool NoWrap => true;
|
protected override bool NoWrap => true;
|
||||||
@ -20,4 +15,3 @@ namespace N_m3u8DL_RE.Column
|
|||||||
return new Text($"{task.Value}/{task.MaxValue} Recording", MyStyle).LeftJustified();
|
return new Text($"{task.Value}/{task.MaxValue} Recording", MyStyle).LeftJustified();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -5,7 +5,6 @@ using N_m3u8DL_RE.Common.Util;
|
|||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using N_m3u8DL_RE.Enum;
|
using N_m3u8DL_RE.Enum;
|
||||||
using N_m3u8DL_RE.Util;
|
using N_m3u8DL_RE.Util;
|
||||||
using NiL.JS.Expressions;
|
|
||||||
using System.CommandLine;
|
using System.CommandLine;
|
||||||
using System.CommandLine.Binding;
|
using System.CommandLine.Binding;
|
||||||
using System.CommandLine.Builder;
|
using System.CommandLine.Builder;
|
||||||
@ -14,115 +13,120 @@ using System.Globalization;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.CommandLine
|
namespace N_m3u8DL_RE.CommandLine;
|
||||||
|
|
||||||
|
internal static partial class CommandInvoker
|
||||||
{
|
{
|
||||||
internal partial class CommandInvoker
|
public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20241216";
|
||||||
{
|
|
||||||
public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20240630";
|
|
||||||
|
|
||||||
[GeneratedRegex("((best|worst)\\d*|all)")]
|
[GeneratedRegex("((best|worst)\\d*|all)")]
|
||||||
private static partial Regex ForStrRegex();
|
private static partial Regex ForStrRegex();
|
||||||
[GeneratedRegex("(\\d*)-(\\d*)")]
|
[GeneratedRegex(@"(\d*)-(\d*)")]
|
||||||
private static partial Regex RangeRegex();
|
private static partial Regex RangeRegex();
|
||||||
|
[GeneratedRegex(@"([\d\\.]+)(M|K)")]
|
||||||
|
private static partial Regex SpeedStrRegex();
|
||||||
|
|
||||||
private readonly static Argument<string> Input = new(name: "input", description: ResString.cmd_Input);
|
private static readonly Argument<string> Input = new(name: "input", description: ResString.cmd_Input);
|
||||||
private readonly static Option<string?> TmpDir = new(new string[] { "--tmp-dir" }, description: ResString.cmd_tmpDir);
|
private static readonly Option<string?> TmpDir = new(["--tmp-dir"], description: ResString.cmd_tmpDir);
|
||||||
private readonly static Option<string?> SaveDir = new(new string[] { "--save-dir" }, description: ResString.cmd_saveDir);
|
private static readonly Option<string?> SaveDir = new(["--save-dir"], description: ResString.cmd_saveDir);
|
||||||
private readonly static Option<string?> SaveName = new(new string[] { "--save-name" }, description: ResString.cmd_saveName, parseArgument: ParseSaveName);
|
private static readonly Option<string?> SaveName = new(["--save-name"], description: ResString.cmd_saveName, parseArgument: ParseSaveName);
|
||||||
private readonly static Option<string?> SavePattern = new(new string[] { "--save-pattern" }, description: ResString.cmd_savePattern, getDefaultValue: () => "<SaveName>_<Id>_<Codecs>_<Language>_<Ext>");
|
private static readonly Option<string?> SavePattern = new(["--save-pattern"], description: ResString.cmd_savePattern, getDefaultValue: () => "<SaveName>_<Id>_<Codecs>_<Language>_<Ext>");
|
||||||
private readonly static Option<string?> UILanguage = new Option<string?>(new string[] { "--ui-language" }, description: ResString.cmd_uiLanguage).FromAmong("en-US", "zh-CN", "zh-TW");
|
private static readonly Option<string?> UILanguage = new Option<string?>(["--ui-language"], description: ResString.cmd_uiLanguage).FromAmong("en-US", "zh-CN", "zh-TW");
|
||||||
private readonly static Option<string?> UrlProcessorArgs = new(new string[] { "--urlprocessor-args" }, description: ResString.cmd_urlProcessorArgs);
|
private static readonly Option<string?> UrlProcessorArgs = new(["--urlprocessor-args"], description: ResString.cmd_urlProcessorArgs);
|
||||||
private readonly static Option<string[]?> Keys = new(new string[] { "--key" }, description: ResString.cmd_keys) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false };
|
private static readonly Option<string[]?> Keys = new(["--key"], description: ResString.cmd_keys) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false };
|
||||||
private readonly static Option<string> KeyTextFile = new(new string[] { "--key-text-file" }, description: ResString.cmd_keyText);
|
private static readonly Option<string> KeyTextFile = new(["--key-text-file"], description: ResString.cmd_keyText);
|
||||||
private readonly static Option<Dictionary<string, string>> Headers = new(new string[] { "-H", "--header" }, description: ResString.cmd_header, parseArgument: ParseHeaders) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false };
|
private static readonly Option<Dictionary<string, string>> Headers = new(["-H", "--header"], description: ResString.cmd_header, parseArgument: ParseHeaders) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false };
|
||||||
private readonly static Option<LogLevel> LogLevel = new(name: "--log-level", description: ResString.cmd_logLevel, getDefaultValue: () => Common.Log.LogLevel.INFO);
|
private static readonly Option<LogLevel> LogLevel = new(name: "--log-level", description: ResString.cmd_logLevel, getDefaultValue: () => Common.Log.LogLevel.INFO);
|
||||||
private readonly static Option<SubtitleFormat> SubtitleFormat = new(name: "--sub-format", description: ResString.cmd_subFormat, getDefaultValue: () => Enum.SubtitleFormat.SRT);
|
private static readonly Option<SubtitleFormat> SubtitleFormat = new(name: "--sub-format", description: ResString.cmd_subFormat, getDefaultValue: () => Enum.SubtitleFormat.SRT);
|
||||||
private readonly static Option<bool> AutoSelect = new(new string[] { "--auto-select" }, description: ResString.cmd_autoSelect, getDefaultValue: () => false);
|
private static readonly Option<bool> DisableUpdateCheck = new(["--disable-update-check"], description: ResString.cmd_disableUpdateCheck, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> SubOnly = new(new string[] { "--sub-only" }, description: ResString.cmd_subOnly, getDefaultValue: () => false);
|
private static readonly Option<bool> AutoSelect = new(["--auto-select"], description: ResString.cmd_autoSelect, getDefaultValue: () => false);
|
||||||
private readonly static Option<int> ThreadCount = new(new string[] { "--thread-count" }, description: ResString.cmd_threadCount, getDefaultValue: () => Environment.ProcessorCount) { ArgumentHelpName = "number" };
|
private static readonly Option<bool> SubOnly = new(["--sub-only"], description: ResString.cmd_subOnly, getDefaultValue: () => false);
|
||||||
private readonly static Option<int> DownloadRetryCount = new(new string[] { "--download-retry-count" }, description: ResString.cmd_downloadRetryCount, getDefaultValue: () => 3) { ArgumentHelpName = "number" };
|
private static readonly Option<int> ThreadCount = new(["--thread-count"], description: ResString.cmd_threadCount, getDefaultValue: () => Environment.ProcessorCount) { ArgumentHelpName = "number" };
|
||||||
private readonly static Option<bool> SkipMerge = new(new string[] { "--skip-merge" }, description: ResString.cmd_skipMerge, getDefaultValue: () => false);
|
private static readonly Option<int> DownloadRetryCount = new(["--download-retry-count"], description: ResString.cmd_downloadRetryCount, getDefaultValue: () => 3) { ArgumentHelpName = "number" };
|
||||||
private readonly static Option<bool> SkipDownload = new(new string[] { "--skip-download" }, description: ResString.cmd_skipDownload, getDefaultValue: () => false);
|
private static readonly Option<double> HttpRequestTimeout = new(["--http-request-timeout"], description: ResString.cmd_httpRequestTimeout, getDefaultValue: () => 100) { ArgumentHelpName = "seconds" };
|
||||||
private readonly static Option<bool> NoDateInfo = new(new string[] { "--no-date-info" }, description: ResString.cmd_noDateInfo, getDefaultValue: () => false);
|
private static readonly Option<bool> SkipMerge = new(["--skip-merge"], description: ResString.cmd_skipMerge, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> BinaryMerge = new(new string[] { "--binary-merge" }, description: ResString.cmd_binaryMerge, getDefaultValue: () => false);
|
private static readonly Option<bool> SkipDownload = new(["--skip-download"], description: ResString.cmd_skipDownload, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> UseFFmpegConcatDemuxer = new(new string[] { "--use-ffmpeg-concat-demuxer" }, description: ResString.cmd_useFFmpegConcatDemuxer, getDefaultValue: () => false);
|
private static readonly Option<bool> NoDateInfo = new(["--no-date-info"], description: ResString.cmd_noDateInfo, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> DelAfterDone = new(new string[] { "--del-after-done" }, description: ResString.cmd_delAfterDone, getDefaultValue: () => true);
|
private static readonly Option<bool> BinaryMerge = new(["--binary-merge"], description: ResString.cmd_binaryMerge, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> AutoSubtitleFix = new(new string[] { "--auto-subtitle-fix" }, description: ResString.cmd_subtitleFix, getDefaultValue: () => true);
|
private static readonly Option<bool> UseFFmpegConcatDemuxer = new(["--use-ffmpeg-concat-demuxer"], description: ResString.cmd_useFFmpegConcatDemuxer, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> CheckSegmentsCount = new(new string[] { "--check-segments-count" }, description: ResString.cmd_checkSegmentsCount, getDefaultValue: () => true);
|
private static readonly Option<bool> DelAfterDone = new(["--del-after-done"], description: ResString.cmd_delAfterDone, getDefaultValue: () => true);
|
||||||
private readonly static Option<bool> WriteMetaJson = new(new string[] { "--write-meta-json" }, description: ResString.cmd_writeMetaJson, getDefaultValue: () => true);
|
private static readonly Option<bool> AutoSubtitleFix = new(["--auto-subtitle-fix"], description: ResString.cmd_subtitleFix, getDefaultValue: () => true);
|
||||||
private readonly static Option<bool> AppendUrlParams = new(new string[] { "--append-url-params" }, description: ResString.cmd_appendUrlParams, getDefaultValue: () => false);
|
private static readonly Option<bool> CheckSegmentsCount = new(["--check-segments-count"], description: ResString.cmd_checkSegmentsCount, getDefaultValue: () => true);
|
||||||
private readonly static Option<bool> MP4RealTimeDecryption = new (new string[] { "--mp4-real-time-decryption" }, description: ResString.cmd_MP4RealTimeDecryption, getDefaultValue: () => false);
|
private static readonly Option<bool> WriteMetaJson = new(["--write-meta-json"], description: ResString.cmd_writeMetaJson, getDefaultValue: () => true);
|
||||||
private readonly static Option<bool> UseShakaPackager = new (new string[] { "--use-shaka-packager" }, description: ResString.cmd_useShakaPackager, getDefaultValue: () => false);
|
private static readonly Option<bool> AppendUrlParams = new(["--append-url-params"], description: ResString.cmd_appendUrlParams, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> ForceAnsiConsole = new(new string[] { "--force-ansi-console" }, description: ResString.cmd_forceAnsiConsole);
|
private static readonly Option<bool> MP4RealTimeDecryption = new (["--mp4-real-time-decryption"], description: ResString.cmd_MP4RealTimeDecryption, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> NoAnsiColor = new(new string[] { "--no-ansi-color" }, description: ResString.cmd_noAnsiColor);
|
private static readonly Option<bool> UseShakaPackager = new (["--use-shaka-packager"], description: ResString.cmd_useShakaPackager, getDefaultValue: () => false) { IsHidden = true };
|
||||||
private readonly static Option<string?> DecryptionBinaryPath = new(new string[] { "--decryption-binary-path" }, description: ResString.cmd_decryptionBinaryPath) { ArgumentHelpName = "PATH" };
|
private static readonly Option<DecryptEngine> DecryptionEngine = new (["--decryption-engine"], description: ResString.cmd_decryptionEngine, getDefaultValue: () => DecryptEngine.MP4DECRYPT);
|
||||||
private readonly static Option<string?> FFmpegBinaryPath = new(new string[] { "--ffmpeg-binary-path" }, description: ResString.cmd_ffmpegBinaryPath) { ArgumentHelpName = "PATH" };
|
private static readonly Option<bool> ForceAnsiConsole = new(["--force-ansi-console"], description: ResString.cmd_forceAnsiConsole);
|
||||||
private readonly static Option<string?> BaseUrl = new(new string[] { "--base-url" }, description: ResString.cmd_baseUrl);
|
private static readonly Option<bool> NoAnsiColor = new(["--no-ansi-color"], description: ResString.cmd_noAnsiColor);
|
||||||
private readonly static Option<bool> ConcurrentDownload = new(new string[] { "-mt", "--concurrent-download" }, description: ResString.cmd_concurrentDownload, getDefaultValue: () => false);
|
private static readonly Option<string?> DecryptionBinaryPath = new(["--decryption-binary-path"], description: ResString.cmd_decryptionBinaryPath) { ArgumentHelpName = "PATH" };
|
||||||
private readonly static Option<bool> NoLog = new(new string[] { "--no-log" }, description: ResString.cmd_noLog, getDefaultValue: () => false);
|
private static readonly Option<string?> FFmpegBinaryPath = new(["--ffmpeg-binary-path"], description: ResString.cmd_ffmpegBinaryPath) { ArgumentHelpName = "PATH" };
|
||||||
private readonly static Option<string[]?> AdKeywords = new(new string[] { "--ad-keyword" }, description: ResString.cmd_adKeyword) { ArgumentHelpName = "REG" };
|
private static readonly Option<string?> BaseUrl = new(["--base-url"], description: ResString.cmd_baseUrl);
|
||||||
private readonly static Option<long?> MaxSpeed = new(new string[] { "-R", "--max-speed" }, description: ResString.cmd_maxSpeed, parseArgument: ParseSpeedLimit) { ArgumentHelpName = "SPEED" };
|
private static readonly Option<bool> ConcurrentDownload = new(["-mt", "--concurrent-download"], description: ResString.cmd_concurrentDownload, getDefaultValue: () => false);
|
||||||
|
private static readonly Option<bool> NoLog = new(["--no-log"], description: ResString.cmd_noLog, getDefaultValue: () => false);
|
||||||
|
private static readonly Option<bool> AllowHlsMultiExtMap = new(["--allow-hls-multi-ext-map"], description: ResString.cmd_allowHlsMultiExtMap, getDefaultValue: () => false);
|
||||||
|
private static readonly Option<string[]?> AdKeywords = new(["--ad-keyword"], description: ResString.cmd_adKeyword) { ArgumentHelpName = "REG" };
|
||||||
|
private static readonly Option<long?> MaxSpeed = new(["-R", "--max-speed"], description: ResString.cmd_maxSpeed, parseArgument: ParseSpeedLimit) { ArgumentHelpName = "SPEED" };
|
||||||
|
|
||||||
|
|
||||||
// 代理选项
|
// 代理选项
|
||||||
private readonly static Option<bool> UseSystemProxy = new(new string[] { "--use-system-proxy" }, description: ResString.cmd_useSystemProxy, getDefaultValue: () => true);
|
private static readonly Option<bool> UseSystemProxy = new(["--use-system-proxy"], description: ResString.cmd_useSystemProxy, getDefaultValue: () => true);
|
||||||
private readonly static Option<WebProxy?> CustomProxy = new(new string[] { "--custom-proxy" }, description: ResString.cmd_customProxy, parseArgument: ParseProxy) { ArgumentHelpName = "URL" };
|
private static readonly Option<WebProxy?> CustomProxy = new(["--custom-proxy"], description: ResString.cmd_customProxy, parseArgument: ParseProxy) { ArgumentHelpName = "URL" };
|
||||||
|
|
||||||
// 只下载部分分片
|
// 只下载部分分片
|
||||||
private readonly static Option<CustomRange?> CustomRange = new(new string[] { "--custom-range" }, description: ResString.cmd_customRange, parseArgument: ParseCustomRange) { ArgumentHelpName = "RANGE" };
|
private static readonly Option<CustomRange?> CustomRange = new(["--custom-range"], description: ResString.cmd_customRange, parseArgument: ParseCustomRange) { ArgumentHelpName = "RANGE" };
|
||||||
|
|
||||||
|
|
||||||
// morehelp
|
// morehelp
|
||||||
private readonly static Option<string?> MoreHelp = new(new string[] { "--morehelp" }, description: ResString.cmd_moreHelp) { ArgumentHelpName = "OPTION" };
|
private static readonly Option<string?> MoreHelp = new(["--morehelp"], description: ResString.cmd_moreHelp) { ArgumentHelpName = "OPTION" };
|
||||||
|
|
||||||
// 自定义KEY等
|
// 自定义KEY等
|
||||||
private readonly static Option<EncryptMethod?> CustomHLSMethod = new(name: "--custom-hls-method", description: ResString.cmd_customHLSMethod) { ArgumentHelpName = "METHOD" };
|
private static readonly Option<EncryptMethod?> CustomHLSMethod = new(name: "--custom-hls-method", description: ResString.cmd_customHLSMethod) { ArgumentHelpName = "METHOD" };
|
||||||
private readonly static Option<byte[]?> CustomHLSKey = new(name: "--custom-hls-key", description: ResString.cmd_customHLSKey, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" };
|
private static readonly Option<byte[]?> CustomHLSKey = new(name: "--custom-hls-key", description: ResString.cmd_customHLSKey, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" };
|
||||||
private readonly static Option<byte[]?> CustomHLSIv = new(name: "--custom-hls-iv", description: ResString.cmd_customHLSIv, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" };
|
private static readonly Option<byte[]?> CustomHLSIv = new(name: "--custom-hls-iv", description: ResString.cmd_customHLSIv, parseArgument: ParseHLSCustomKey) { ArgumentHelpName = "FILE|HEX|BASE64" };
|
||||||
|
|
||||||
// 任务开始时间
|
// 任务开始时间
|
||||||
private readonly static Option<DateTime?> TaskStartAt = new(new string[] { "--task-start-at" }, description: ResString.cmd_taskStartAt, parseArgument: ParseStartTime) { ArgumentHelpName = "yyyyMMddHHmmss" };
|
private static readonly Option<DateTime?> TaskStartAt = new(["--task-start-at"], description: ResString.cmd_taskStartAt, parseArgument: ParseStartTime) { ArgumentHelpName = "yyyyMMddHHmmss" };
|
||||||
|
|
||||||
|
|
||||||
// 直播相关
|
// 直播相关
|
||||||
private readonly static Option<bool> LivePerformAsVod = new(new string[] { "--live-perform-as-vod" }, description: ResString.cmd_livePerformAsVod, getDefaultValue: () => false);
|
private static readonly Option<bool> LivePerformAsVod = new(["--live-perform-as-vod"], description: ResString.cmd_livePerformAsVod, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> LiveRealTimeMerge = new(new string[] { "--live-real-time-merge" }, description: ResString.cmd_liveRealTimeMerge, getDefaultValue: () => false);
|
private static readonly Option<bool> LiveRealTimeMerge = new(["--live-real-time-merge"], description: ResString.cmd_liveRealTimeMerge, getDefaultValue: () => false);
|
||||||
private readonly static Option<bool> LiveKeepSegments = new(new string[] { "--live-keep-segments" }, description: ResString.cmd_liveKeepSegments, getDefaultValue: () => true);
|
private static readonly Option<bool> LiveKeepSegments = new(["--live-keep-segments"], description: ResString.cmd_liveKeepSegments, getDefaultValue: () => true);
|
||||||
private readonly static Option<bool> LivePipeMux = new(new string[] { "--live-pipe-mux" }, description: ResString.cmd_livePipeMux, getDefaultValue: () => false);
|
private static readonly Option<bool> LivePipeMux = new(["--live-pipe-mux"], description: ResString.cmd_livePipeMux, getDefaultValue: () => false);
|
||||||
private readonly static Option<TimeSpan?> LiveRecordLimit = new(new string[] { "--live-record-limit" }, description: ResString.cmd_liveRecordLimit, parseArgument: ParseLiveLimit) { ArgumentHelpName = "HH:mm:ss" };
|
private static readonly Option<TimeSpan?> LiveRecordLimit = new(["--live-record-limit"], description: ResString.cmd_liveRecordLimit, parseArgument: ParseLiveLimit) { ArgumentHelpName = "HH:mm:ss" };
|
||||||
private readonly static Option<int?> LiveWaitTime = new(new string[] { "--live-wait-time" }, description: ResString.cmd_liveWaitTime) { ArgumentHelpName = "SEC" };
|
private static readonly Option<int?> LiveWaitTime = new(["--live-wait-time"], description: ResString.cmd_liveWaitTime) { ArgumentHelpName = "SEC" };
|
||||||
private readonly static Option<int> LiveTakeCount = new(new string[] { "--live-take-count" }, description: ResString.cmd_liveTakeCount, getDefaultValue: () => 16) { ArgumentHelpName = "NUM" };
|
private static readonly Option<int> LiveTakeCount = new(["--live-take-count"], description: ResString.cmd_liveTakeCount, getDefaultValue: () => 16) { ArgumentHelpName = "NUM" };
|
||||||
private readonly static Option<bool> LiveFixVttByAudio = new(new string[] { "--live-fix-vtt-by-audio" }, description: ResString.cmd_liveFixVttByAudio, getDefaultValue: () => false);
|
private static readonly Option<bool> LiveFixVttByAudio = new(["--live-fix-vtt-by-audio"], description: ResString.cmd_liveFixVttByAudio, getDefaultValue: () => false);
|
||||||
|
|
||||||
|
|
||||||
// 复杂命令行如下
|
// 复杂命令行如下
|
||||||
private readonly static Option<MuxOptions?> MuxAfterDone = new(new string[] { "-M", "--mux-after-done" }, description: ResString.cmd_muxAfterDone, parseArgument: ParseMuxAfterDone) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<MuxOptions?> MuxAfterDone = new(["-M", "--mux-after-done"], description: ResString.cmd_muxAfterDone, parseArgument: ParseMuxAfterDone) { ArgumentHelpName = "OPTIONS" };
|
||||||
private readonly static Option<List<OutputFile>> MuxImports = new("--mux-import", description: ResString.cmd_muxImport, parseArgument: ParseImports) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false, ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<List<OutputFile>> MuxImports = new("--mux-import", description: ResString.cmd_muxImport, parseArgument: ParseImports) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false, ArgumentHelpName = "OPTIONS" };
|
||||||
private readonly static Option<StreamFilter?> VideoFilter = new(new string[] { "-sv", "--select-video" }, description: ResString.cmd_selectVideo, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<StreamFilter?> VideoFilter = new(["-sv", "--select-video"], description: ResString.cmd_selectVideo, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||||
private readonly static Option<StreamFilter?> AudioFilter = new(new string[] { "-sa", "--select-audio" }, description: ResString.cmd_selectAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<StreamFilter?> AudioFilter = new(["-sa", "--select-audio"], description: ResString.cmd_selectAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||||
private readonly static Option<StreamFilter?> SubtitleFilter = new(new string[] { "-ss", "--select-subtitle" }, description: ResString.cmd_selectSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<StreamFilter?> SubtitleFilter = new(["-ss", "--select-subtitle"], description: ResString.cmd_selectSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||||
|
|
||||||
private readonly static Option<StreamFilter?> DropVideoFilter = new(new string[] { "-dv", "--drop-video" }, description: ResString.cmd_dropVideo, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<StreamFilter?> DropVideoFilter = new(["-dv", "--drop-video"], description: ResString.cmd_dropVideo, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||||
private readonly static Option<StreamFilter?> DropAudioFilter = new(new string[] { "-da", "--drop-audio" }, description: ResString.cmd_dropAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<StreamFilter?> DropAudioFilter = new(["-da", "--drop-audio"], description: ResString.cmd_dropAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||||
private readonly static Option<StreamFilter?> DropSubtitleFilter = new(new string[] { "-ds", "--drop-subtitle" }, description: ResString.cmd_dropSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
private static readonly Option<StreamFilter?> DropSubtitleFilter = new(["-ds", "--drop-subtitle"], description: ResString.cmd_dropSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 解析录制直播时长限制
|
/// 解析下载速度限制
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="result"></param>
|
/// <param name="result"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static long? ParseSpeedLimit(ArgumentResult result)
|
private static long? ParseSpeedLimit(ArgumentResult result)
|
||||||
{
|
{
|
||||||
var input = result.Tokens.First().Value.ToUpper();
|
var input = result.Tokens[0].Value.ToUpper();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var reg = new Regex("([\\d\\\\.]+)(M|K)");
|
var reg = SpeedStrRegex();
|
||||||
if (!reg.IsMatch(input)) throw new ArgumentException();
|
if (!reg.IsMatch(input)) throw new ArgumentException($"Invalid Speed Limit: {input}");
|
||||||
|
|
||||||
var number = double.Parse(reg.Match(input).Groups[1].Value);
|
var number = double.Parse(reg.Match(input).Groups[1].Value);
|
||||||
if (reg.Match(input).Groups[2].Value == "M")
|
if (reg.Match(input).Groups[2].Value == "M")
|
||||||
return (long)(number * 1024 * 1024);
|
return (long)(number * 1024 * 1024);
|
||||||
else
|
|
||||||
return (long)(number * 1024);
|
return (long)(number * 1024);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
@ -140,7 +144,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
/// <exception cref="ArgumentException"></exception>
|
/// <exception cref="ArgumentException"></exception>
|
||||||
private static CustomRange? ParseCustomRange(ArgumentResult result)
|
private static CustomRange? ParseCustomRange(ArgumentResult result)
|
||||||
{
|
{
|
||||||
var input = result.Tokens.First().Value;
|
var input = result.Tokens[0].Value;
|
||||||
// 支持的种类 0-100; 01:00:00-02:30:00; -300; 300-; 05:00-; -03:00;
|
// 支持的种类 0-100; 01:00:00-02:30:00; -300; 300-; 05:00-; -03:00;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -151,7 +155,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
if (arr.Length != 2)
|
if (arr.Length != 2)
|
||||||
throw new ArgumentException("Bad format!");
|
throw new ArgumentException("Bad format!");
|
||||||
|
|
||||||
if (input.Contains(":"))
|
if (input.Contains(':'))
|
||||||
{
|
{
|
||||||
return new CustomRange()
|
return new CustomRange()
|
||||||
{
|
{
|
||||||
@ -160,7 +164,8 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
EndSec = arr[1] == "" ? double.MaxValue : OtherUtil.ParseDur(arr[1]).TotalSeconds,
|
EndSec = arr[1] == "" ? double.MaxValue : OtherUtil.ParseDur(arr[1]).TotalSeconds,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (RangeRegex().IsMatch(input))
|
|
||||||
|
if (RangeRegex().IsMatch(input))
|
||||||
{
|
{
|
||||||
var left = RangeRegex().Match(input).Groups[1].Value;
|
var left = RangeRegex().Match(input).Groups[1].Value;
|
||||||
var right = RangeRegex().Match(input).Groups[2].Value;
|
var right = RangeRegex().Match(input).Groups[2].Value;
|
||||||
@ -189,7 +194,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
/// <exception cref="ArgumentException"></exception>
|
/// <exception cref="ArgumentException"></exception>
|
||||||
private static WebProxy? ParseProxy(ArgumentResult result)
|
private static WebProxy? ParseProxy(ArgumentResult result)
|
||||||
{
|
{
|
||||||
var input = result.Tokens.First().Value;
|
var input = result.Tokens[0].Value;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(input))
|
if (string.IsNullOrEmpty(input))
|
||||||
@ -218,16 +223,15 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static byte[]? ParseHLSCustomKey(ArgumentResult result)
|
private static byte[]? ParseHLSCustomKey(ArgumentResult result)
|
||||||
{
|
{
|
||||||
var input = result.Tokens.First().Value;
|
var input = result.Tokens[0].Value;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(input))
|
if (string.IsNullOrEmpty(input))
|
||||||
return null;
|
return null;
|
||||||
if (File.Exists(input))
|
if (File.Exists(input))
|
||||||
return File.ReadAllBytes(input);
|
return File.ReadAllBytes(input);
|
||||||
else if (HexUtil.TryParseHexString(input, out byte[]? bytes))
|
if (HexUtil.TryParseHexString(input, out byte[]? bytes))
|
||||||
return bytes;
|
return bytes;
|
||||||
else
|
|
||||||
return Convert.FromBase64String(input);
|
return Convert.FromBase64String(input);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
@ -244,7 +248,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static TimeSpan? ParseLiveLimit(ArgumentResult result)
|
private static TimeSpan? ParseLiveLimit(ArgumentResult result)
|
||||||
{
|
{
|
||||||
var input = result.Tokens.First().Value;
|
var input = result.Tokens[0].Value;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return OtherUtil.ParseDur(input);
|
return OtherUtil.ParseDur(input);
|
||||||
@ -263,7 +267,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static DateTime? ParseStartTime(ArgumentResult result)
|
private static DateTime? ParseStartTime(ArgumentResult result)
|
||||||
{
|
{
|
||||||
var input = result.Tokens.First().Value;
|
var input = result.Tokens[0].Value;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CultureInfo provider = CultureInfo.InvariantCulture;
|
CultureInfo provider = CultureInfo.InvariantCulture;
|
||||||
@ -278,7 +282,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
|
|
||||||
private static string? ParseSaveName(ArgumentResult result)
|
private static string? ParseSaveName(ArgumentResult result)
|
||||||
{
|
{
|
||||||
var input = result.Tokens.First().Value;
|
var input = result.Tokens[0].Value;
|
||||||
var newName = OtherUtil.GetValidFileName(input);
|
var newName = OtherUtil.GetValidFileName(input);
|
||||||
if (string.IsNullOrEmpty(newName))
|
if (string.IsNullOrEmpty(newName))
|
||||||
{
|
{
|
||||||
@ -296,7 +300,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
private static StreamFilter? ParseStreamFilter(ArgumentResult result)
|
private static StreamFilter? ParseStreamFilter(ArgumentResult result)
|
||||||
{
|
{
|
||||||
var streamFilter = new StreamFilter();
|
var streamFilter = new StreamFilter();
|
||||||
var input = result.Tokens.First().Value;
|
var input = result.Tokens[0].Value;
|
||||||
var p = new ComplexParamParser(input);
|
var p = new ComplexParamParser(input);
|
||||||
|
|
||||||
|
|
||||||
@ -434,11 +438,12 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private static MuxOptions? ParseMuxAfterDone(ArgumentResult result)
|
private static MuxOptions? ParseMuxAfterDone(ArgumentResult result)
|
||||||
{
|
{
|
||||||
var v = result.Tokens.First().Value;
|
var v = result.Tokens[0].Value;
|
||||||
var p = new ComplexParamParser(v);
|
var p = new ComplexParamParser(v);
|
||||||
// 混流格式
|
// 混流格式
|
||||||
var format = p.GetValue("format") ?? v.Split(':')[0]; // 若未获取到,直接:前的字符串作为format解析
|
var format = p.GetValue("format") ?? v.Split(':')[0]; // 若未获取到,直接:前的字符串作为format解析
|
||||||
if (format != "mp4" && format != "mkv")
|
var parseResult = System.Enum.TryParse(format.ToUpperInvariant(), out MuxFormat muxFormat);
|
||||||
|
if (!parseResult)
|
||||||
{
|
{
|
||||||
result.ErrorMessage = $"format={format} not valid";
|
result.ErrorMessage = $"format={format} not valid";
|
||||||
return null;
|
return null;
|
||||||
@ -480,7 +485,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
return new MuxOptions()
|
return new MuxOptions()
|
||||||
{
|
{
|
||||||
UseMkvmerge = muxer == "mkvmerge",
|
UseMkvmerge = muxer == "mkvmerge",
|
||||||
MuxToMp4 = format == "mp4",
|
MuxFormat = muxFormat,
|
||||||
KeepFiles = keep == "true",
|
KeepFiles = keep == "true",
|
||||||
SkipSubtitle = skipSub == "true",
|
SkipSubtitle = skipSub == "true",
|
||||||
BinPath = bin_path == "auto" ? null : bin_path
|
BinPath = bin_path == "auto" ? null : bin_path
|
||||||
@ -498,6 +503,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
NoAnsiColor = bindingContext.ParseResult.GetValueForOption(NoAnsiColor),
|
NoAnsiColor = bindingContext.ParseResult.GetValueForOption(NoAnsiColor),
|
||||||
LogLevel = bindingContext.ParseResult.GetValueForOption(LogLevel),
|
LogLevel = bindingContext.ParseResult.GetValueForOption(LogLevel),
|
||||||
AutoSelect = bindingContext.ParseResult.GetValueForOption(AutoSelect),
|
AutoSelect = bindingContext.ParseResult.GetValueForOption(AutoSelect),
|
||||||
|
DisableUpdateCheck = bindingContext.ParseResult.GetValueForOption(DisableUpdateCheck),
|
||||||
SkipMerge = bindingContext.ParseResult.GetValueForOption(SkipMerge),
|
SkipMerge = bindingContext.ParseResult.GetValueForOption(SkipMerge),
|
||||||
BinaryMerge = bindingContext.ParseResult.GetValueForOption(BinaryMerge),
|
BinaryMerge = bindingContext.ParseResult.GetValueForOption(BinaryMerge),
|
||||||
UseFFmpegConcatDemuxer = bindingContext.ParseResult.GetValueForOption(UseFFmpegConcatDemuxer),
|
UseFFmpegConcatDemuxer = bindingContext.ParseResult.GetValueForOption(UseFFmpegConcatDemuxer),
|
||||||
@ -519,10 +525,12 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
UrlProcessorArgs = bindingContext.ParseResult.GetValueForOption(UrlProcessorArgs),
|
UrlProcessorArgs = bindingContext.ParseResult.GetValueForOption(UrlProcessorArgs),
|
||||||
MP4RealTimeDecryption = bindingContext.ParseResult.GetValueForOption(MP4RealTimeDecryption),
|
MP4RealTimeDecryption = bindingContext.ParseResult.GetValueForOption(MP4RealTimeDecryption),
|
||||||
UseShakaPackager = bindingContext.ParseResult.GetValueForOption(UseShakaPackager),
|
UseShakaPackager = bindingContext.ParseResult.GetValueForOption(UseShakaPackager),
|
||||||
|
DecryptionEngine = bindingContext.ParseResult.GetValueForOption(DecryptionEngine),
|
||||||
DecryptionBinaryPath = bindingContext.ParseResult.GetValueForOption(DecryptionBinaryPath),
|
DecryptionBinaryPath = bindingContext.ParseResult.GetValueForOption(DecryptionBinaryPath),
|
||||||
FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath),
|
FFmpegBinaryPath = bindingContext.ParseResult.GetValueForOption(FFmpegBinaryPath),
|
||||||
KeyTextFile = bindingContext.ParseResult.GetValueForOption(KeyTextFile),
|
KeyTextFile = bindingContext.ParseResult.GetValueForOption(KeyTextFile),
|
||||||
DownloadRetryCount = bindingContext.ParseResult.GetValueForOption(DownloadRetryCount),
|
DownloadRetryCount = bindingContext.ParseResult.GetValueForOption(DownloadRetryCount),
|
||||||
|
HttpRequestTimeout = bindingContext.ParseResult.GetValueForOption(HttpRequestTimeout),
|
||||||
BaseUrl = bindingContext.ParseResult.GetValueForOption(BaseUrl),
|
BaseUrl = bindingContext.ParseResult.GetValueForOption(BaseUrl),
|
||||||
MuxImports = bindingContext.ParseResult.GetValueForOption(MuxImports),
|
MuxImports = bindingContext.ParseResult.GetValueForOption(MuxImports),
|
||||||
ConcurrentDownload = bindingContext.ParseResult.GetValueForOption(ConcurrentDownload),
|
ConcurrentDownload = bindingContext.ParseResult.GetValueForOption(ConcurrentDownload),
|
||||||
@ -546,6 +554,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
LiveTakeCount = bindingContext.ParseResult.GetValueForOption(LiveTakeCount),
|
LiveTakeCount = bindingContext.ParseResult.GetValueForOption(LiveTakeCount),
|
||||||
NoDateInfo = bindingContext.ParseResult.GetValueForOption(NoDateInfo),
|
NoDateInfo = bindingContext.ParseResult.GetValueForOption(NoDateInfo),
|
||||||
NoLog = bindingContext.ParseResult.GetValueForOption(NoLog),
|
NoLog = bindingContext.ParseResult.GetValueForOption(NoLog),
|
||||||
|
AllowHlsMultiExtMap = bindingContext.ParseResult.GetValueForOption(AllowHlsMultiExtMap),
|
||||||
AdKeywords = bindingContext.ParseResult.GetValueForOption(AdKeywords),
|
AdKeywords = bindingContext.ParseResult.GetValueForOption(AdKeywords),
|
||||||
MaxSpeed = bindingContext.ParseResult.GetValueForOption(MaxSpeed),
|
MaxSpeed = bindingContext.ParseResult.GetValueForOption(MaxSpeed),
|
||||||
};
|
};
|
||||||
@ -569,14 +578,12 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
|
|
||||||
// 混流设置
|
// 混流设置
|
||||||
var muxAfterDoneValue = bindingContext.ParseResult.GetValueForOption(MuxAfterDone);
|
var muxAfterDoneValue = bindingContext.ParseResult.GetValueForOption(MuxAfterDone);
|
||||||
if (muxAfterDoneValue != null)
|
if (muxAfterDoneValue == null) return option;
|
||||||
{
|
|
||||||
option.MuxAfterDone = true;
|
option.MuxAfterDone = true;
|
||||||
option.MuxOptions = muxAfterDoneValue;
|
option.MuxOptions = muxAfterDoneValue;
|
||||||
if (muxAfterDoneValue.UseMkvmerge) option.MkvmergeBinaryPath = muxAfterDoneValue.BinPath;
|
if (muxAfterDoneValue.UseMkvmerge) option.MkvmergeBinaryPath = muxAfterDoneValue.BinPath;
|
||||||
else option.FFmpegBinaryPath ??= muxAfterDoneValue.BinPath;
|
else option.FFmpegBinaryPath ??= muxAfterDoneValue.BinPath;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
@ -606,19 +613,19 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
|
|
||||||
var rootCommand = new RootCommand(VERSION_INFO)
|
var rootCommand = new RootCommand(VERSION_INFO)
|
||||||
{
|
{
|
||||||
Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, ForceAnsiConsole, NoAnsiColor,AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
|
Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, HttpRequestTimeout, ForceAnsiConsole, NoAnsiColor,AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
|
||||||
BinaryMerge, UseFFmpegConcatDemuxer, DelAfterDone, NoDateInfo, NoLog, WriteMetaJson, AppendUrlParams, ConcurrentDownload, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
|
BinaryMerge, UseFFmpegConcatDemuxer, DelAfterDone, NoDateInfo, NoLog, WriteMetaJson, AppendUrlParams, ConcurrentDownload, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
|
||||||
FFmpegBinaryPath,
|
FFmpegBinaryPath,
|
||||||
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
|
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionEngine, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
|
||||||
MaxSpeed,
|
MaxSpeed,
|
||||||
MuxAfterDone,
|
MuxAfterDone,
|
||||||
CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, CustomRange, TaskStartAt,
|
CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, CustomRange, TaskStartAt,
|
||||||
LivePerformAsVod, LiveRealTimeMerge, LiveKeepSegments, LivePipeMux, LiveFixVttByAudio, LiveRecordLimit, LiveWaitTime, LiveTakeCount,
|
LivePerformAsVod, LiveRealTimeMerge, LiveKeepSegments, LivePipeMux, LiveFixVttByAudio, LiveRecordLimit, LiveWaitTime, LiveTakeCount,
|
||||||
MuxImports, VideoFilter, AudioFilter, SubtitleFilter, DropVideoFilter, DropAudioFilter, DropSubtitleFilter, AdKeywords, MoreHelp
|
MuxImports, VideoFilter, AudioFilter, SubtitleFilter, DropVideoFilter, DropAudioFilter, DropSubtitleFilter, AdKeywords, DisableUpdateCheck, AllowHlsMultiExtMap, MoreHelp
|
||||||
};
|
};
|
||||||
|
|
||||||
rootCommand.TreatUnmatchedTokensAsErrors = true;
|
rootCommand.TreatUnmatchedTokensAsErrors = true;
|
||||||
rootCommand.SetHandler(async (myOption) => await action(myOption), new MyOptionBinder());
|
rootCommand.SetHandler(async myOption => await action(myOption), new MyOptionBinder());
|
||||||
|
|
||||||
var parser = new CommandLineBuilder(rootCommand)
|
var parser = new CommandLineBuilder(rootCommand)
|
||||||
.UseDefaults()
|
.UseDefaults()
|
||||||
@ -639,4 +646,3 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
return await parser.InvokeAsync(args);
|
return await parser.InvokeAsync(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
using System;
|
using System.Text;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
namespace N_m3u8DL_RE.CommandLine;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.CommandLine
|
|
||||||
{
|
|
||||||
internal class ComplexParamParser
|
internal class ComplexParamParser
|
||||||
{
|
{
|
||||||
private string _arg;
|
private readonly string _arg;
|
||||||
public ComplexParamParser(string arg)
|
public ComplexParamParser(string arg)
|
||||||
{
|
{
|
||||||
_arg = arg;
|
_arg = arg;
|
||||||
@ -20,7 +16,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var index = _arg.IndexOf(key + "=");
|
var index = _arg.IndexOf(key + "=", StringComparison.Ordinal);
|
||||||
if (index == -1) return (_arg.Contains(key) && _arg.EndsWith(key)) ? "true" : null;
|
if (index == -1) return (_arg.Contains(key) && _arg.EndsWith(key)) ? "true" : null;
|
||||||
|
|
||||||
var chars = _arg[(index + key.Length + 1)..].ToCharArray();
|
var chars = _arg[(index + key.Length + 1)..].ToCharArray();
|
||||||
@ -58,4 +54,3 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -4,8 +4,8 @@ using N_m3u8DL_RE.Entity;
|
|||||||
using N_m3u8DL_RE.Enum;
|
using N_m3u8DL_RE.Enum;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.CommandLine
|
namespace N_m3u8DL_RE.CommandLine;
|
||||||
{
|
|
||||||
internal class MyOption
|
internal class MyOption
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -53,10 +53,18 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool NoLog { get; set; }
|
public bool NoLog { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// See: <see cref="CommandInvoker.AllowHlsMultiExtMap"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool AllowHlsMultiExtMap { get; set; }
|
||||||
|
/// <summary>
|
||||||
/// See: <see cref="CommandInvoker.AutoSelect"/>.
|
/// See: <see cref="CommandInvoker.AutoSelect"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AutoSelect { get; set; }
|
public bool AutoSelect { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// See: <see cref="CommandInvoker.DisableUpdateCheck"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool DisableUpdateCheck { get; set; }
|
||||||
|
/// <summary>
|
||||||
/// See: <see cref="CommandInvoker.SubOnly"/>.
|
/// See: <see cref="CommandInvoker.SubOnly"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool SubOnly { get; set; }
|
public bool SubOnly { get; set; }
|
||||||
@ -69,6 +77,10 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int DownloadRetryCount { get; set; }
|
public int DownloadRetryCount { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// See: <see cref="CommandInvoker.HttpRequestTimeout"/>.
|
||||||
|
/// </summary>
|
||||||
|
public double HttpRequestTimeout { get; set; }
|
||||||
|
/// <summary>
|
||||||
/// See: <see cref="CommandInvoker.LiveRecordLimit"/>.
|
/// See: <see cref="CommandInvoker.LiveRecordLimit"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan? LiveRecordLimit { get; set; }
|
public TimeSpan? LiveRecordLimit { get; set; }
|
||||||
@ -127,8 +139,13 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// See: <see cref="CommandInvoker.UseShakaPackager"/>.
|
/// See: <see cref="CommandInvoker.UseShakaPackager"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Use DecryptionEngine instead")]
|
||||||
public bool UseShakaPackager { get; set; }
|
public bool UseShakaPackager { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// See: <see cref="CommandInvoker.DecryptionEngine"/>.
|
||||||
|
/// </summary>
|
||||||
|
public DecryptEngine DecryptionEngine { get; set; }
|
||||||
|
/// <summary>
|
||||||
/// See: <see cref="CommandInvoker.MuxAfterDone"/>.
|
/// See: <see cref="CommandInvoker.MuxAfterDone"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool MuxAfterDone { get; set; }
|
public bool MuxAfterDone { get; set; }
|
||||||
@ -244,7 +261,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
/// See: <see cref="CommandInvoker.LiveTakeCount"/>.
|
/// See: <see cref="CommandInvoker.LiveTakeCount"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int LiveTakeCount { get; set; }
|
public int LiveTakeCount { get; set; }
|
||||||
public MuxOptions MuxOptions { get; set; }
|
public MuxOptions? MuxOptions { get; set; }
|
||||||
// public bool LiveWriteHLS { get; set; } = true;
|
// public bool LiveWriteHLS { get; set; } = true;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// See: <see cref="CommandInvoker.LivePipeMux"/>.
|
/// See: <see cref="CommandInvoker.LivePipeMux"/>.
|
||||||
@ -255,4 +272,3 @@ namespace N_m3u8DL_RE.CommandLine
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool LiveFixVttByAudio { get; set; }
|
public bool LiveFixVttByAudio { get; set; }
|
||||||
}
|
}
|
||||||
}
|
|
@ -1,14 +1,7 @@
|
|||||||
using N_m3u8DL_RE.CommandLine;
|
using N_m3u8DL_RE.CommandLine;
|
||||||
using N_m3u8DL_RE.Enum;
|
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Config
|
namespace N_m3u8DL_RE.Config;
|
||||||
{
|
|
||||||
internal class DownloaderConfig
|
internal class DownloaderConfig
|
||||||
{
|
{
|
||||||
public required MyOption MyOptions { get; set; }
|
public required MyOption MyOptions { get; set; }
|
||||||
@ -30,4 +23,3 @@ namespace N_m3u8DL_RE.Config
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
|
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
using System;
|
using System.Security.Cryptography;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Crypto
|
namespace N_m3u8DL_RE.Crypto;
|
||||||
{
|
|
||||||
internal class AESUtil
|
internal static class AESUtil
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// AES-128解密,解密后原地替换文件
|
/// AES-128解密,解密后原地替换文件
|
||||||
@ -41,4 +36,3 @@ namespace N_m3u8DL_RE.Crypto
|
|||||||
return resultArray;
|
return resultArray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
using CSChaCha20;
|
using CSChaCha20;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Crypto
|
namespace N_m3u8DL_RE.Crypto;
|
||||||
{
|
|
||||||
internal class ChaCha20Util
|
internal static class ChaCha20Util
|
||||||
{
|
{
|
||||||
public static byte[] DecryptPer1024Bytes(byte[] encryptedBuff, byte[] keyBytes, byte[] nonceBytes)
|
public static byte[] DecryptPer1024Bytes(byte[] encryptedBuff, byte[] keyBytes, byte[] nonceBytes)
|
||||||
{
|
{
|
||||||
@ -40,4 +35,3 @@ namespace N_m3u8DL_RE.Crypto
|
|||||||
return decStream.ToArray();
|
return decStream.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -11,12 +11,12 @@
|
|||||||
<SatelliteResourceLanguages>zh-CN;zh-TW;en-US</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>zh-CN;zh-TW;en-US</SatelliteResourceLanguages>
|
||||||
<PublishAot>true</PublishAot>
|
<PublishAot>true</PublishAot>
|
||||||
<StripSymbols>true</StripSymbols>
|
<StripSymbols>true</StripSymbols>
|
||||||
<ObjCopyName Condition="'$(RuntimeIdentifier)' == 'linux-arm64'">aarch64-linux-gnu-objcopy</ObjCopyName>
|
<ObjCopyName Condition="'$(RuntimeIdentifier)' == 'linux-arm64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">aarch64-linux-gnu-objcopy</ObjCopyName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(PublishAot)' == 'true' and '$(RuntimeIdentifier)' != 'win-arm64' and '$(RuntimeIdentifier)' != 'linux-arm64' and '$(RuntimeIdentifier)' != 'osx-arm64' and '$(RuntimeIdentifier)' != 'osx-x64'">
|
<!--<ItemGroup Condition="'$(PublishAot)' == 'true' and '$(RuntimeIdentifier)' != 'win-arm64' and '$(RuntimeIdentifier)' != 'linux-arm64' and '$(RuntimeIdentifier)' != 'osx-arm64' and '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||||
<PackageReference Include="PublishAotCompressed" Version="1.0.0" />
|
<PackageReference Include="PublishAotCompressed" Version="1.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>-->
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<RdXmlFile Include="rd.xml" />
|
<RdXmlFile Include="rd.xml" />
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
using Mp4SubtitleParser;
|
using N_m3u8DL_RE.Column;
|
||||||
using N_m3u8DL_RE.Column;
|
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
@ -11,27 +9,18 @@ using N_m3u8DL_RE.Entity;
|
|||||||
using N_m3u8DL_RE.Parser;
|
using N_m3u8DL_RE.Parser;
|
||||||
using N_m3u8DL_RE.Util;
|
using N_m3u8DL_RE.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.IO;
|
|
||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Reflection.PortableExecutable;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Threading.Tasks.Dataflow;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.DownloadManager
|
namespace N_m3u8DL_RE.DownloadManager;
|
||||||
{
|
|
||||||
internal class HTTPLiveRecordManager
|
internal class HTTPLiveRecordManager
|
||||||
{
|
{
|
||||||
IDownloader Downloader;
|
IDownloader Downloader;
|
||||||
DownloaderConfig DownloaderConfig;
|
DownloaderConfig DownloaderConfig;
|
||||||
StreamExtractor StreamExtractor;
|
StreamExtractor StreamExtractor;
|
||||||
List<StreamSpec> SelectedSteams;
|
List<StreamSpec> SelectedSteams;
|
||||||
List<OutputFile> OutputFiles = new();
|
List<OutputFile> OutputFiles = [];
|
||||||
DateTime NowDateTime;
|
DateTime NowDateTime;
|
||||||
DateTime? PublishDateTime;
|
DateTime? PublishDateTime;
|
||||||
bool STOP_FLAG = false;
|
bool STOP_FLAG = false;
|
||||||
@ -84,9 +73,9 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var size = 0;
|
var size = 0;
|
||||||
|
|
||||||
// 计时器
|
// 计时器
|
||||||
TimeCounterAsync();
|
_ = TimeCounterAsync();
|
||||||
// 读取INFO
|
// 读取INFO
|
||||||
ReadInfoAsync();
|
_ = ReadInfoAsync();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -118,7 +107,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
if (InfoBuffer.Count < 188 * 5000) continue;
|
if (InfoBuffer.Count < 188 * 5000) continue;
|
||||||
|
|
||||||
UInt16 ConvertToUint16(IEnumerable<byte> bytes)
|
ushort ConvertToUint16(IEnumerable<byte> bytes)
|
||||||
{
|
{
|
||||||
if (BitConverter.IsLittleEndian)
|
if (BitConverter.IsLittleEndian)
|
||||||
bytes = bytes.Reverse();
|
bytes = bytes.Reverse();
|
||||||
@ -228,7 +217,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
return (item, task);
|
return (item, task);
|
||||||
}).ToDictionary(item => item.item, item => item.task);
|
}).ToDictionary(item => item.item, item => item.task);
|
||||||
|
|
||||||
DownloaderConfig.MyOptions.LiveRecordLimit = DownloaderConfig.MyOptions.LiveRecordLimit ?? TimeSpan.MaxValue;
|
DownloaderConfig.MyOptions.LiveRecordLimit ??= TimeSpan.MaxValue;
|
||||||
var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
|
var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
|
||||||
if (limit != TimeSpan.MaxValue)
|
if (limit != TimeSpan.MaxValue)
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimit}{GlobalUtil.FormatTime((int)limit.Value.TotalSeconds)}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimit}{GlobalUtil.FormatTime((int)limit.Value.TotalSeconds)}[/]");
|
||||||
@ -251,4 +240,3 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -13,16 +13,17 @@ using N_m3u8DL_RE.Util;
|
|||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using N_m3u8DL_RE.Enum;
|
||||||
|
|
||||||
|
namespace N_m3u8DL_RE.DownloadManager;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.DownloadManager
|
|
||||||
{
|
|
||||||
internal class SimpleDownloadManager
|
internal class SimpleDownloadManager
|
||||||
{
|
{
|
||||||
IDownloader Downloader;
|
IDownloader Downloader;
|
||||||
DownloaderConfig DownloaderConfig;
|
DownloaderConfig DownloaderConfig;
|
||||||
StreamExtractor StreamExtractor;
|
StreamExtractor StreamExtractor;
|
||||||
List<StreamSpec> SelectedSteams;
|
List<StreamSpec> SelectedSteams;
|
||||||
List<OutputFile> OutputFiles = new();
|
List<OutputFile> OutputFiles = [];
|
||||||
|
|
||||||
public SimpleDownloadManager(DownloaderConfig downloaderConfig, List<StreamSpec> selectedSteams, StreamExtractor streamExtractor)
|
public SimpleDownloadManager(DownloaderConfig downloaderConfig, List<StreamSpec> selectedSteams, StreamExtractor streamExtractor)
|
||||||
{
|
{
|
||||||
@ -39,21 +40,21 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
if (_key != null)
|
if (_key != null)
|
||||||
{
|
{
|
||||||
if (DownloaderConfig.MyOptions.Keys == null)
|
if (DownloaderConfig.MyOptions.Keys == null)
|
||||||
DownloaderConfig.MyOptions.Keys = new string[] { _key };
|
DownloaderConfig.MyOptions.Keys = [_key];
|
||||||
else
|
else
|
||||||
DownloaderConfig.MyOptions.Keys = DownloaderConfig.MyOptions.Keys.Concat(new string[] { _key }).ToArray();
|
DownloaderConfig.MyOptions.Keys = [..DownloaderConfig.MyOptions.Keys, _key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ChangeSpecInfo(StreamSpec streamSpec, List<Mediainfo> mediainfos, ref bool useAACFilter)
|
private void ChangeSpecInfo(StreamSpec streamSpec, List<Mediainfo> mediainfos, ref bool useAACFilter)
|
||||||
{
|
{
|
||||||
if (!DownloaderConfig.MyOptions.BinaryMerge && mediainfos.Any(m => m.DolbyVison == true))
|
if (!DownloaderConfig.MyOptions.BinaryMerge && mediainfos.Any(m => m.DolbyVison))
|
||||||
{
|
{
|
||||||
DownloaderConfig.MyOptions.BinaryMerge = true;
|
DownloaderConfig.MyOptions.BinaryMerge = true;
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge2}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge2}[/]");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DownloaderConfig.MyOptions.MuxAfterDone && mediainfos.Any(m => m.DolbyVison == true))
|
if (DownloaderConfig.MyOptions.MuxAfterDone && mediainfos.Any(m => m.DolbyVison))
|
||||||
{
|
{
|
||||||
DownloaderConfig.MyOptions.MuxAfterDone = false;
|
DownloaderConfig.MyOptions.MuxAfterDone = false;
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge5}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge5}[/]");
|
||||||
@ -71,7 +72,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
else if (mediainfos.All(m => m.Type == "Subtitle"))
|
else if (mediainfos.All(m => m.Type == "Subtitle"))
|
||||||
{
|
{
|
||||||
streamSpec.MediaType = MediaType.SUBTITLES;
|
streamSpec.MediaType = MediaType.SUBTITLES;
|
||||||
if (streamSpec.Extension == null || streamSpec.Extension == "ts")
|
if (streamSpec.Extension is null or "ts")
|
||||||
streamSpec.Extension = "vtt";
|
streamSpec.Extension = "vtt";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,7 +81,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
{
|
{
|
||||||
speedContainer.ResetVars();
|
speedContainer.ResetVars();
|
||||||
bool useAACFilter = false; // ffmpeg合并flag
|
bool useAACFilter = false; // ffmpeg合并flag
|
||||||
List<Mediainfo> mediaInfos = new();
|
List<Mediainfo> mediaInfos = [];
|
||||||
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
|
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
|
||||||
|
|
||||||
var segments = streamSpec.Playlist?.MediaParts.SelectMany(m => m.MediaSegments);
|
var segments = streamSpec.Playlist?.MediaParts.SelectMany(m => m.MediaSegments);
|
||||||
@ -109,8 +110,8 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
|
var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
|
||||||
var headers = DownloaderConfig.Headers;
|
var headers = DownloaderConfig.Headers;
|
||||||
|
|
||||||
//mp4decrypt
|
var decryptionBinaryPath = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
|
||||||
var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
|
var decryptEngine = DownloaderConfig.MyOptions.DecryptionEngine;
|
||||||
var mp4InitFile = "";
|
var mp4InitFile = "";
|
||||||
var currentKID = "";
|
var currentKID = "";
|
||||||
var readInfo = false; // 是否读取过
|
var readInfo = false; // 是否读取过
|
||||||
@ -157,7 +158,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var path = Path.Combine(tmpDir, "_init.mp4.tmp");
|
var path = Path.Combine(tmpDir, "_init.mp4.tmp");
|
||||||
var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, speedContainer, headers);
|
var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, speedContainer, headers);
|
||||||
FileDic[streamSpec.Playlist.MediaInit] = result;
|
FileDic[streamSpec.Playlist.MediaInit] = result;
|
||||||
if (result == null || !result.Success)
|
if (result is not { Success: true })
|
||||||
{
|
{
|
||||||
throw new Exception("Download init file failed!");
|
throw new Exception("Download init file failed!");
|
||||||
}
|
}
|
||||||
@ -165,22 +166,22 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
task.Increment(1);
|
task.Increment(1);
|
||||||
|
|
||||||
// 读取mp4信息
|
// 读取mp4信息
|
||||||
if (result != null && result.Success)
|
if (result is { Success: true })
|
||||||
{
|
{
|
||||||
mp4Info = MP4DecryptUtil.GetMP4Info(result.ActualFilePath);
|
mp4Info = MP4DecryptUtil.GetMP4Info(result.ActualFilePath);
|
||||||
currentKID = mp4Info.KID;
|
currentKID = mp4Info.KID;
|
||||||
// try shaka packager, which can handle WebM
|
// try shaka packager, which can handle WebM
|
||||||
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.DecryptionEngine == DecryptEngine.SHAKA_PACKAGER) {
|
||||||
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt);
|
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, decryptionBinaryPath);
|
||||||
}
|
}
|
||||||
// 从文件读取KEY
|
// 从文件读取KEY
|
||||||
await SearchKeyAsync(currentKID);
|
await SearchKeyAsync(currentKID);
|
||||||
// 实时解密
|
// 实时解密
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID) && StreamExtractor.ExtractorType != ExtractorType.MSS)
|
if ((streamSpec.Playlist.MediaInit.IsEncrypted || !string.IsNullOrEmpty(currentKID)) && DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID) && StreamExtractor.ExtractorType != ExtractorType.MSS)
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
||||||
@ -211,12 +212,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var path = Path.Combine(tmpDir, index.ToString(pad) + $".{streamSpec.Extension ?? "clip"}.tmp");
|
var path = Path.Combine(tmpDir, index.ToString(pad) + $".{streamSpec.Extension ?? "clip"}.tmp");
|
||||||
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
|
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
|
||||||
FileDic[seg] = result;
|
FileDic[seg] = result;
|
||||||
if (result == null || !result.Success)
|
if (result is not { Success: true })
|
||||||
{
|
{
|
||||||
throw new Exception("Download first segment failed!");
|
throw new Exception("Download first segment failed!");
|
||||||
}
|
}
|
||||||
task.Increment(1);
|
task.Increment(1);
|
||||||
if (result != null && result.Success)
|
if (result is { Success: true })
|
||||||
{
|
{
|
||||||
// 修复MSS init
|
// 修复MSS init
|
||||||
if (StreamExtractor.ExtractorType == ExtractorType.MSS)
|
if (StreamExtractor.ExtractorType == ExtractorType.MSS)
|
||||||
@ -224,12 +225,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var processor = new MSSMoovProcessor(streamSpec);
|
var processor = new MSSMoovProcessor(streamSpec);
|
||||||
var header = processor.GenHeader(File.ReadAllBytes(result.ActualFilePath));
|
var header = processor.GenHeader(File.ReadAllBytes(result.ActualFilePath));
|
||||||
await File.WriteAllBytesAsync(FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath, header);
|
await File.WriteAllBytesAsync(FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath, header);
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
if (seg.IsEncrypted && DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
// 需要重新解密init
|
// 需要重新解密init
|
||||||
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
|
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec;
|
FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec;
|
||||||
@ -242,18 +243,18 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID;
|
currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID;
|
||||||
}
|
}
|
||||||
// try shaka packager, which can handle WebM
|
// try shaka packager, which can handle WebM
|
||||||
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.DecryptionEngine == DecryptEngine.SHAKA_PACKAGER) {
|
||||||
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, mp4decrypt);
|
currentKID = MP4DecryptUtil.ReadInitShaka(result.ActualFilePath, decryptionBinaryPath);
|
||||||
}
|
}
|
||||||
// 从文件读取KEY
|
// 从文件读取KEY
|
||||||
await SearchKeyAsync(currentKID);
|
await SearchKeyAsync(currentKID);
|
||||||
// 实时解密
|
// 实时解密
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
if (seg.IsEncrypted && DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
mp4Info = MP4DecryptUtil.GetMP4Info(enc);
|
mp4Info = MP4DecryptUtil.GetMP4Info(enc);
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
File.Delete(enc);
|
File.Delete(enc);
|
||||||
@ -283,15 +284,15 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var path = Path.Combine(tmpDir, index.ToString(pad) + $".{streamSpec.Extension ?? "clip"}.tmp");
|
var path = Path.Combine(tmpDir, index.ToString(pad) + $".{streamSpec.Extension ?? "clip"}.tmp");
|
||||||
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
|
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
|
||||||
FileDic[seg] = result;
|
FileDic[seg] = result;
|
||||||
if (result != null && result.Success)
|
if (result is { Success: true })
|
||||||
task.Increment(1);
|
task.Increment(1);
|
||||||
// 实时解密
|
// 实时解密
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && result != null && result.Success && !string.IsNullOrEmpty(currentKID))
|
if (seg.IsEncrypted && DownloaderConfig.MyOptions.MP4RealTimeDecryption && result is { Success: true } && !string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
mp4Info = MP4DecryptUtil.GetMP4Info(enc);
|
mp4Info = MP4DecryptUtil.GetMP4Info(enc);
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile, isMultiDRM: mp4Info.isMultiDRM);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
File.Delete(enc);
|
File.Delete(enc);
|
||||||
@ -303,13 +304,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
// 修改输出后缀
|
// 修改输出后缀
|
||||||
var outputExt = "." + streamSpec.Extension;
|
var outputExt = "." + streamSpec.Extension;
|
||||||
if (streamSpec.Extension == null) outputExt = ".ts";
|
if (streamSpec.Extension == null) outputExt = ".ts";
|
||||||
else if (streamSpec.MediaType == MediaType.AUDIO && (streamSpec.Extension == "m4s" || streamSpec.Extension == "mp4")) outputExt = ".m4a";
|
else if (streamSpec is { MediaType: MediaType.AUDIO, Extension: "m4s" or "mp4" }) outputExt = ".m4a";
|
||||||
else if (streamSpec.MediaType != MediaType.SUBTITLES && (streamSpec.Extension == "m4s" || streamSpec.Extension == "mp4")) outputExt = ".mp4";
|
else if (streamSpec.MediaType != MediaType.SUBTITLES && streamSpec.Extension is "m4s" or "mp4") outputExt = ".mp4";
|
||||||
|
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == MediaType.SUBTITLES)
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == MediaType.SUBTITLES)
|
||||||
{
|
{
|
||||||
if (DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT) outputExt = ".srt";
|
outputExt = DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT ? ".srt" : ".vtt";
|
||||||
else outputExt = ".vtt";
|
|
||||||
}
|
}
|
||||||
var output = Path.Combine(saveDir, saveName + outputExt);
|
var output = Path.Combine(saveDir, saveName + outputExt);
|
||||||
|
|
||||||
@ -319,11 +319,11 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
Logger.WarnMarkUp($"{Path.GetFileName(output)} => {Path.GetFileName(output = Path.ChangeExtension(output, $"copy" + Path.GetExtension(output)))}");
|
Logger.WarnMarkUp($"{Path.GetFileName(output)} => {Path.GetFileName(output = Path.ChangeExtension(output, $"copy" + Path.GetExtension(output)))}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0 && mp4InitFile != "")
|
if (!string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, Keys.Length: > 0 } && mp4InitFile != "")
|
||||||
{
|
{
|
||||||
File.Delete(mp4InitFile);
|
File.Delete(mp4InitFile);
|
||||||
//shaka实时解密不需要init文件用于合并
|
// shaka/ffmpeg实时解密不需要init文件用于合并
|
||||||
if (DownloaderConfig.MyOptions.UseShakaPackager)
|
if (decryptEngine != DecryptEngine.MP4DECRYPT)
|
||||||
{
|
{
|
||||||
FileDic!.Remove(streamSpec.Playlist!.MediaInit, out _);
|
FileDic!.Remove(streamSpec.Playlist!.MediaInit, out _);
|
||||||
}
|
}
|
||||||
@ -332,7 +332,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
// 校验分片数量
|
// 校验分片数量
|
||||||
if (DownloaderConfig.MyOptions.CheckSegmentsCount && FileDic.Values.Any(s => s == null))
|
if (DownloaderConfig.MyOptions.CheckSegmentsCount && FileDic.Values.Any(s => s == null))
|
||||||
{
|
{
|
||||||
Logger.ErrorMarkUp(ResString.segmentCountCheckNotPass, totalCount, FileDic.Values.Where(s => s != null).Count());
|
Logger.ErrorMarkUp(ResString.segmentCountCheckNotPass, totalCount, FileDic.Values.Count(s => s != null));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,8 +350,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 自动修复VTT raw字幕
|
// 自动修复VTT raw字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("vtt"))
|
||||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("vtt"))
|
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp(ResString.fixingVTT);
|
Logger.WarnMarkUp(ResString.fixingVTT);
|
||||||
// 排序字幕并修正时间戳
|
// 排序字幕并修正时间戳
|
||||||
@ -397,7 +396,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||||
&& streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s"))
|
&& streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s"))
|
||||||
{
|
{
|
||||||
var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault();
|
var initFile = FileDic.Values.FirstOrDefault(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init"));
|
||||||
var iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath);
|
var iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath);
|
||||||
var (sawVtt, timescale) = MP4VttUtil.CheckInit(iniFileBytes);
|
var (sawVtt, timescale) = MP4VttUtil.CheckInit(iniFileBytes);
|
||||||
if (sawVtt)
|
if (sawVtt)
|
||||||
@ -431,8 +430,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 自动修复TTML raw字幕
|
// 自动修复TTML raw字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("ttml"))
|
||||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("ttml"))
|
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp(ResString.fixingTTML);
|
Logger.WarnMarkUp(ResString.fixingTTML);
|
||||||
var first = true;
|
var first = true;
|
||||||
@ -478,8 +476,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 自动修复TTML mp4字幕
|
// 自动修复TTML mp4字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("m4s")
|
||||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")
|
|
||||||
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp"))
|
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp"))
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp(ResString.fixingTTMLmp4);
|
Logger.WarnMarkUp(ResString.fixingTTMLmp4);
|
||||||
@ -574,7 +571,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 删除临时文件夹
|
// 删除临时文件夹
|
||||||
if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && mergeSuccess)
|
if (DownloaderConfig.MyOptions is { SkipMerge: false, DelAfterDone: true } && mergeSuccess)
|
||||||
{
|
{
|
||||||
var files = FileDic.Values.Select(v => v!.ActualFilePath);
|
var files = FileDic.Values.Select(v => v!.ActualFilePath);
|
||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
@ -589,21 +586,21 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
{
|
{
|
||||||
currentKID = MP4DecryptUtil.GetMP4Info(output).KID;
|
currentKID = MP4DecryptUtil.GetMP4Info(output).KID;
|
||||||
// try shaka packager, which can handle WebM
|
// try shaka packager, which can handle WebM
|
||||||
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.UseShakaPackager) {
|
if (string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions.DecryptionEngine == DecryptEngine.SHAKA_PACKAGER) {
|
||||||
currentKID = MP4DecryptUtil.ReadInitShaka(output, mp4decrypt);
|
currentKID = MP4DecryptUtil.ReadInitShaka(output, decryptionBinaryPath);
|
||||||
}
|
}
|
||||||
// 从文件读取KEY
|
// 从文件读取KEY
|
||||||
await SearchKeyAsync(currentKID);
|
await SearchKeyAsync(currentKID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用mp4decrypt解密
|
// 调用mp4decrypt解密
|
||||||
if (mergeSuccess && File.Exists(output) && !string.IsNullOrEmpty(currentKID) && !DownloaderConfig.MyOptions.MP4RealTimeDecryption && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0)
|
if (mergeSuccess && File.Exists(output) && !string.IsNullOrEmpty(currentKID) && DownloaderConfig.MyOptions is { MP4RealTimeDecryption: false, Keys.Length: > 0 })
|
||||||
{
|
{
|
||||||
var enc = output;
|
var enc = output;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
mp4Info = MP4DecryptUtil.GetMP4Info(enc);
|
mp4Info = MP4DecryptUtil.GetMP4Info(enc);
|
||||||
Logger.InfoMarkUp($"[grey]Decrypting...[/]");
|
Logger.InfoMarkUp($"[grey]Decrypting using {decryptEngine}...[/]");
|
||||||
var result = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
|
var result = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, isMultiDRM: mp4Info.isMultiDRM);
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
File.Delete(enc);
|
File.Delete(enc);
|
||||||
@ -653,8 +650,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
}
|
}
|
||||||
progress.Columns(progressColumns);
|
progress.Columns(progressColumns);
|
||||||
|
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !DownloaderConfig.MyOptions.UseShakaPackager
|
if (DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, DecryptionEngine: not DecryptEngine.SHAKA_PACKAGER, Keys.Length: > 0 })
|
||||||
&& DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0)
|
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
|
||||||
|
|
||||||
await progress.StartAsync(async ctx =>
|
await progress.StartAsync(async ctx =>
|
||||||
@ -700,7 +696,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var success = Results.Values.All(v => v == true);
|
var success = Results.Values.All(v => v == true);
|
||||||
|
|
||||||
// 删除临时文件夹
|
// 删除临时文件夹
|
||||||
if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && success)
|
if (DownloaderConfig.MyOptions is { SkipMerge: false, DelAfterDone: true } && success)
|
||||||
{
|
{
|
||||||
foreach (var item in StreamExtractor.RawFiles)
|
foreach (var item in StreamExtractor.RawFiles)
|
||||||
{
|
{
|
||||||
@ -715,7 +711,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
{
|
{
|
||||||
OutputFiles = OutputFiles.OrderBy(o => o.Index).ToList();
|
OutputFiles = OutputFiles.OrderBy(o => o.Index).ToList();
|
||||||
// 是否跳过字幕
|
// 是否跳过字幕
|
||||||
if (DownloaderConfig.MyOptions.MuxOptions.SkipSubtitle)
|
if (DownloaderConfig.MyOptions.MuxOptions!.SkipSubtitle)
|
||||||
{
|
{
|
||||||
OutputFiles = OutputFiles.Where(o => o.MediaType != MediaType.SUBTITLES).ToList();
|
OutputFiles = OutputFiles.Where(o => o.MediaType != MediaType.SUBTITLES).ToList();
|
||||||
}
|
}
|
||||||
@ -725,14 +721,14 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
}
|
}
|
||||||
OutputFiles.ForEach(f => Logger.WarnMarkUp($"[grey]{Path.GetFileName(f.FilePath).EscapeMarkup()}[/]"));
|
OutputFiles.ForEach(f => Logger.WarnMarkUp($"[grey]{Path.GetFileName(f.FilePath).EscapeMarkup()}[/]"));
|
||||||
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
||||||
var ext = DownloaderConfig.MyOptions.MuxOptions.MuxToMp4 ? ".mp4" : ".mkv";
|
var ext = OtherUtil.GetMuxExtension(DownloaderConfig.MyOptions.MuxOptions.MuxFormat);
|
||||||
var dirName = Path.GetFileName(DownloaderConfig.DirPrefix);
|
var dirName = Path.GetFileName(DownloaderConfig.DirPrefix);
|
||||||
var outName = $"{dirName}.MUX";
|
var outName = $"{dirName}.MUX";
|
||||||
var outPath = Path.Combine(saveDir, outName);
|
var outPath = Path.Combine(saveDir, outName);
|
||||||
Logger.WarnMarkUp($"Muxing to [grey]{outName.EscapeMarkup()}{ext}[/]");
|
Logger.WarnMarkUp($"Muxing to [grey]{outName.EscapeMarkup()}{ext}[/]");
|
||||||
var result = false;
|
var result = false;
|
||||||
if (DownloaderConfig.MyOptions.MuxOptions.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MyOptions.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
|
if (DownloaderConfig.MyOptions.MuxOptions.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MyOptions.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
|
||||||
else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MyOptions.MuxOptions.MuxToMp4, !DownloaderConfig.MyOptions.NoDateInfo);
|
else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MyOptions.MuxOptions.MuxFormat, !DownloaderConfig.MyOptions.NoDateInfo);
|
||||||
// 完成后删除各轨道文件
|
// 完成后删除各轨道文件
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
@ -761,4 +757,3 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -12,19 +12,14 @@ using N_m3u8DL_RE.Parser;
|
|||||||
using N_m3u8DL_RE.Parser.Mp4;
|
using N_m3u8DL_RE.Parser.Mp4;
|
||||||
using N_m3u8DL_RE.Util;
|
using N_m3u8DL_RE.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Threading.Tasks.Dataflow;
|
using System.Threading.Tasks.Dataflow;
|
||||||
using System.Xml.Linq;
|
using N_m3u8DL_RE.Enum;
|
||||||
|
|
||||||
|
namespace N_m3u8DL_RE.DownloadManager;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.DownloadManager
|
|
||||||
{
|
|
||||||
internal class SimpleLiveRecordManager2
|
internal class SimpleLiveRecordManager2
|
||||||
{
|
{
|
||||||
IDownloader Downloader;
|
IDownloader Downloader;
|
||||||
@ -32,7 +27,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
StreamExtractor StreamExtractor;
|
StreamExtractor StreamExtractor;
|
||||||
List<StreamSpec> SelectedSteams;
|
List<StreamSpec> SelectedSteams;
|
||||||
ConcurrentDictionary<int, string> PipeSteamNamesDic = new();
|
ConcurrentDictionary<int, string> PipeSteamNamesDic = new();
|
||||||
List<OutputFile> OutputFiles = new();
|
List<OutputFile> OutputFiles = [];
|
||||||
DateTime? PublishDateTime;
|
DateTime? PublishDateTime;
|
||||||
bool STOP_FLAG = false;
|
bool STOP_FLAG = false;
|
||||||
int WAIT_SEC = 0; // 刷新间隔
|
int WAIT_SEC = 0; // 刷新间隔
|
||||||
@ -46,7 +41,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
ConcurrentDictionary<int, long> DateTimeDic = new(); // 上次下载的dateTime
|
ConcurrentDictionary<int, long> DateTimeDic = new(); // 上次下载的dateTime
|
||||||
CancellationTokenSource CancellationTokenSource = new(); // 取消Wait
|
CancellationTokenSource CancellationTokenSource = new(); // 取消Wait
|
||||||
|
|
||||||
private readonly object lockObj = new object();
|
private readonly Lock lockObj = new();
|
||||||
TimeSpan? audioStart = null;
|
TimeSpan? audioStart = null;
|
||||||
|
|
||||||
public SimpleLiveRecordManager2(DownloaderConfig downloaderConfig, List<StreamSpec> selectedSteams, StreamExtractor streamExtractor)
|
public SimpleLiveRecordManager2(DownloaderConfig downloaderConfig, List<StreamSpec> selectedSteams, StreamExtractor streamExtractor)
|
||||||
@ -65,9 +60,9 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
if (_key != null)
|
if (_key != null)
|
||||||
{
|
{
|
||||||
if (DownloaderConfig.MyOptions.Keys == null)
|
if (DownloaderConfig.MyOptions.Keys == null)
|
||||||
DownloaderConfig.MyOptions.Keys = new string[] { _key };
|
DownloaderConfig.MyOptions.Keys = [_key];
|
||||||
else
|
else
|
||||||
DownloaderConfig.MyOptions.Keys = DownloaderConfig.MyOptions.Keys.Concat(new string[] { _key }).ToArray();
|
DownloaderConfig.MyOptions.Keys = [..DownloaderConfig.MyOptions.Keys, _key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,13 +111,13 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
|
|
||||||
private void ChangeSpecInfo(StreamSpec streamSpec, List<Mediainfo> mediainfos, ref bool useAACFilter)
|
private void ChangeSpecInfo(StreamSpec streamSpec, List<Mediainfo> mediainfos, ref bool useAACFilter)
|
||||||
{
|
{
|
||||||
if (!DownloaderConfig.MyOptions.BinaryMerge && mediainfos.Any(m => m.DolbyVison == true))
|
if (!DownloaderConfig.MyOptions.BinaryMerge && mediainfos.Any(m => m.DolbyVison))
|
||||||
{
|
{
|
||||||
DownloaderConfig.MyOptions.BinaryMerge = true;
|
DownloaderConfig.MyOptions.BinaryMerge = true;
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge2}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge2}[/]");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DownloaderConfig.MyOptions.MuxAfterDone && mediainfos.Any(m => m.DolbyVison == true))
|
if (DownloaderConfig.MyOptions.MuxAfterDone && mediainfos.Any(m => m.DolbyVison))
|
||||||
{
|
{
|
||||||
DownloaderConfig.MyOptions.MuxAfterDone = false;
|
DownloaderConfig.MyOptions.MuxAfterDone = false;
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge5}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge5}[/]");
|
||||||
@ -141,7 +136,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
{
|
{
|
||||||
streamSpec.MediaType = MediaType.SUBTITLES;
|
streamSpec.MediaType = MediaType.SUBTITLES;
|
||||||
|
|
||||||
if (streamSpec.Extension == null || streamSpec.Extension == "ts")
|
if (streamSpec.Extension is null or "ts")
|
||||||
streamSpec.Extension = "vtt";
|
streamSpec.Extension = "vtt";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,15 +144,14 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
private async Task<bool> RecordStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer, BufferBlock<List<MediaSegment>> source)
|
private async Task<bool> RecordStreamAsync(StreamSpec streamSpec, ProgressTask task, SpeedContainer speedContainer, BufferBlock<List<MediaSegment>> source)
|
||||||
{
|
{
|
||||||
var baseTimestamp = PublishDateTime == null ? 0L : (long)(PublishDateTime.Value.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
|
var baseTimestamp = PublishDateTime == null ? 0L : (long)(PublishDateTime.Value.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds;
|
||||||
//mp4decrypt
|
var decryptionBinaryPath = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
|
||||||
var mp4decrypt = DownloaderConfig.MyOptions.DecryptionBinaryPath!;
|
|
||||||
var mp4InitFile = "";
|
var mp4InitFile = "";
|
||||||
var currentKID = "";
|
var currentKID = "";
|
||||||
var readInfo = false; // 是否读取过
|
var readInfo = false; // 是否读取过
|
||||||
bool useAACFilter = false; // ffmpeg合并flag
|
bool useAACFilter = false; // ffmpeg合并flag
|
||||||
bool initDownloaded = false; // 是否下载过init文件
|
bool initDownloaded = false; // 是否下载过init文件
|
||||||
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
|
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
|
||||||
List<Mediainfo> mediaInfos = new();
|
List<Mediainfo> mediaInfos = [];
|
||||||
Stream? fileOutputStream = null;
|
Stream? fileOutputStream = null;
|
||||||
WebVttSub currentVtt = new(); // 字幕流始终维护一个实例
|
WebVttSub currentVtt = new(); // 字幕流始终维护一个实例
|
||||||
bool firstSub = true;
|
bool firstSub = true;
|
||||||
@ -170,6 +164,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
||||||
var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
|
var saveName = DownloaderConfig.MyOptions.SaveName != null ? $"{DownloaderConfig.MyOptions.SaveName}.{streamSpec.Language}".TrimEnd('.') : dirName;
|
||||||
var headers = DownloaderConfig.Headers;
|
var headers = DownloaderConfig.Headers;
|
||||||
|
var decryptEngine = DownloaderConfig.MyOptions.DecryptionEngine;
|
||||||
|
|
||||||
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}");
|
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}");
|
||||||
|
|
||||||
@ -202,7 +197,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var path = Path.Combine(tmpDir, "_init.mp4.tmp");
|
var path = Path.Combine(tmpDir, "_init.mp4.tmp");
|
||||||
var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, speedContainer, headers);
|
var result = await Downloader.DownloadSegmentAsync(streamSpec.Playlist.MediaInit, path, speedContainer, headers);
|
||||||
FileDic[streamSpec.Playlist.MediaInit] = result;
|
FileDic[streamSpec.Playlist.MediaInit] = result;
|
||||||
if (result == null || !result.Success)
|
if (result is not { Success: true })
|
||||||
{
|
{
|
||||||
throw new Exception("Download init file failed!");
|
throw new Exception("Download init file failed!");
|
||||||
}
|
}
|
||||||
@ -210,17 +205,17 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
task.Increment(1);
|
task.Increment(1);
|
||||||
|
|
||||||
// 读取mp4信息
|
// 读取mp4信息
|
||||||
if (result != null && result.Success)
|
if (result is { Success: true })
|
||||||
{
|
{
|
||||||
currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID;
|
currentKID = MP4DecryptUtil.GetMP4Info(result.ActualFilePath).KID;
|
||||||
// 从文件读取KEY
|
// 从文件读取KEY
|
||||||
await SearchKeyAsync(currentKID);
|
await SearchKeyAsync(currentKID);
|
||||||
// 实时解密
|
// 实时解密
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID) && StreamExtractor.ExtractorType != ExtractorType.MSS)
|
if ((streamSpec.Playlist.MediaInit.IsEncrypted || !string.IsNullOrEmpty(currentKID)) && DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID) && StreamExtractor.ExtractorType != ExtractorType.MSS)
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
FileDic[streamSpec.Playlist.MediaInit]!.ActualFilePath = dec;
|
||||||
@ -262,12 +257,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp");
|
var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp");
|
||||||
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
|
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
|
||||||
FileDic[seg] = result;
|
FileDic[seg] = result;
|
||||||
if (result == null || !result.Success)
|
if (result is not { Success: true })
|
||||||
{
|
{
|
||||||
throw new Exception("Download first segment failed!");
|
throw new Exception("Download first segment failed!");
|
||||||
}
|
}
|
||||||
task.Increment(1);
|
task.Increment(1);
|
||||||
if (result != null && result.Success)
|
if (result is { Success: true })
|
||||||
{
|
{
|
||||||
// 修复MSS init
|
// 修复MSS init
|
||||||
if (StreamExtractor.ExtractorType == ExtractorType.MSS)
|
if (StreamExtractor.ExtractorType == ExtractorType.MSS)
|
||||||
@ -275,12 +270,12 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var processor = new MSSMoovProcessor(streamSpec);
|
var processor = new MSSMoovProcessor(streamSpec);
|
||||||
var header = processor.GenHeader(File.ReadAllBytes(result.ActualFilePath));
|
var header = processor.GenHeader(File.ReadAllBytes(result.ActualFilePath));
|
||||||
await File.WriteAllBytesAsync(FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath, header);
|
await File.WriteAllBytesAsync(FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath, header);
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
if (seg.IsEncrypted && DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
// 需要重新解密init
|
// 需要重新解密init
|
||||||
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
|
var enc = FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec;
|
FileDic[streamSpec.Playlist!.MediaInit!]!.ActualFilePath = dec;
|
||||||
@ -295,11 +290,11 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
// 从文件读取KEY
|
// 从文件读取KEY
|
||||||
await SearchKeyAsync(currentKID);
|
await SearchKeyAsync(currentKID);
|
||||||
// 实时解密
|
// 实时解密
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
if (seg.IsEncrypted && DownloaderConfig.MyOptions.MP4RealTimeDecryption && !string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
File.Delete(enc);
|
File.Delete(enc);
|
||||||
@ -335,14 +330,14 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp");
|
var path = Path.Combine(tmpDir, filename + $".{streamSpec.Extension ?? "clip"}.tmp");
|
||||||
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
|
var result = await Downloader.DownloadSegmentAsync(seg, path, speedContainer, headers);
|
||||||
FileDic[seg] = result;
|
FileDic[seg] = result;
|
||||||
if (result != null && result.Success)
|
if (result is { Success: true })
|
||||||
task.Increment(1);
|
task.Increment(1);
|
||||||
// 实时解密
|
// 实时解密
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && result != null && result.Success && !string.IsNullOrEmpty(currentKID))
|
if (seg.IsEncrypted && DownloaderConfig.MyOptions.MP4RealTimeDecryption && result is { Success: true } && !string.IsNullOrEmpty(currentKID))
|
||||||
{
|
{
|
||||||
var enc = result.ActualFilePath;
|
var enc = result.ActualFilePath;
|
||||||
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
var dec = Path.Combine(Path.GetDirectoryName(enc)!, Path.GetFileNameWithoutExtension(enc) + "_dec" + Path.GetExtension(enc));
|
||||||
var dResult = await MP4DecryptUtil.DecryptAsync(DownloaderConfig.MyOptions.UseShakaPackager, mp4decrypt, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
|
var dResult = await MP4DecryptUtil.DecryptAsync(decryptEngine, decryptionBinaryPath, DownloaderConfig.MyOptions.Keys, enc, dec, currentKID, mp4InitFile);
|
||||||
if (dResult)
|
if (dResult)
|
||||||
{
|
{
|
||||||
File.Delete(enc);
|
File.Delete(enc);
|
||||||
@ -352,14 +347,13 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 自动修复VTT raw字幕
|
// 自动修复VTT raw字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("vtt"))
|
||||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("vtt"))
|
|
||||||
{
|
{
|
||||||
// 排序字幕并修正时间戳
|
// 排序字幕并修正时间戳
|
||||||
var keys = FileDic.Keys.OrderBy(k => k.Index);
|
var keys = FileDic.Keys.OrderBy(k => k.Index).ToList();
|
||||||
foreach (var seg in keys)
|
foreach (var seg in keys)
|
||||||
{
|
{
|
||||||
var vttContent = File.ReadAllText(FileDic[seg]!.ActualFilePath);
|
var vttContent = await File.ReadAllTextAsync(FileDic[seg]!.ActualFilePath);
|
||||||
var waitCount = 0;
|
var waitCount = 0;
|
||||||
while (DownloaderConfig.MyOptions.LiveFixVttByAudio && audioStart == null && waitCount++ < 5)
|
while (DownloaderConfig.MyOptions.LiveFixVttByAudio && audioStart == null && waitCount++ < 5)
|
||||||
{
|
{
|
||||||
@ -381,7 +375,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
||||||
&& streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s"))
|
&& streamSpec.Codecs != "stpp" && streamSpec.Extension != null && streamSpec.Extension.Contains("m4s"))
|
||||||
{
|
{
|
||||||
var initFile = FileDic.Values.Where(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init")).FirstOrDefault();
|
var initFile = FileDic.Values.FirstOrDefault(v => Path.GetFileName(v!.ActualFilePath).StartsWith("_init"));
|
||||||
var iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath);
|
var iniFileBytes = File.ReadAllBytes(initFile!.ActualFilePath);
|
||||||
var (sawVtt, timescale) = MP4VttUtil.CheckInit(iniFileBytes);
|
var (sawVtt, timescale) = MP4VttUtil.CheckInit(iniFileBytes);
|
||||||
if (sawVtt)
|
if (sawVtt)
|
||||||
@ -401,10 +395,9 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 自动修复TTML raw字幕
|
// 自动修复TTML raw字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("ttml"))
|
||||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("ttml"))
|
|
||||||
{
|
{
|
||||||
var keys = FileDic.OrderBy(s => s.Key.Index).Where(v => v.Value!.ActualFilePath.EndsWith(".m4s")).Select(s => s.Key);
|
var keys = FileDic.OrderBy(s => s.Key.Index).Where(v => v.Value!.ActualFilePath.EndsWith(".m4s")).Select(s => s.Key).ToList();
|
||||||
if (firstSub)
|
if (firstSub)
|
||||||
{
|
{
|
||||||
if (baseTimestamp != 0)
|
if (baseTimestamp != 0)
|
||||||
@ -442,8 +435,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 自动修复TTML mp4字幕
|
// 自动修复TTML mp4字幕
|
||||||
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec.MediaType == Common.Enum.MediaType.SUBTITLES
|
if (DownloaderConfig.MyOptions.AutoSubtitleFix && streamSpec is { MediaType: Common.Enum.MediaType.SUBTITLES, Extension: not null } && streamSpec.Extension.Contains("m4s")
|
||||||
&& streamSpec.Extension != null && streamSpec.Extension.Contains("m4s")
|
|
||||||
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp"))
|
&& streamSpec.Codecs != null && streamSpec.Codecs.Contains("stpp"))
|
||||||
{
|
{
|
||||||
// sawTtml暂时不判断
|
// sawTtml暂时不判断
|
||||||
@ -503,12 +495,11 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
// 合并
|
// 合并
|
||||||
var outputExt = "." + streamSpec.Extension;
|
var outputExt = "." + streamSpec.Extension;
|
||||||
if (streamSpec.Extension == null) outputExt = ".ts";
|
if (streamSpec.Extension == null) outputExt = ".ts";
|
||||||
else if (streamSpec.MediaType == MediaType.AUDIO && streamSpec.Extension == "m4s") outputExt = ".m4a";
|
else if (streamSpec is { MediaType: MediaType.AUDIO, Extension: "m4s" }) outputExt = ".m4a";
|
||||||
else if (streamSpec.MediaType != MediaType.SUBTITLES && streamSpec.Extension == "m4s") outputExt = ".mp4";
|
else if (streamSpec.MediaType != MediaType.SUBTITLES && streamSpec.Extension == "m4s") outputExt = ".mp4";
|
||||||
else if (streamSpec.MediaType == MediaType.SUBTITLES)
|
else if (streamSpec.MediaType == MediaType.SUBTITLES)
|
||||||
{
|
{
|
||||||
if (DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT) outputExt = ".srt";
|
outputExt = DownloaderConfig.MyOptions.SubtitleFormat == Enum.SubtitleFormat.SRT ? ".srt" : ".vtt";
|
||||||
else outputExt = ".vtt";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var output = Path.Combine(saveDir, saveName + outputExt);
|
var output = Path.Combine(saveDir, saveName + outputExt);
|
||||||
@ -541,7 +532,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
fileOutputStream = PipeUtil.CreatePipe(pipeName);
|
fileOutputStream = PipeUtil.CreatePipe(pipeName);
|
||||||
Logger.InfoMarkUp($"{ResString.namedPipeCreated} [cyan]{pipeName.EscapeMarkup()}[/]");
|
Logger.InfoMarkUp($"{ResString.namedPipeCreated} [cyan]{pipeName.EscapeMarkup()}[/]");
|
||||||
PipeSteamNamesDic[task.Id] = pipeName;
|
PipeSteamNamesDic[task.Id] = pipeName;
|
||||||
if (PipeSteamNamesDic.Count == SelectedSteams.Where(x => x.MediaType != MediaType.SUBTITLES).Count())
|
if (PipeSteamNamesDic.Count == SelectedSteams.Count(x => x.MediaType != MediaType.SUBTITLES))
|
||||||
{
|
{
|
||||||
var names = PipeSteamNamesDic.OrderBy(i => i.Key).Select(k => k.Value).ToArray();
|
var names = PipeSteamNamesDic.OrderBy(i => i.Key).Select(k => k.Value).ToArray();
|
||||||
Logger.WarnMarkUp($"{ResString.namedPipeMux} [deepskyblue1]{Path.GetFileName(output).EscapeMarkup()}[/]");
|
Logger.WarnMarkUp($"{ResString.namedPipeMux} [deepskyblue1]{Path.GetFileName(output).EscapeMarkup()}[/]");
|
||||||
@ -560,10 +551,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var files = FileDic.Where(f => f.Key != streamSpec.Playlist!.MediaInit).OrderBy(s => s.Key.Index).Select(f => f.Value).Select(v => v!.ActualFilePath).ToArray();
|
var files = FileDic.Where(f => f.Key != streamSpec.Playlist!.MediaInit).OrderBy(s => s.Key.Index).Select(f => f.Value).Select(v => v!.ActualFilePath).ToArray();
|
||||||
if (initResult != null && mp4InitFile != "")
|
if (initResult != null && mp4InitFile != "")
|
||||||
{
|
{
|
||||||
//shaka实时解密不需要init文件用于合并,mp4decrpyt需要
|
// shaka/ffmpeg实时解密不需要init文件用于合并,mp4decrpyt需要
|
||||||
if (!DownloaderConfig.MyOptions.UseShakaPackager)
|
if (string.IsNullOrEmpty(currentKID) || decryptEngine == DecryptEngine.MP4DECRYPT)
|
||||||
{
|
{
|
||||||
files = new string[] { initResult.ActualFilePath }.Concat(files).ToArray();
|
files = [initResult.ActualFilePath, ..files];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var inputFilePath in files)
|
foreach (var inputFilePath in files)
|
||||||
@ -627,8 +618,8 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileOutputStream != null)
|
if (fileOutputStream == null) return true;
|
||||||
{
|
|
||||||
if (!DownloaderConfig.MyOptions.LivePipeMux)
|
if (!DownloaderConfig.MyOptions.LivePipeMux)
|
||||||
{
|
{
|
||||||
// 记录所有文件信息
|
// 记录所有文件信息
|
||||||
@ -644,7 +635,6 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
}
|
}
|
||||||
fileOutputStream.Close();
|
fileOutputStream.Close();
|
||||||
fileOutputStream.Dispose();
|
fileOutputStream.Dispose();
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -653,11 +643,10 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
{
|
{
|
||||||
while (!STOP_FLAG)
|
while (!STOP_FLAG)
|
||||||
{
|
{
|
||||||
if (WAIT_SEC != 0)
|
if (WAIT_SEC == 0) continue;
|
||||||
{
|
|
||||||
// 1. MPD 所有URL相同 单次请求即可获得所有轨道的信息
|
// 1. MPD 所有URL相同 单次请求即可获得所有轨道的信息
|
||||||
// 2. M3U8 所有URL不同 才需要多次请求
|
// 2. M3U8 所有URL不同 才需要多次请求
|
||||||
|
|
||||||
await Parallel.ForEachAsync(dic, async (dic, _) =>
|
await Parallel.ForEachAsync(dic, async (dic, _) =>
|
||||||
{
|
{
|
||||||
var streamSpec = dic.Key;
|
var streamSpec = dic.Key;
|
||||||
@ -697,7 +686,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检测时长限制
|
// 检测时长限制
|
||||||
if (!STOP_FLAG && RecordLimitReachedDic.Values.All(x => x == true))
|
if (!STOP_FLAG && RecordLimitReachedDic.Values.All(x => x))
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimitReached}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.liveLimitReached}[/]");
|
||||||
STOP_FLAG = true;
|
STOP_FLAG = true;
|
||||||
@ -728,7 +717,6 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void FilterMediaSegments(StreamSpec streamSpec, ProgressTask task, bool allHasDatetime, bool allSamePath)
|
private void FilterMediaSegments(StreamSpec streamSpec, ProgressTask task, bool allHasDatetime, bool allSamePath)
|
||||||
{
|
{
|
||||||
@ -788,7 +776,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
Logger.WarnMarkUp($"set refresh interval to {WAIT_SEC} seconds");
|
Logger.WarnMarkUp($"set refresh interval to {WAIT_SEC} seconds");
|
||||||
}
|
}
|
||||||
// 如果没有选中音频 取消通过音频修复vtt时间轴
|
// 如果没有选中音频 取消通过音频修复vtt时间轴
|
||||||
if (!SelectedSteams.Any(x => x.MediaType == MediaType.AUDIO))
|
if (SelectedSteams.All(x => x.MediaType != MediaType.AUDIO))
|
||||||
{
|
{
|
||||||
DownloaderConfig.MyOptions.LiveFixVttByAudio = false;
|
DownloaderConfig.MyOptions.LiveFixVttByAudio = false;
|
||||||
}
|
}
|
||||||
@ -844,9 +832,8 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
|
|
||||||
DownloaderConfig.MyOptions.ConcurrentDownload = true;
|
DownloaderConfig.MyOptions.ConcurrentDownload = true;
|
||||||
DownloaderConfig.MyOptions.MP4RealTimeDecryption = true;
|
DownloaderConfig.MyOptions.MP4RealTimeDecryption = true;
|
||||||
DownloaderConfig.MyOptions.LiveRecordLimit = DownloaderConfig.MyOptions.LiveRecordLimit ?? TimeSpan.MaxValue;
|
DownloaderConfig.MyOptions.LiveRecordLimit ??= TimeSpan.MaxValue;
|
||||||
if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !DownloaderConfig.MyOptions.UseShakaPackager
|
if (DownloaderConfig.MyOptions is { MP4RealTimeDecryption: true, DecryptionEngine: not DecryptEngine.SHAKA_PACKAGER, Keys.Length: > 0 })
|
||||||
&& DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0)
|
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.realTimeDecMessage}[/]");
|
||||||
var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
|
var limit = DownloaderConfig.MyOptions.LiveRecordLimit;
|
||||||
if (limit != TimeSpan.MaxValue)
|
if (limit != TimeSpan.MaxValue)
|
||||||
@ -871,7 +858,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
var success = Results.Values.All(v => v == true);
|
var success = Results.Values.All(v => v == true);
|
||||||
|
|
||||||
// 删除临时文件夹
|
// 删除临时文件夹
|
||||||
if (!DownloaderConfig.MyOptions.SkipMerge && DownloaderConfig.MyOptions.DelAfterDone && success)
|
if (DownloaderConfig.MyOptions is { SkipMerge: false, DelAfterDone: true } && success)
|
||||||
{
|
{
|
||||||
foreach (var item in StreamExtractor.RawFiles)
|
foreach (var item in StreamExtractor.RawFiles)
|
||||||
{
|
{
|
||||||
@ -886,7 +873,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
{
|
{
|
||||||
OutputFiles = OutputFiles.OrderBy(o => o.Index).ToList();
|
OutputFiles = OutputFiles.OrderBy(o => o.Index).ToList();
|
||||||
// 是否跳过字幕
|
// 是否跳过字幕
|
||||||
if (DownloaderConfig.MyOptions.MuxOptions.SkipSubtitle)
|
if (DownloaderConfig.MyOptions.MuxOptions!.SkipSubtitle)
|
||||||
{
|
{
|
||||||
OutputFiles = OutputFiles.Where(o => o.MediaType != MediaType.SUBTITLES).ToList();
|
OutputFiles = OutputFiles.Where(o => o.MediaType != MediaType.SUBTITLES).ToList();
|
||||||
}
|
}
|
||||||
@ -896,14 +883,14 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
}
|
}
|
||||||
OutputFiles.ForEach(f => Logger.WarnMarkUp($"[grey]{Path.GetFileName(f.FilePath).EscapeMarkup()}[/]"));
|
OutputFiles.ForEach(f => Logger.WarnMarkUp($"[grey]{Path.GetFileName(f.FilePath).EscapeMarkup()}[/]"));
|
||||||
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
|
||||||
var ext = DownloaderConfig.MyOptions.MuxOptions.MuxToMp4 ? ".mp4" : ".mkv";
|
var ext = OtherUtil.GetMuxExtension(DownloaderConfig.MyOptions.MuxOptions.MuxFormat);
|
||||||
var dirName = Path.GetFileName(DownloaderConfig.DirPrefix);
|
var dirName = Path.GetFileName(DownloaderConfig.DirPrefix);
|
||||||
var outName = $"{dirName}.MUX";
|
var outName = $"{dirName}.MUX";
|
||||||
var outPath = Path.Combine(saveDir, outName);
|
var outPath = Path.Combine(saveDir, outName);
|
||||||
Logger.WarnMarkUp($"Muxing to [grey]{outName.EscapeMarkup()}{ext}[/]");
|
Logger.WarnMarkUp($"Muxing to [grey]{outName.EscapeMarkup()}{ext}[/]");
|
||||||
var result = false;
|
var result = false;
|
||||||
if (DownloaderConfig.MyOptions.MuxOptions.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MyOptions.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
|
if (DownloaderConfig.MyOptions.MuxOptions.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MyOptions.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
|
||||||
else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MyOptions.MuxOptions.MuxToMp4, !DownloaderConfig.MyOptions.NoDateInfo);
|
else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MyOptions.MuxOptions.MuxFormat, !DownloaderConfig.MyOptions.NoDateInfo);
|
||||||
// 完成后删除各轨道文件
|
// 完成后删除各轨道文件
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
@ -932,4 +919,3 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,15 +1,9 @@
|
|||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Downloader
|
namespace N_m3u8DL_RE.Downloader;
|
||||||
{
|
|
||||||
internal interface IDownloader
|
internal interface IDownloader
|
||||||
{
|
{
|
||||||
Task<DownloadResult?> DownloadSegmentAsync(MediaSegment segment, string savePath, SpeedContainer speedContainer, Dictionary<string, string>? headers = null);
|
Task<DownloadResult?> DownloadSegmentAsync(MediaSegment segment, string savePath, SpeedContainer speedContainer, Dictionary<string, string>? headers = null);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -6,18 +6,9 @@ using N_m3u8DL_RE.Crypto;
|
|||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using N_m3u8DL_RE.Util;
|
using N_m3u8DL_RE.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Downloader
|
namespace N_m3u8DL_RE.Downloader;
|
||||||
{
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 简单下载器
|
/// 简单下载器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -34,23 +25,25 @@ namespace N_m3u8DL_RE.Downloader
|
|||||||
{
|
{
|
||||||
var url = segment.Url;
|
var url = segment.Url;
|
||||||
var (des, dResult) = await DownClipAsync(url, savePath, speedContainer, segment.StartRange, segment.StopRange, headers, DownloaderConfig.MyOptions.DownloadRetryCount);
|
var (des, dResult) = await DownClipAsync(url, savePath, speedContainer, segment.StartRange, segment.StopRange, headers, DownloaderConfig.MyOptions.DownloadRetryCount);
|
||||||
if (dResult != null && dResult.Success && dResult.ActualFilePath != des)
|
if (dResult is { Success: true } && dResult.ActualFilePath != des)
|
||||||
{
|
{
|
||||||
if (segment.EncryptInfo != null)
|
switch (segment.EncryptInfo.Method)
|
||||||
{
|
{
|
||||||
if (segment.EncryptInfo.Method == EncryptMethod.AES_128)
|
case EncryptMethod.AES_128:
|
||||||
{
|
{
|
||||||
var key = segment.EncryptInfo.Key;
|
var key = segment.EncryptInfo.Key;
|
||||||
var iv = segment.EncryptInfo.IV;
|
var iv = segment.EncryptInfo.IV;
|
||||||
AESUtil.AES128Decrypt(dResult.ActualFilePath, key!, iv!);
|
AESUtil.AES128Decrypt(dResult.ActualFilePath, key!, iv!);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else if (segment.EncryptInfo.Method == EncryptMethod.AES_128_ECB)
|
case EncryptMethod.AES_128_ECB:
|
||||||
{
|
{
|
||||||
var key = segment.EncryptInfo.Key;
|
var key = segment.EncryptInfo.Key;
|
||||||
var iv = segment.EncryptInfo.IV;
|
var iv = segment.EncryptInfo.IV;
|
||||||
AESUtil.AES128Decrypt(dResult.ActualFilePath, key!, iv!, System.Security.Cryptography.CipherMode.ECB);
|
AESUtil.AES128Decrypt(dResult.ActualFilePath, key!, iv!, System.Security.Cryptography.CipherMode.ECB);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else if (segment.EncryptInfo.Method == EncryptMethod.CHACHA20)
|
case EncryptMethod.CHACHA20:
|
||||||
{
|
{
|
||||||
var key = segment.EncryptInfo.Key;
|
var key = segment.EncryptInfo.Key;
|
||||||
var nonce = segment.EncryptInfo.IV;
|
var nonce = segment.EncryptInfo.IV;
|
||||||
@ -58,10 +51,11 @@ namespace N_m3u8DL_RE.Downloader
|
|||||||
var fileBytes = File.ReadAllBytes(dResult.ActualFilePath);
|
var fileBytes = File.ReadAllBytes(dResult.ActualFilePath);
|
||||||
var decrypted = ChaCha20Util.DecryptPer1024Bytes(fileBytes, key!, nonce!);
|
var decrypted = ChaCha20Util.DecryptPer1024Bytes(fileBytes, key!, nonce!);
|
||||||
await File.WriteAllBytesAsync(dResult.ActualFilePath, decrypted);
|
await File.WriteAllBytesAsync(dResult.ActualFilePath, decrypted);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else if (segment.EncryptInfo.Method == EncryptMethod.SAMPLE_AES_CTR)
|
case EncryptMethod.SAMPLE_AES_CTR:
|
||||||
{
|
|
||||||
// throw new NotSupportedException("SAMPLE-AES-CTR");
|
// throw new NotSupportedException("SAMPLE-AES-CTR");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Image头处理
|
// Image头处理
|
||||||
@ -74,7 +68,6 @@ namespace N_m3u8DL_RE.Downloader
|
|||||||
{
|
{
|
||||||
await OtherUtil.DeGzipFileAsync(dResult.ActualFilePath);
|
await OtherUtil.DeGzipFileAsync(dResult.ActualFilePath);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 处理完成后改名
|
// 处理完成后改名
|
||||||
File.Move(dResult.ActualFilePath, des);
|
File.Move(dResult.ActualFilePath, des);
|
||||||
@ -108,14 +101,15 @@ namespace N_m3u8DL_RE.Downloader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 另起线程进行监控
|
// 另起线程进行监控
|
||||||
|
var cts = cancellationTokenSource;
|
||||||
using var watcher = Task.Factory.StartNew(async () =>
|
using var watcher = Task.Factory.StartNew(async () =>
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (cancellationTokenSource == null || cancellationTokenSource.IsCancellationRequested) break;
|
if (cts.IsCancellationRequested) break;
|
||||||
if (speedContainer.ShouldStop)
|
if (speedContainer.ShouldStop)
|
||||||
{
|
{
|
||||||
cancellationTokenSource.Cancel();
|
cts.Cancel();
|
||||||
Logger.DebugMarkUp("Cancel...");
|
Logger.DebugMarkUp("Cancel...");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -132,7 +126,7 @@ namespace N_m3u8DL_RE.Downloader
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.DebugMarkUp($"[grey]{ex.Message.EscapeMarkup()} retryCount: {retryCount}[/]");
|
Logger.DebugMarkUp($"[grey]{ex.Message.EscapeMarkup()} retryCount: {retryCount}[/]");
|
||||||
Logger.Debug(url + " " + ex.ToString());
|
Logger.Debug(url + " " + ex);
|
||||||
Logger.Extra($"Ah oh!{Environment.NewLine}RetryCount => {retryCount}{Environment.NewLine}Exception => {ex.Message}{Environment.NewLine}Url => {url}");
|
Logger.Extra($"Ah oh!{Environment.NewLine}RetryCount => {retryCount}{Environment.NewLine}Exception => {ex.Message}{Environment.NewLine}Url => {url}");
|
||||||
if (retryCount-- > 0)
|
if (retryCount-- > 0)
|
||||||
{
|
{
|
||||||
@ -158,4 +152,3 @@ namespace N_m3u8DL_RE.Downloader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Entity;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
|
||||||
{
|
|
||||||
public class CustomRange
|
public class CustomRange
|
||||||
{
|
{
|
||||||
public required string InputStr { get; set; }
|
public required string InputStr { get; set; }
|
||||||
@ -20,4 +14,3 @@ namespace N_m3u8DL_RE.Entity
|
|||||||
return $"StartSec: {StartSec}, EndSec: {EndSec}, StartSegIndex: {StartSegIndex}, EndSegIndex: {EndSegIndex}";
|
return $"StartSec: {StartSec}, EndSec: {EndSec}, StartSegIndex: {StartSegIndex}, EndSegIndex: {EndSegIndex}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,19 +1,11 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Entity;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
|
||||||
{
|
|
||||||
internal class DownloadResult
|
internal class DownloadResult
|
||||||
{
|
{
|
||||||
public bool Success { get => (ActualContentLength != null && RespContentLength != null) ? (RespContentLength == ActualContentLength) : (ActualContentLength == null ? false : true); }
|
public bool Success => (ActualContentLength != null && RespContentLength != null) ? (RespContentLength == ActualContentLength) : (ActualContentLength != null);
|
||||||
public long? RespContentLength { get; set; }
|
public long? RespContentLength { get; set; }
|
||||||
public long? ActualContentLength { get; set; }
|
public long? ActualContentLength { get; set; }
|
||||||
public bool ImageHeader { get; set; } = false; // 图片伪装
|
public bool ImageHeader { get; set; } = false; // 图片伪装
|
||||||
public bool GzipHeader { get; set; } = false; // GZip压缩
|
public bool GzipHeader { get; set; } = false; // GZip压缩
|
||||||
public required string ActualFilePath { get; set; }
|
public required string ActualFilePath { get; set; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
using System;
|
using Spectre.Console;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
namespace N_m3u8DL_RE.Entity;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Spectre.Console;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
|
||||||
{
|
|
||||||
internal class Mediainfo
|
internal class Mediainfo
|
||||||
{
|
{
|
||||||
public string? Id { get; set; }
|
public string? Id { get; set; }
|
||||||
@ -30,4 +25,3 @@ namespace N_m3u8DL_RE.Entity
|
|||||||
return "[steelblue]" + ToString().EscapeMarkup() + ((HDR && !DolbyVison) ? " [darkorange3_1][[HDR]][/]" : "") + (DolbyVison ? " [darkorange3_1][[DOVI]][/]" : "") + "[/]";
|
return "[steelblue]" + ToString().EscapeMarkup() + ((HDR && !DolbyVison) ? " [darkorange3_1][[HDR]][/]" : "") + (DolbyVison ? " [darkorange3_1][[DOVI]][/]" : "") + "[/]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
using System;
|
using N_m3u8DL_RE.Enum;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
namespace N_m3u8DL_RE.Entity;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
|
||||||
{
|
|
||||||
internal class MuxOptions
|
internal class MuxOptions
|
||||||
{
|
{
|
||||||
public bool UseMkvmerge { get; set; } = false;
|
public bool UseMkvmerge { get; set; } = false;
|
||||||
public bool MuxToMp4 { get; set; } = false;
|
public MuxFormat MuxFormat { get; set; } = MuxFormat.MP4;
|
||||||
public bool KeepFiles { get; set; } = false;
|
public bool KeepFiles { get; set; } = false;
|
||||||
public bool SkipSubtitle { get; set; } = false;
|
public bool SkipSubtitle { get; set; } = false;
|
||||||
public string? BinPath { get; set; }
|
public string? BinPath { get; set; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
namespace N_m3u8DL_RE.Entity;
|
||||||
{
|
|
||||||
internal class OutputFile
|
internal class OutputFile
|
||||||
{
|
{
|
||||||
public MediaType? MediaType { get; set; }
|
public MediaType? MediaType { get; set; }
|
||||||
@ -14,6 +9,5 @@ namespace N_m3u8DL_RE.Entity
|
|||||||
public required string FilePath { get; set; }
|
public required string FilePath { get; set; }
|
||||||
public string? LangCode { get; set; }
|
public string? LangCode { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public List<Mediainfo> Mediainfos { get; set; } = new();
|
public List<Mediainfo> Mediainfos { get; set; } = [];
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,30 +1,21 @@
|
|||||||
using NiL.JS.Statements;
|
namespace N_m3u8DL_RE.Entity;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
|
||||||
{
|
|
||||||
internal class SpeedContainer
|
internal class SpeedContainer
|
||||||
{
|
{
|
||||||
public bool SingleSegment { get; set; } = false;
|
public bool SingleSegment { get; set; } = false;
|
||||||
public long NowSpeed { get; set; } = 0L; // 当前每秒速度
|
public long NowSpeed { get; set; } = 0L; // 当前每秒速度
|
||||||
public long SpeedLimit { get; set; } = long.MaxValue; // 限速设置
|
public long SpeedLimit { get; set; } = long.MaxValue; // 限速设置
|
||||||
public long? ResponseLength { get; set; }
|
public long? ResponseLength { get; set; }
|
||||||
public long RDownloaded { get => _Rdownloaded; }
|
public long RDownloaded => _Rdownloaded;
|
||||||
private int _zeroSpeedCount = 0;
|
private int _zeroSpeedCount = 0;
|
||||||
public int LowSpeedCount { get => _zeroSpeedCount; }
|
public int LowSpeedCount => _zeroSpeedCount;
|
||||||
public bool ShouldStop { get => LowSpeedCount >= 20; }
|
public bool ShouldStop => LowSpeedCount >= 20;
|
||||||
|
|
||||||
///////////////////////////////////////////////////
|
///////////////////////////////////////////////////
|
||||||
|
|
||||||
private long _downloaded = 0;
|
private long _downloaded = 0;
|
||||||
private long _Rdownloaded = 0;
|
private long _Rdownloaded = 0;
|
||||||
public long Downloaded { get => _downloaded; }
|
public long Downloaded => _downloaded;
|
||||||
|
|
||||||
public int AddLowSpeedCount()
|
public int AddLowSpeedCount()
|
||||||
{
|
{
|
||||||
@ -56,4 +47,3 @@ namespace N_m3u8DL_RE.Entity
|
|||||||
_Rdownloaded = 0L;
|
_Rdownloaded = 0L;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Entity
|
namespace N_m3u8DL_RE.Entity;
|
||||||
{
|
|
||||||
public class StreamFilter
|
public class StreamFilter
|
||||||
{
|
{
|
||||||
public Regex? GroupIdReg { get; set; }
|
public Regex? GroupIdReg { get; set; }
|
||||||
@ -50,7 +46,6 @@ namespace N_m3u8DL_RE.Entity
|
|||||||
if (BandwidthMax != null) sb.Append($"{nameof(BandwidthMax)}: {BandwidthMax} ");
|
if (BandwidthMax != null) sb.Append($"{nameof(BandwidthMax)}: {BandwidthMax} ");
|
||||||
if (Role.HasValue) sb.Append($"Role: {Role} ");
|
if (Role.HasValue) sb.Append($"Role: {Role} ");
|
||||||
|
|
||||||
return sb.ToString() + $"For: {For}";
|
return sb + $"For: {For}";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
8
src/N_m3u8DL-RE/Enum/DecryptEngine.cs
Normal file
8
src/N_m3u8DL-RE/Enum/DecryptEngine.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace N_m3u8DL_RE.Enum;
|
||||||
|
|
||||||
|
internal enum DecryptEngine
|
||||||
|
{
|
||||||
|
MP4DECRYPT,
|
||||||
|
SHAKA_PACKAGER,
|
||||||
|
FFMPEG,
|
||||||
|
}
|
8
src/N_m3u8DL-RE/Enum/MuxFormat.cs
Normal file
8
src/N_m3u8DL-RE/Enum/MuxFormat.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace N_m3u8DL_RE.Enum;
|
||||||
|
|
||||||
|
internal enum MuxFormat
|
||||||
|
{
|
||||||
|
MP4,
|
||||||
|
MKV,
|
||||||
|
TS,
|
||||||
|
}
|
@ -1,14 +1,7 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Enum;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Enum
|
|
||||||
{
|
|
||||||
internal enum SubtitleFormat
|
internal enum SubtitleFormat
|
||||||
{
|
{
|
||||||
VTT,
|
VTT,
|
||||||
SRT
|
SRT
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<RootNamespace>N_m3u8DL_RE</RootNamespace>
|
<RootNamespace>N_m3u8DL_RE</RootNamespace>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>0.2.1</Version>
|
<Version>0.3.0</Version>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
@ -16,7 +16,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="NiL.JS" Version="2.5.1600" />
|
<PackageReference Include="NiL.JS" Version="2.5.1684" />
|
||||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -2,14 +2,9 @@
|
|||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Parser.Processor;
|
using N_m3u8DL_RE.Parser.Processor;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Processor
|
namespace N_m3u8DL_RE.Processor;
|
||||||
{
|
|
||||||
internal class DemoProcessor : ContentProcessor
|
internal class DemoProcessor : ContentProcessor
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -24,4 +19,3 @@ namespace N_m3u8DL_RE.Processor
|
|||||||
return rawText;
|
return rawText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -5,14 +5,9 @@ using N_m3u8DL_RE.Common.Util;
|
|||||||
using N_m3u8DL_RE.Parser.Config;
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Parser.Processor;
|
using N_m3u8DL_RE.Parser.Processor;
|
||||||
using N_m3u8DL_RE.Parser.Processor.HLS;
|
using N_m3u8DL_RE.Parser.Processor.HLS;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Processor
|
namespace N_m3u8DL_RE.Processor;
|
||||||
{
|
|
||||||
internal class DemoProcessor2 : KeyProcessor
|
internal class DemoProcessor2 : KeyProcessor
|
||||||
{
|
{
|
||||||
public override bool CanProcess(ExtractorType extractorType, string keyLine, string m3u8Url, string m3u8Content, ParserConfig parserConfig)
|
public override bool CanProcess(ExtractorType extractorType, string keyLine, string m3u8Url, string m3u8Content, ParserConfig parserConfig)
|
||||||
@ -28,4 +23,3 @@ namespace N_m3u8DL_RE.Processor
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -6,16 +6,9 @@ using N_m3u8DL_RE.Parser.Util;
|
|||||||
using NiL.JS.BaseLibrary;
|
using NiL.JS.BaseLibrary;
|
||||||
using NiL.JS.Core;
|
using NiL.JS.Core;
|
||||||
using NiL.JS.Extensions;
|
using NiL.JS.Extensions;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Processor
|
namespace N_m3u8DL_RE.Processor;
|
||||||
{
|
|
||||||
// "https://1429754964.rsc.cdn77.org/r/nh22/2022/VNUS_DE_NYKE/19_07_22_2302_skt/h264.mpd?secure=mSvVfvuciJt9wufUyzuBnA==,1658505709774" --urlprocessor-args "nowehoryzonty:timeDifference=-2274,filminfo.secureToken=vx54axqjal4f0yy2"
|
// "https://1429754964.rsc.cdn77.org/r/nh22/2022/VNUS_DE_NYKE/19_07_22_2302_skt/h264.mpd?secure=mSvVfvuciJt9wufUyzuBnA==,1658505709774" --urlprocessor-args "nowehoryzonty:timeDifference=-2274,filminfo.secureToken=vx54axqjal4f0yy2"
|
||||||
internal class NowehoryzontyUrlProcessor : UrlProcessor
|
internal class NowehoryzontyUrlProcessor : UrlProcessor
|
||||||
{
|
{
|
||||||
@ -221,4 +214,3 @@ namespace N_m3u8DL_RE.Processor
|
|||||||
|
|
||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using N_m3u8DL_RE.Parser.Config;
|
using System.Globalization;
|
||||||
|
using N_m3u8DL_RE.Parser.Config;
|
||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.Enum;
|
using N_m3u8DL_RE.Common.Enum;
|
||||||
using N_m3u8DL_RE.Parser;
|
using N_m3u8DL_RE.Parser;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using System.Globalization;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using N_m3u8DL_RE.Processor;
|
using N_m3u8DL_RE.Processor;
|
||||||
@ -14,21 +14,31 @@ using N_m3u8DL_RE.Util;
|
|||||||
using N_m3u8DL_RE.DownloadManager;
|
using N_m3u8DL_RE.DownloadManager;
|
||||||
using N_m3u8DL_RE.CommandLine;
|
using N_m3u8DL_RE.CommandLine;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http.Headers;
|
using N_m3u8DL_RE.Enum;
|
||||||
|
|
||||||
|
namespace N_m3u8DL_RE;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE
|
|
||||||
{
|
|
||||||
internal class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
static async Task Main(string[] args)
|
static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
|
// 处理NT6.0及以下System.CommandLine报错CultureNotFound问题
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
var osVersion = Environment.OSVersion.Version;
|
||||||
|
if (osVersion.Major < 6 || osVersion is { Major: 6, Minor: 0 })
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Console.CancelKeyPress += Console_CancelKeyPress;
|
Console.CancelKeyPress += Console_CancelKeyPress;
|
||||||
ServicePointManager.DefaultConnectionLimit = 1024;
|
ServicePointManager.DefaultConnectionLimit = 1024;
|
||||||
try { Console.CursorVisible = true; } catch { }
|
try { Console.CursorVisible = true; } catch { }
|
||||||
|
|
||||||
string loc = "en-US";
|
string loc = ResString.CurrentLoc;
|
||||||
string currLoc = Thread.CurrentThread.CurrentUICulture.Name;
|
string currLoc = Thread.CurrentThread.CurrentUICulture.Name;
|
||||||
if (currLoc == "zh-CN" || currLoc == "zh-SG") loc = "zh-CN";
|
if (currLoc is "zh-CN" or "zh-SG") loc = "zh-CN";
|
||||||
else if (currLoc.StartsWith("zh-")) loc = "zh-TW";
|
else if (currLoc.StartsWith("zh-")) loc = "zh-TW";
|
||||||
|
|
||||||
// 处理用户-h等请求
|
// 处理用户-h等请求
|
||||||
@ -39,10 +49,18 @@ namespace N_m3u8DL_RE
|
|||||||
loc = list[index + 1];
|
loc = list[index + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResString.CurrentLoc = loc;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(loc);
|
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(loc);
|
||||||
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(loc);
|
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(loc);
|
||||||
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(loc);
|
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(loc);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Culture not work on NT6.0, so catch the exception
|
||||||
|
}
|
||||||
|
|
||||||
await CommandInvoker.InvokeArgs(args, DoWorkAsync);
|
await CommandInvoker.InvokeArgs(args, DoWorkAsync);
|
||||||
}
|
}
|
||||||
@ -54,7 +72,7 @@ namespace N_m3u8DL_RE
|
|||||||
{
|
{
|
||||||
Console.CursorVisible = true;
|
Console.CursorVisible = true;
|
||||||
if (!OperatingSystem.IsWindows())
|
if (!OperatingSystem.IsWindows())
|
||||||
System.Diagnostics.Process.Start("stty", "echo");
|
System.Diagnostics.Process.Start("tput", "cnorm");
|
||||||
} catch { }
|
} catch { }
|
||||||
Environment.Exit(0);
|
Environment.Exit(0);
|
||||||
}
|
}
|
||||||
@ -69,7 +87,7 @@ namespace N_m3u8DL_RE
|
|||||||
|
|
||||||
static async Task DoWorkAsync(MyOption option)
|
static async Task DoWorkAsync(MyOption option)
|
||||||
{
|
{
|
||||||
|
HTTPUtil.AppHttpClient.Timeout = TimeSpan.FromSeconds(option.HttpRequestTimeout);
|
||||||
if (Console.IsOutputRedirected || Console.IsErrorRedirected)
|
if (Console.IsOutputRedirected || Console.IsErrorRedirected)
|
||||||
{
|
{
|
||||||
option.ForceAnsiConsole = true;
|
option.ForceAnsiConsole = true;
|
||||||
@ -77,8 +95,10 @@ namespace N_m3u8DL_RE
|
|||||||
Logger.Info(ResString.consoleRedirected);
|
Logger.Info(ResString.consoleRedirected);
|
||||||
}
|
}
|
||||||
CustomAnsiConsole.InitConsole(option.ForceAnsiConsole, option.NoAnsiColor);
|
CustomAnsiConsole.InitConsole(option.ForceAnsiConsole, option.NoAnsiColor);
|
||||||
|
|
||||||
// 检测更新
|
// 检测更新
|
||||||
CheckUpdateAsync();
|
if (!option.DisableUpdateCheck)
|
||||||
|
_ = CheckUpdateAsync();
|
||||||
|
|
||||||
Logger.IsWriteFile = !option.NoLog;
|
Logger.IsWriteFile = !option.NoLog;
|
||||||
Logger.InitLogFile();
|
Logger.InitLogFile();
|
||||||
@ -97,22 +117,25 @@ namespace N_m3u8DL_RE
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查互斥的选项
|
// 检查互斥的选项
|
||||||
|
if (option is { MuxAfterDone: false, MuxImports.Count: > 0 })
|
||||||
if (!option.MuxAfterDone && option.MuxImports != null && option.MuxImports.Count > 0)
|
|
||||||
{
|
{
|
||||||
throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!");
|
throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (option.UseShakaPackager)
|
||||||
|
{
|
||||||
|
option.DecryptionEngine = DecryptEngine.SHAKA_PACKAGER;
|
||||||
|
}
|
||||||
|
|
||||||
// LivePipeMux开启时 LiveRealTimeMerge必须开启
|
// LivePipeMux开启时 LiveRealTimeMerge必须开启
|
||||||
if (option.LivePipeMux && !option.LiveRealTimeMerge)
|
if (option is { LivePipeMux: true, LiveRealTimeMerge: false })
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp("LivePipeMux detected, forced enable LiveRealTimeMerge");
|
Logger.WarnMarkUp("LivePipeMux detected, forced enable LiveRealTimeMerge");
|
||||||
option.LiveRealTimeMerge = true;
|
option.LiveRealTimeMerge = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预先检查ffmpeg
|
// 预先检查ffmpeg
|
||||||
if (option.FFmpegBinaryPath == null)
|
option.FFmpegBinaryPath ??= GlobalUtil.FindExecutable("ffmpeg");
|
||||||
option.FFmpegBinaryPath = GlobalUtil.FindExecutable("ffmpeg");
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(option.FFmpegBinaryPath) || !File.Exists(option.FFmpegBinaryPath))
|
if (string.IsNullOrEmpty(option.FFmpegBinaryPath) || !File.Exists(option.FFmpegBinaryPath))
|
||||||
{
|
{
|
||||||
@ -122,43 +145,49 @@ namespace N_m3u8DL_RE
|
|||||||
Logger.Extra($"ffmpeg => {option.FFmpegBinaryPath}");
|
Logger.Extra($"ffmpeg => {option.FFmpegBinaryPath}");
|
||||||
|
|
||||||
// 预先检查mkvmerge
|
// 预先检查mkvmerge
|
||||||
if (option.MuxOptions != null && option.MuxOptions.UseMkvmerge && option.MuxAfterDone)
|
if (option is { MuxOptions.UseMkvmerge: true, MuxAfterDone: true })
|
||||||
{
|
{
|
||||||
if (option.MkvmergeBinaryPath == null)
|
option.MkvmergeBinaryPath ??= GlobalUtil.FindExecutable("mkvmerge");
|
||||||
option.MkvmergeBinaryPath = GlobalUtil.FindExecutable("mkvmerge");
|
|
||||||
if (string.IsNullOrEmpty(option.MkvmergeBinaryPath) || !File.Exists(option.MkvmergeBinaryPath))
|
if (string.IsNullOrEmpty(option.MkvmergeBinaryPath) || !File.Exists(option.MkvmergeBinaryPath))
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException("mkvmerge not found");
|
throw new FileNotFoundException(ResString.mkvmergeNotFound);
|
||||||
}
|
}
|
||||||
Logger.Extra($"mkvmerge => {option.MkvmergeBinaryPath}");
|
Logger.Extra($"mkvmerge => {option.MkvmergeBinaryPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预先检查
|
// 预先检查
|
||||||
if ((option.Keys != null && option.Keys.Length > 0) || option.KeyTextFile != null)
|
if (option.Keys is { Length: > 0 } || option.KeyTextFile != null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(option.DecryptionBinaryPath))
|
if (!string.IsNullOrEmpty(option.DecryptionBinaryPath) && !File.Exists(option.DecryptionBinaryPath))
|
||||||
{
|
{
|
||||||
if (option.UseShakaPackager)
|
throw new FileNotFoundException(option.DecryptionBinaryPath);
|
||||||
|
}
|
||||||
|
switch (option.DecryptionEngine)
|
||||||
|
{
|
||||||
|
case DecryptEngine.SHAKA_PACKAGER:
|
||||||
{
|
{
|
||||||
var file = GlobalUtil.FindExecutable("shaka-packager");
|
var file = GlobalUtil.FindExecutable("shaka-packager");
|
||||||
var file2 = GlobalUtil.FindExecutable("packager-linux-x64");
|
var file2 = GlobalUtil.FindExecutable("packager-linux-x64");
|
||||||
var file3 = GlobalUtil.FindExecutable("packager-osx-x64");
|
var file3 = GlobalUtil.FindExecutable("packager-osx-x64");
|
||||||
var file4 = GlobalUtil.FindExecutable("packager-win-x64");
|
var file4 = GlobalUtil.FindExecutable("packager-win-x64");
|
||||||
if (file == null && file2 == null && file3 == null && file4 == null) throw new FileNotFoundException("shaka-packager not found!");
|
if (file == null && file2 == null && file3 == null && file4 == null)
|
||||||
|
throw new FileNotFoundException(ResString.shakaPackagerNotFound);
|
||||||
option.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4;
|
option.DecryptionBinaryPath = file ?? file2 ?? file3 ?? file4;
|
||||||
Logger.Extra($"shaka-packager => {option.DecryptionBinaryPath}");
|
Logger.Extra($"shaka-packager => {option.DecryptionBinaryPath}");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
else
|
case DecryptEngine.MP4DECRYPT:
|
||||||
{
|
{
|
||||||
var file = GlobalUtil.FindExecutable("mp4decrypt");
|
var file = GlobalUtil.FindExecutable("mp4decrypt");
|
||||||
if (file == null) throw new FileNotFoundException("mp4decrypt not found!");
|
if (file == null) throw new FileNotFoundException(ResString.mp4decryptNotFound);
|
||||||
option.DecryptionBinaryPath = file;
|
option.DecryptionBinaryPath = file;
|
||||||
Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}");
|
Logger.Extra($"mp4decrypt => {option.DecryptionBinaryPath}");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
case DecryptEngine.FFMPEG:
|
||||||
else if (!File.Exists(option.DecryptionBinaryPath))
|
default:
|
||||||
{
|
option.DecryptionBinaryPath = option.FFmpegBinaryPath;
|
||||||
throw new FileNotFoundException(option.DecryptionBinaryPath);
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,6 +214,11 @@ namespace N_m3u8DL_RE
|
|||||||
CustomeIV = option.CustomHLSIv,
|
CustomeIV = option.CustomHLSIv,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (option.AllowHlsMultiExtMap)
|
||||||
|
{
|
||||||
|
parserConfig.CustomParserArgs.Add("AllowHlsMultiExtMap", "true");
|
||||||
|
}
|
||||||
|
|
||||||
// demo1
|
// demo1
|
||||||
parserConfig.ContentProcessors.Insert(0, new DemoProcessor());
|
parserConfig.ContentProcessors.Insert(0, new DemoProcessor());
|
||||||
// demo2
|
// demo2
|
||||||
@ -217,13 +251,13 @@ namespace N_m3u8DL_RE
|
|||||||
|
|
||||||
|
|
||||||
// 全部媒体
|
// 全部媒体
|
||||||
var lists = streams.OrderBy(p => p.MediaType).ThenByDescending(p => p.Bandwidth).ThenByDescending(GetOrder);
|
var lists = streams.OrderBy(p => p.MediaType).ThenByDescending(p => p.Bandwidth).ThenByDescending(GetOrder).ToList();
|
||||||
// 基本流
|
// 基本流
|
||||||
var basicStreams = lists.Where(x => x.MediaType == null || x.MediaType == MediaType.VIDEO);
|
var basicStreams = lists.Where(x => x.MediaType is null or MediaType.VIDEO).ToList();
|
||||||
// 可选音频轨道
|
// 可选音频轨道
|
||||||
var audios = lists.Where(x => x.MediaType == MediaType.AUDIO);
|
var audios = lists.Where(x => x.MediaType == MediaType.AUDIO).ToList();
|
||||||
// 可选字幕轨道
|
// 可选字幕轨道
|
||||||
var subs = lists.Where(x => x.MediaType == MediaType.SUBTITLES);
|
var subs = lists.Where(x => x.MediaType == MediaType.SUBTITLES).ToList();
|
||||||
|
|
||||||
// 尝试从URL或文件读取文件名
|
// 尝试从URL或文件读取文件名
|
||||||
if (string.IsNullOrEmpty(option.SaveName))
|
if (string.IsNullOrEmpty(option.SaveName))
|
||||||
@ -238,7 +272,7 @@ namespace N_m3u8DL_RE
|
|||||||
// 写出文件
|
// 写出文件
|
||||||
await WriteRawFilesAsync(option, extractor, tmpDir);
|
await WriteRawFilesAsync(option, extractor, tmpDir);
|
||||||
|
|
||||||
Logger.Info(ResString.streamsInfo, lists.Count(), basicStreams.Count(), audios.Count(), subs.Count());
|
Logger.Info(ResString.streamsInfo, lists.Count, basicStreams.Count, audios.Count, subs.Count);
|
||||||
|
|
||||||
foreach (var item in lists)
|
foreach (var item in lists)
|
||||||
{
|
{
|
||||||
@ -251,7 +285,7 @@ namespace N_m3u8DL_RE
|
|||||||
basicStreams = FilterUtil.DoFilterDrop(basicStreams, option.DropVideoFilter);
|
basicStreams = FilterUtil.DoFilterDrop(basicStreams, option.DropVideoFilter);
|
||||||
audios = FilterUtil.DoFilterDrop(audios, option.DropAudioFilter);
|
audios = FilterUtil.DoFilterDrop(audios, option.DropAudioFilter);
|
||||||
subs = FilterUtil.DoFilterDrop(subs, option.DropSubtitleFilter);
|
subs = FilterUtil.DoFilterDrop(subs, option.DropSubtitleFilter);
|
||||||
lists = basicStreams.Concat(audios).Concat(subs).OrderBy(x => true);
|
lists = basicStreams.Concat(audios).Concat(subs).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (option.DropVideoFilter != null) Logger.Extra($"DropVideoFilter => {option.DropVideoFilter}");
|
if (option.DropVideoFilter != null) Logger.Extra($"DropVideoFilter => {option.DropVideoFilter}");
|
||||||
@ -263,7 +297,7 @@ namespace N_m3u8DL_RE
|
|||||||
|
|
||||||
if (option.AutoSelect)
|
if (option.AutoSelect)
|
||||||
{
|
{
|
||||||
if (basicStreams.Any())
|
if (basicStreams.Count != 0)
|
||||||
selectedStreams.Add(basicStreams.First());
|
selectedStreams.Add(basicStreams.First());
|
||||||
var langs = audios.DistinctBy(a => a.Language).Select(a => a.Language);
|
var langs = audios.DistinctBy(a => a.Language).Select(a => a.Language);
|
||||||
foreach (var lang in langs)
|
foreach (var lang in langs)
|
||||||
@ -289,7 +323,7 @@ namespace N_m3u8DL_RE
|
|||||||
selectedStreams = FilterUtil.SelectStreams(lists);
|
selectedStreams = FilterUtil.SelectStreams(lists);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedStreams.Any())
|
if (selectedStreams.Count == 0)
|
||||||
throw new Exception(ResString.noStreamsToDownload);
|
throw new Exception(ResString.noStreamsToDownload);
|
||||||
|
|
||||||
// HLS: 选中流中若有没加载出playlist的,加载playlist
|
// HLS: 选中流中若有没加载出playlist的,加载playlist
|
||||||
@ -305,7 +339,7 @@ namespace N_m3u8DL_RE
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 无法识别的加密方式,自动开启二进制合并
|
// 无法识别的加密方式,自动开启二进制合并
|
||||||
if (selectedStreams.Any(s => s.Playlist.MediaParts.Any(p => p.MediaSegments.Any(m => m.EncryptInfo.Method == EncryptMethod.UNKNOWN))))
|
if (selectedStreams.Any(s => s.Playlist!.MediaParts.Any(p => p.MediaSegments.Any(m => m.EncryptInfo.Method == EncryptMethod.UNKNOWN))))
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge3}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge3}[/]");
|
||||||
option.BinaryMerge = true;
|
option.BinaryMerge = true;
|
||||||
@ -343,7 +377,7 @@ namespace N_m3u8DL_RE
|
|||||||
Logger.InfoMarkUp(ResString.saveName + $"[deepskyblue1]{option.SaveName.EscapeMarkup()}[/]");
|
Logger.InfoMarkUp(ResString.saveName + $"[deepskyblue1]{option.SaveName.EscapeMarkup()}[/]");
|
||||||
|
|
||||||
// 开始MuxAfterDone后自动使用二进制版
|
// 开始MuxAfterDone后自动使用二进制版
|
||||||
if (!option.BinaryMerge && option.MuxAfterDone)
|
if (option is { BinaryMerge: false, MuxAfterDone: true })
|
||||||
{
|
{
|
||||||
option.BinaryMerge = true;
|
option.BinaryMerge = true;
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge6}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.autoBinaryMerge6}[/]");
|
||||||
@ -426,27 +460,23 @@ namespace N_m3u8DL_RE
|
|||||||
static async Task<string> Get302Async(string url)
|
static async Task<string> Get302Async(string url)
|
||||||
{
|
{
|
||||||
// this allows you to set the settings so that we can get the redirect url
|
// this allows you to set the settings so that we can get the redirect url
|
||||||
var handler = new HttpClientHandler()
|
var handler = new HttpClientHandler
|
||||||
{
|
{
|
||||||
AllowAutoRedirect = false
|
AllowAutoRedirect = false
|
||||||
};
|
};
|
||||||
string redirectedUrl = "";
|
var redirectedUrl = "";
|
||||||
using (HttpClient client = new(handler))
|
using var client = new HttpClient(handler);
|
||||||
using (HttpResponseMessage response = await client.GetAsync(url))
|
using var response = await client.GetAsync(url);
|
||||||
using (HttpContent content = response.Content)
|
using var content = response.Content;
|
||||||
{
|
|
||||||
// ... Read the response to see if we have the redirected url
|
// ... Read the response to see if we have the redirected url
|
||||||
if (response.StatusCode == System.Net.HttpStatusCode.Found)
|
if (response.StatusCode != HttpStatusCode.Found) return redirectedUrl;
|
||||||
{
|
|
||||||
HttpResponseHeaders headers = response.Headers;
|
var headers = response.Headers;
|
||||||
if (headers != null && headers.Location != null)
|
if (headers.Location != null)
|
||||||
{
|
{
|
||||||
redirectedUrl = headers.Location.AbsoluteUri;
|
redirectedUrl = headers.Location.AbsoluteUri;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirectedUrl;
|
return redirectedUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
@ -2,13 +2,11 @@
|
|||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util
|
namespace N_m3u8DL_RE.Util;
|
||||||
{
|
|
||||||
internal class DownloadUtil
|
internal static class DownloadUtil
|
||||||
{
|
{
|
||||||
private static readonly HttpClient AppHttpClient = HTTPUtil.AppHttpClient;
|
private static readonly HttpClient AppHttpClient = HTTPUtil.AppHttpClient;
|
||||||
|
|
||||||
@ -26,8 +24,8 @@ namespace N_m3u8DL_RE.Util
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var buffer = new byte[expect];
|
var buffer = new byte[expect];
|
||||||
await inputStream.ReadAsync(buffer);
|
_ = await inputStream.ReadAsync(buffer);
|
||||||
await outputStream.WriteAsync(buffer, 0, buffer.Length);
|
await outputStream.WriteAsync(buffer);
|
||||||
speedContainer.Add(buffer.Length);
|
speedContainer.Add(buffer.Length);
|
||||||
}
|
}
|
||||||
return new DownloadResult()
|
return new DownloadResult()
|
||||||
@ -83,7 +81,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
{
|
{
|
||||||
HttpResponseHeaders respHeaders = response.Headers;
|
HttpResponseHeaders respHeaders = response.Headers;
|
||||||
Logger.Debug(respHeaders.ToString());
|
Logger.Debug(respHeaders.ToString());
|
||||||
if (respHeaders != null && respHeaders.Location != null)
|
if (respHeaders.Location != null)
|
||||||
{
|
{
|
||||||
var redirectedUrl = "";
|
var redirectedUrl = "";
|
||||||
if (!respHeaders.Location.IsAbsoluteUri)
|
if (!respHeaders.Location.IsAbsoluteUri)
|
||||||
@ -110,7 +108,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
|
|
||||||
size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token);
|
size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token);
|
||||||
speedContainer.Add(size);
|
speedContainer.Add(size);
|
||||||
await stream.WriteAsync(buffer, 0, size);
|
await stream.WriteAsync(buffer.AsMemory(0, size));
|
||||||
// 检测imageHeader
|
// 检测imageHeader
|
||||||
bool imageHeader = ImageHeaderUtil.IsImageHeader(buffer);
|
bool imageHeader = ImageHeaderUtil.IsImageHeader(buffer);
|
||||||
// 检测GZip(For DDP Audio)
|
// 检测GZip(For DDP Audio)
|
||||||
@ -119,7 +117,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
while ((size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token)) > 0)
|
while ((size = await responseStream.ReadAsync(buffer, cancellationTokenSource.Token)) > 0)
|
||||||
{
|
{
|
||||||
speedContainer.Add(size);
|
speedContainer.Add(size);
|
||||||
await stream.WriteAsync(buffer, 0, size);
|
await stream.WriteAsync(buffer.AsMemory(0, size));
|
||||||
// 限速策略
|
// 限速策略
|
||||||
while (speedContainer.Downloaded > speedContainer.SpeedLimit)
|
while (speedContainer.Downloaded > speedContainer.SpeedLimit)
|
||||||
{
|
{
|
||||||
@ -143,4 +141,3 @@ namespace N_m3u8DL_RE.Util
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -4,20 +4,15 @@ using N_m3u8DL_RE.Common.Log;
|
|||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util
|
namespace N_m3u8DL_RE.Util;
|
||||||
{
|
|
||||||
public class FilterUtil
|
public static class FilterUtil
|
||||||
{
|
{
|
||||||
public static List<StreamSpec> DoFilterKeep(IEnumerable<StreamSpec> lists, StreamFilter? filter)
|
public static List<StreamSpec> DoFilterKeep(IEnumerable<StreamSpec> lists, StreamFilter? filter)
|
||||||
{
|
{
|
||||||
if (filter == null) return new List<StreamSpec>();
|
if (filter == null) return [];
|
||||||
|
|
||||||
var inputs = lists.Where(_ => true);
|
var inputs = lists.Where(_ => true);
|
||||||
if (filter.GroupIdReg != null)
|
if (filter.GroupIdReg != null)
|
||||||
@ -56,13 +51,13 @@ namespace N_m3u8DL_RE.Util
|
|||||||
var bestNumberStr = filter.For.Replace("best", "");
|
var bestNumberStr = filter.For.Replace("best", "");
|
||||||
var worstNumberStr = filter.For.Replace("worst", "");
|
var worstNumberStr = filter.For.Replace("worst", "");
|
||||||
|
|
||||||
if (filter.For == "best" && inputs.Count() > 0)
|
if (filter.For == "best" && inputs.Any())
|
||||||
inputs = inputs.Take(1).ToList();
|
inputs = inputs.Take(1).ToList();
|
||||||
else if (filter.For == "worst" && inputs.Count() > 0)
|
else if (filter.For == "worst" && inputs.Any())
|
||||||
inputs = inputs.TakeLast(1).ToList();
|
inputs = inputs.TakeLast(1).ToList();
|
||||||
else if (int.TryParse(bestNumberStr, out int bestNumber) && inputs.Count() > 0)
|
else if (int.TryParse(bestNumberStr, out int bestNumber) && inputs.Any())
|
||||||
inputs = inputs.Take(bestNumber).ToList();
|
inputs = inputs.Take(bestNumber).ToList();
|
||||||
else if (int.TryParse(worstNumberStr, out int worstNumber) && inputs.Count() > 0)
|
else if (int.TryParse(worstNumberStr, out int worstNumber) && inputs.Any())
|
||||||
inputs = inputs.TakeLast(worstNumber).ToList();
|
inputs = inputs.TakeLast(worstNumber).ToList();
|
||||||
|
|
||||||
return inputs.ToList();
|
return inputs.ToList();
|
||||||
@ -70,7 +65,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
|
|
||||||
public static List<StreamSpec> DoFilterDrop(IEnumerable<StreamSpec> lists, StreamFilter? filter)
|
public static List<StreamSpec> DoFilterDrop(IEnumerable<StreamSpec> lists, StreamFilter? filter)
|
||||||
{
|
{
|
||||||
if (filter == null) return new List<StreamSpec>(lists);
|
if (filter == null) return [..lists];
|
||||||
|
|
||||||
var inputs = lists.Where(_ => true);
|
var inputs = lists.Where(_ => true);
|
||||||
var selected = DoFilterKeep(lists, filter);
|
var selected = DoFilterKeep(lists, filter);
|
||||||
@ -82,23 +77,23 @@ namespace N_m3u8DL_RE.Util
|
|||||||
|
|
||||||
public static List<StreamSpec> SelectStreams(IEnumerable<StreamSpec> lists)
|
public static List<StreamSpec> SelectStreams(IEnumerable<StreamSpec> lists)
|
||||||
{
|
{
|
||||||
if (lists.Count() == 1)
|
var streamSpecs = lists.ToList();
|
||||||
return new List<StreamSpec>(lists);
|
if (streamSpecs.Count == 1)
|
||||||
|
return [..streamSpecs];
|
||||||
|
|
||||||
// 基本流
|
// 基本流
|
||||||
var basicStreams = lists.Where(x => x.MediaType == null);
|
var basicStreams = streamSpecs.Where(x => x.MediaType == null).ToList();
|
||||||
// 可选音频轨道
|
// 可选音频轨道
|
||||||
var audios = lists.Where(x => x.MediaType == MediaType.AUDIO);
|
var audios = streamSpecs.Where(x => x.MediaType == MediaType.AUDIO).ToList();
|
||||||
// 可选字幕轨道
|
// 可选字幕轨道
|
||||||
var subs = lists.Where(x => x.MediaType == MediaType.SUBTITLES);
|
var subs = streamSpecs.Where(x => x.MediaType == MediaType.SUBTITLES).ToList();
|
||||||
|
|
||||||
var prompt = new MultiSelectionPrompt<StreamSpec>()
|
var prompt = new MultiSelectionPrompt<StreamSpec>()
|
||||||
.Title(ResString.promptTitle)
|
.Title(ResString.promptTitle)
|
||||||
.UseConverter(x =>
|
.UseConverter(x =>
|
||||||
{
|
{
|
||||||
if (x.Name != null && x.Name.StartsWith("__"))
|
if (x.Name != null && x.Name.StartsWith("__"))
|
||||||
return $"[darkslategray1]{x.Name.Substring(2)}[/]";
|
return $"[darkslategray1]{x.Name[2..]}[/]";
|
||||||
else
|
|
||||||
return x.ToString().EscapeMarkup().RemoveMarkup();
|
return x.ToString().EscapeMarkup().RemoveMarkup();
|
||||||
})
|
})
|
||||||
.Required()
|
.Required()
|
||||||
@ -108,15 +103,15 @@ namespace N_m3u8DL_RE.Util
|
|||||||
;
|
;
|
||||||
|
|
||||||
// 默认选中第一个
|
// 默认选中第一个
|
||||||
var first = lists.First();
|
var first = streamSpecs.First();
|
||||||
prompt.Select(first);
|
prompt.Select(first);
|
||||||
|
|
||||||
if (basicStreams.Any())
|
if (basicStreams.Count != 0)
|
||||||
{
|
{
|
||||||
prompt.AddChoiceGroup(new StreamSpec() { Name = "__Basic" }, basicStreams);
|
prompt.AddChoiceGroup(new StreamSpec() { Name = "__Basic" }, basicStreams);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audios.Any())
|
if (audios.Count != 0)
|
||||||
{
|
{
|
||||||
prompt.AddChoiceGroup(new StreamSpec() { Name = "__Audio" }, audios);
|
prompt.AddChoiceGroup(new StreamSpec() { Name = "__Audio" }, audios);
|
||||||
// 默认音轨
|
// 默认音轨
|
||||||
@ -125,7 +120,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
prompt.Select(audios.First(a => a.GroupId == first.AudioId));
|
prompt.Select(audios.First(a => a.GroupId == first.AudioId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (subs.Any())
|
if (subs.Count != 0)
|
||||||
{
|
{
|
||||||
prompt.AddChoiceGroup(new StreamSpec() { Name = "__Subtitle" }, subs);
|
prompt.AddChoiceGroup(new StreamSpec() { Name = "__Subtitle" }, subs);
|
||||||
// 默认字幕轨
|
// 默认字幕轨
|
||||||
@ -147,7 +142,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 直播使用。对齐各个轨道的起始。
|
/// 直播使用。对齐各个轨道的起始。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="streams"></param>
|
/// <param name="selectedSteams"></param>
|
||||||
/// <param name="takeLastCount"></param>
|
/// <param name="takeLastCount"></param>
|
||||||
public static void SyncStreams(List<StreamSpec> selectedSteams, int takeLastCount = 15)
|
public static void SyncStreams(List<StreamSpec> selectedSteams, int takeLastCount = 15)
|
||||||
{
|
{
|
||||||
@ -198,17 +193,15 @@ namespace N_m3u8DL_RE.Util
|
|||||||
/// <param name="customRange"></param>
|
/// <param name="customRange"></param>
|
||||||
public static void ApplyCustomRange(List<StreamSpec> selectedSteams, CustomRange? customRange)
|
public static void ApplyCustomRange(List<StreamSpec> selectedSteams, CustomRange? customRange)
|
||||||
{
|
{
|
||||||
var resultList = selectedSteams.Select(x => 0d).ToList();
|
|
||||||
|
|
||||||
if (customRange == null) return;
|
if (customRange == null) return;
|
||||||
|
|
||||||
Logger.InfoMarkUp($"{ResString.customRangeFound}[Cyan underline]{customRange.InputStr}[/]");
|
Logger.InfoMarkUp($"{ResString.customRangeFound}[Cyan underline]{customRange.InputStr}[/]");
|
||||||
Logger.WarnMarkUp($"[darkorange3_1]{ResString.customRangeWarn}[/]");
|
Logger.WarnMarkUp($"[darkorange3_1]{ResString.customRangeWarn}[/]");
|
||||||
|
|
||||||
var filteByIndex = customRange.StartSegIndex != null && customRange.EndSegIndex != null;
|
var filterByIndex = customRange is { StartSegIndex: not null, EndSegIndex: not null };
|
||||||
var filteByTime = customRange.StartSec != null && customRange.EndSec != null;
|
var filterByTime = customRange is { StartSec: not null, EndSec: not null };
|
||||||
|
|
||||||
if (!filteByIndex && !filteByTime)
|
if (!filterByIndex && !filterByTime)
|
||||||
{
|
{
|
||||||
Logger.ErrorMarkUp(ResString.customRangeInvalid);
|
Logger.ErrorMarkUp(ResString.customRangeInvalid);
|
||||||
return;
|
return;
|
||||||
@ -220,8 +213,8 @@ namespace N_m3u8DL_RE.Util
|
|||||||
if (stream.Playlist == null) continue;
|
if (stream.Playlist == null) continue;
|
||||||
foreach (var part in stream.Playlist.MediaParts)
|
foreach (var part in stream.Playlist.MediaParts)
|
||||||
{
|
{
|
||||||
var newSegments = new List<MediaSegment>();
|
List<MediaSegment> newSegments;
|
||||||
if (filteByIndex)
|
if (filterByIndex)
|
||||||
newSegments = part.MediaSegments.Where(seg => seg.Index >= customRange.StartSegIndex && seg.Index <= customRange.EndSegIndex).ToList();
|
newSegments = part.MediaSegments.Where(seg => seg.Index >= customRange.StartSegIndex && seg.Index <= customRange.EndSegIndex).ToList();
|
||||||
else
|
else
|
||||||
newSegments = part.MediaSegments.Where(seg => stream.Playlist.MediaParts.SelectMany(p => p.MediaSegments).Where(x => x.Index < seg.Index).Sum(x => x.Duration) >= customRange.StartSec
|
newSegments = part.MediaSegments.Where(seg => stream.Playlist.MediaParts.SelectMany(p => p.MediaSegments).Where(x => x.Index < seg.Index).Sum(x => x.Duration) >= customRange.StartSec
|
||||||
@ -239,11 +232,11 @@ namespace N_m3u8DL_RE.Util
|
|||||||
/// 根据用户输入,清除广告分片
|
/// 根据用户输入,清除广告分片
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="selectedSteams"></param>
|
/// <param name="selectedSteams"></param>
|
||||||
/// <param name="customRange"></param>
|
/// <param name="keywords"></param>
|
||||||
public static void CleanAd(List<StreamSpec> selectedSteams, string[]? keywords)
|
public static void CleanAd(List<StreamSpec> selectedSteams, string[]? keywords)
|
||||||
{
|
{
|
||||||
if (keywords == null) return;
|
if (keywords == null) return;
|
||||||
var regList = keywords.Select(s => new Regex(s));
|
var regList = keywords.Select(s => new Regex(s)).ToList();
|
||||||
foreach ( var reg in regList)
|
foreach ( var reg in regList)
|
||||||
{
|
{
|
||||||
Logger.InfoMarkUp($"{ResString.customAdKeywordsFound}[Cyan underline]{reg}[/]");
|
Logger.InfoMarkUp($"{ResString.customAdKeywordsFound}[Cyan underline]{reg}[/]");
|
||||||
@ -263,11 +256,8 @@ namespace N_m3u8DL_RE.Util
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// 找到广告分片 清理
|
// 找到广告分片 清理
|
||||||
else
|
|
||||||
{
|
|
||||||
part.MediaSegments = part.MediaSegments.Where(x => regList.All(reg => !reg.IsMatch(x.Url))).ToList();
|
part.MediaSegments = part.MediaSegments.Where(x => regList.All(reg => !reg.IsMatch(x.Url))).ToList();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 清理已经为空的 part
|
// 清理已经为空的 part
|
||||||
stream.Playlist.MediaParts = stream.Playlist.MediaParts.Where(x => x.MediaSegments.Count > 0).ToList();
|
stream.Playlist.MediaParts = stream.Playlist.MediaParts.Where(x => x.MediaSegments.Count > 0).ToList();
|
||||||
@ -281,4 +271,3 @@ namespace N_m3u8DL_RE.Util
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
using System;
|
namespace N_m3u8DL_RE.Util;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util
|
internal static class ImageHeaderUtil
|
||||||
{
|
|
||||||
internal class ImageHeaderUtil
|
|
||||||
{
|
{
|
||||||
public static bool IsImageHeader(byte[] bArr)
|
public static bool IsImageHeader(byte[] bArr)
|
||||||
{
|
{
|
||||||
@ -16,13 +9,13 @@ namespace N_m3u8DL_RE.Util
|
|||||||
if (size > 3 && 137 == bArr[0] && 80 == bArr[1] && 78 == bArr[2] && 71 == bArr[3])
|
if (size > 3 && 137 == bArr[0] && 80 == bArr[1] && 78 == bArr[2] && 71 == bArr[3])
|
||||||
return true;
|
return true;
|
||||||
// GIF HEADER检测
|
// GIF HEADER检测
|
||||||
else if (size > 3 && 0x47 == bArr[0] && 0x49 == bArr[1] && 0x46 == bArr[2] && 0x38 == bArr[3])
|
if (size > 3 && 0x47 == bArr[0] && 0x49 == bArr[1] && 0x46 == bArr[2] && 0x38 == bArr[3])
|
||||||
return true;
|
return true;
|
||||||
// BMP HEADER检测
|
// BMP HEADER检测
|
||||||
else if (size > 10 && 0x42 == bArr[0] && 0x4D == bArr[1] && 0x00 == bArr[5] && 0x00 == bArr[6] && 0x00 == bArr[7] && 0x00 == bArr[8])
|
if (size > 10 && 0x42 == bArr[0] && 0x4D == bArr[1] && 0x00 == bArr[5] && 0x00 == bArr[6] && 0x00 == bArr[7] && 0x00 == bArr[8])
|
||||||
return true;
|
return true;
|
||||||
// JPEG HEADER检测
|
// JPEG HEADER检测
|
||||||
else if (size > 3 && 0xFF == bArr[0] && 0xD8 == bArr[1] && 0xFF == bArr[2])
|
if (size > 3 && 0xFF == bArr[0] && 0xD8 == bArr[1] && 0xFF == bArr[2])
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -86,4 +79,3 @@ namespace N_m3u8DL_RE.Util
|
|||||||
await File.WriteAllBytesAsync(sourcePath, sourceData);
|
await File.WriteAllBytesAsync(sourcePath, sourceData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,34 +1,20 @@
|
|||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util
|
namespace N_m3u8DL_RE.Util;
|
||||||
{
|
|
||||||
class Language
|
|
||||||
{
|
|
||||||
public string Code;
|
|
||||||
public string ExtendCode;
|
|
||||||
public string Description;
|
|
||||||
public string DescriptionAudio;
|
|
||||||
|
|
||||||
public Language(string extendCode, string code, string desc, string descA)
|
internal class Language(string extendCode, string code, string desc, string descA)
|
||||||
{
|
{
|
||||||
Code = code;
|
public readonly string Code = code;
|
||||||
ExtendCode = extendCode;
|
public readonly string ExtendCode = extendCode;
|
||||||
Description = desc;
|
public readonly string Description = desc;
|
||||||
DescriptionAudio = descA;
|
public readonly string DescriptionAudio = descA;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class LanguageCodeUtil
|
internal static class LanguageCodeUtil
|
||||||
{
|
{
|
||||||
private LanguageCodeUtil() { }
|
|
||||||
|
|
||||||
private readonly static List<Language> ALL_LANGS = @"
|
private static readonly List<Language> ALL_LANGS = @"
|
||||||
|
default;und;default;default
|
||||||
af;afr;Afrikaans;Afrikaans
|
af;afr;Afrikaans;Afrikaans
|
||||||
af-ZA;afr;Afrikaans (South Africa);Afrikaans (South Africa)
|
af-ZA;afr;Afrikaans (South Africa);Afrikaans (South Africa)
|
||||||
am;amh;Amharic;Amharic
|
am;amh;Amharic;Amharic
|
||||||
@ -390,8 +376,8 @@ MA;msa;Melayu;Melayu
|
|||||||
"
|
"
|
||||||
.Trim().Replace("\r", "").Split('\n').Where(x => !string.IsNullOrWhiteSpace(x)).Select(x =>
|
.Trim().Replace("\r", "").Split('\n').Where(x => !string.IsNullOrWhiteSpace(x)).Select(x =>
|
||||||
{
|
{
|
||||||
var arr = x.Trim().Split(';');
|
var arr = x.Trim().Split(';', StringSplitOptions.TrimEntries);
|
||||||
return new Language(arr[0].Trim(), arr[1].Trim(), arr[2].Trim(), arr[3].Trim());
|
return new Language(arr[0], arr[1], arr[2], arr[3]);
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
private static Dictionary<string, string> CODE_MAP = @"
|
private static Dictionary<string, string> CODE_MAP = @"
|
||||||
@ -505,8 +491,7 @@ sr;srp
|
|||||||
|
|
||||||
private static string ConvertTwoToThree(string input)
|
private static string ConvertTwoToThree(string input)
|
||||||
{
|
{
|
||||||
if (CODE_MAP.TryGetValue(input, out var code)) return code;
|
return CODE_MAP.GetValueOrDefault(input, input);
|
||||||
return input;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -535,7 +520,7 @@ sr;srp
|
|||||||
if (string.IsNullOrEmpty(outputFile.Description))
|
if (string.IsNullOrEmpty(outputFile.Description))
|
||||||
outputFile.Description = outputFile.MediaType == Common.Enum.MediaType.SUBTITLES ? lang.Description : lang.DescriptionAudio;
|
outputFile.Description = outputFile.MediaType == Common.Enum.MediaType.SUBTITLES ? lang.Description : lang.DescriptionAudio;
|
||||||
}
|
}
|
||||||
else if (outputFile.LangCode == null)
|
else
|
||||||
{
|
{
|
||||||
outputFile.LangCode = "und"; // 无法识别直接置为und
|
outputFile.LangCode = "und"; // 无法识别直接置为und
|
||||||
}
|
}
|
||||||
@ -544,4 +529,3 @@ sr;srp
|
|||||||
if (string.IsNullOrEmpty(outputFile.Description)) outputFile.Description = originalLangCode;
|
if (string.IsNullOrEmpty(outputFile.Description)) outputFile.Description = originalLangCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,31 +1,23 @@
|
|||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Util;
|
using N_m3u8DL_RE.Common.Util;
|
||||||
using NiL.JS.Expressions;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util
|
namespace N_m3u8DL_RE.Util;
|
||||||
{
|
|
||||||
internal class LargeSingleFileSplitUtil
|
internal static class LargeSingleFileSplitUtil
|
||||||
{
|
{
|
||||||
class Clip
|
class Clip
|
||||||
{
|
{
|
||||||
public required int index;
|
public required int Index;
|
||||||
public required long from;
|
public required long From;
|
||||||
public required long to;
|
public required long To;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// URL大文件切片处理
|
/// URL大文件切片处理
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="url"></param>
|
/// <param name="segment"></param>
|
||||||
/// <param name="headers"></param>
|
/// <param name="headers"></param>
|
||||||
/// <param name="splitSegments"></param>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static async Task<List<MediaSegment>?> SplitUrlAsync(MediaSegment segment, Dictionary<string,string> headers)
|
public static async Task<List<MediaSegment>?> SplitUrlAsync(MediaSegment segment, Dictionary<string,string> headers)
|
||||||
{
|
{
|
||||||
@ -43,10 +35,10 @@ namespace N_m3u8DL_RE.Util
|
|||||||
{
|
{
|
||||||
splitSegments.Add(new MediaSegment()
|
splitSegments.Add(new MediaSegment()
|
||||||
{
|
{
|
||||||
Index = clip.index,
|
Index = clip.Index,
|
||||||
Url = url,
|
Url = url,
|
||||||
StartRange = clip.from,
|
StartRange = clip.From,
|
||||||
ExpectLength = clip.to == -1 ? null : clip.to - clip.from + 1,
|
ExpectLength = clip.To == -1 ? null : clip.To - clip.From + 1,
|
||||||
EncryptInfo = segment.EncryptInfo,
|
EncryptInfo = segment.EncryptInfo,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -88,7 +80,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
// 此函数主要是切片下载逻辑
|
// 此函数主要是切片下载逻辑
|
||||||
private static List<Clip> GetAllClips(string url, long fileSize)
|
private static List<Clip> GetAllClips(string url, long fileSize)
|
||||||
{
|
{
|
||||||
List<Clip> clips = new();
|
List<Clip> clips = [];
|
||||||
int index = 0;
|
int index = 0;
|
||||||
long counter = 0;
|
long counter = 0;
|
||||||
int perSize = 10 * 1024 * 1024;
|
int perSize = 10 * 1024 * 1024;
|
||||||
@ -96,9 +88,9 @@ namespace N_m3u8DL_RE.Util
|
|||||||
{
|
{
|
||||||
Clip c = new()
|
Clip c = new()
|
||||||
{
|
{
|
||||||
index = index,
|
Index = index,
|
||||||
from = counter,
|
From = counter,
|
||||||
to = counter + perSize
|
To = counter + perSize
|
||||||
};
|
};
|
||||||
// 没到最后
|
// 没到最后
|
||||||
if (fileSize - perSize > 0)
|
if (fileSize - perSize > 0)
|
||||||
@ -111,7 +103,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
// 已到最后
|
// 已到最后
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
c.to = -1;
|
c.To = -1;
|
||||||
clips.Add(c);
|
clips.Add(c);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -119,4 +111,3 @@ namespace N_m3u8DL_RE.Util
|
|||||||
return clips;
|
return clips;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
using Mp4SubtitleParser;
|
using Mp4SubtitleParser;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using N_m3u8DL_RE.Config;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using N_m3u8DL_RE.Enum;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util
|
namespace N_m3u8DL_RE.Util;
|
||||||
|
|
||||||
|
internal static partial class MP4DecryptUtil
|
||||||
{
|
{
|
||||||
internal class MP4DecryptUtil
|
private static readonly string ZeroKid = "00000000000000000000000000000000";
|
||||||
{
|
public static async Task<bool> DecryptAsync(DecryptEngine decryptEngine, string bin, string[]? keys, string source, string dest, string? kid, string init = "", bool isMultiDRM=false)
|
||||||
private static string ZeroKid = "00000000000000000000000000000000";
|
|
||||||
public static async Task<bool> DecryptAsync(bool shakaPackager, string bin, string[]? keys, string source, string dest, string? kid, string init = "", bool isMultiDRM=false)
|
|
||||||
{
|
{
|
||||||
if (keys == null || keys.Length == 0) return false;
|
if (keys == null || keys.Length == 0) return false;
|
||||||
|
|
||||||
|
var keyPairs = keys.ToList();
|
||||||
string? keyPair = null;
|
string? keyPair = null;
|
||||||
string? trackId = null;
|
string? trackId = null;
|
||||||
|
string? tmpEncFile = null;
|
||||||
|
string? tmpDecFile = null;
|
||||||
|
string? workDir = null;
|
||||||
|
|
||||||
if (isMultiDRM)
|
if (isMultiDRM)
|
||||||
{
|
{
|
||||||
@ -24,80 +28,117 @@ namespace N_m3u8DL_RE.Util
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(kid))
|
if (!string.IsNullOrEmpty(kid))
|
||||||
{
|
{
|
||||||
var test = keys.Where(k => k.StartsWith(kid));
|
var test = keyPairs.Where(k => k.StartsWith(kid)).ToList();
|
||||||
if (test.Any()) keyPair = test.First();
|
if (test.Count != 0) keyPair = test.First();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apple
|
// Apple
|
||||||
if (kid == ZeroKid)
|
if (kid == ZeroKid)
|
||||||
{
|
{
|
||||||
keyPair = keys.First();
|
keyPair = keyPairs.First();
|
||||||
trackId = "1";
|
trackId = "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// user only input key, append kid
|
||||||
|
if (keyPair == null && keyPairs.Count == 1 && !keyPairs.First().Contains(':'))
|
||||||
|
{
|
||||||
|
keyPairs = keyPairs.Select(x => $"{kid}:{x}").ToList();
|
||||||
|
keyPair = keyPairs.First();
|
||||||
|
}
|
||||||
|
|
||||||
if (keyPair == null) return false;
|
if (keyPair == null) return false;
|
||||||
|
|
||||||
//shakaPackager 无法单独解密init文件
|
// shakaPackager/ffmpeg 无法单独解密init文件
|
||||||
if (source.EndsWith("_init.mp4") && shakaPackager) return false;
|
if (source.EndsWith("_init.mp4") && decryptEngine != DecryptEngine.MP4DECRYPT) return false;
|
||||||
|
|
||||||
var cmd = "";
|
string cmd;
|
||||||
|
|
||||||
var tmpFile = "";
|
var tmpFile = "";
|
||||||
if (shakaPackager)
|
if (decryptEngine == DecryptEngine.SHAKA_PACKAGER)
|
||||||
{
|
{
|
||||||
var enc = source;
|
var enc = source;
|
||||||
// shakaPackager 手动构造文件
|
// shakaPackager 手动构造文件
|
||||||
if (init != "")
|
if (init != "")
|
||||||
{
|
{
|
||||||
tmpFile = Path.ChangeExtension(source, ".itmp");
|
tmpFile = Path.ChangeExtension(source, ".itmp");
|
||||||
MergeUtil.CombineMultipleFilesIntoSingleFile(new string[] { init, source }, tmpFile);
|
MergeUtil.CombineMultipleFilesIntoSingleFile([init, source], tmpFile);
|
||||||
enc = tmpFile;
|
enc = tmpFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = $"--quiet --enable_raw_key_decryption input=\"{enc}\",stream=0,output=\"{dest}\" " +
|
cmd = $"--quiet --enable_raw_key_decryption input=\"{enc}\",stream=0,output=\"{dest}\" " +
|
||||||
$"--keys {(trackId != null ? $"label={trackId}:" : "")}key_id={(trackId != null ? ZeroKid : kid)}:key={keyPair.Split(':')[1]}";
|
$"--keys {(trackId != null ? $"label={trackId}:" : "")}key_id={(trackId != null ? ZeroKid : kid)}:key={keyPair.Split(':')[1]}";
|
||||||
}
|
}
|
||||||
else
|
else if (decryptEngine == DecryptEngine.MP4DECRYPT)
|
||||||
{
|
{
|
||||||
if (trackId == null)
|
if (trackId == null)
|
||||||
{
|
{
|
||||||
cmd = string.Join(" ", keys.Select(k => $"--key {k}"));
|
cmd = string.Join(" ", keyPairs.Select(k => $"--key {k}"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cmd = string.Join(" ", keys.Select(k => $"--key {trackId}:{k.Split(':')[1]}"));
|
cmd = string.Join(" ", keyPairs.Select(k => $"--key {trackId}:{k.Split(':')[1]}"));
|
||||||
}
|
}
|
||||||
|
// 解决mp4decrypt中文问题 切换到源文件所在目录并改名再解密
|
||||||
|
workDir = Path.GetDirectoryName(source)!;
|
||||||
|
tmpEncFile = Path.Combine(workDir, $"{Guid.NewGuid()}{Path.GetExtension(source)}");
|
||||||
|
tmpDecFile = Path.Combine(workDir, $"{Path.GetFileNameWithoutExtension(tmpEncFile)}_dec{Path.GetExtension(tmpEncFile)}");
|
||||||
|
File.Move(source, tmpEncFile);
|
||||||
if (init != "")
|
if (init != "")
|
||||||
{
|
{
|
||||||
cmd += $" --fragments-info \"{init}\" ";
|
var infoFile = Path.GetDirectoryName(init) == workDir ? Path.GetFileName(init) : init;
|
||||||
|
cmd += $" --fragments-info \"{infoFile}\" ";
|
||||||
}
|
}
|
||||||
cmd += $" \"{source}\" \"{dest}\"";
|
cmd += $" \"{Path.GetFileName(tmpEncFile)}\" \"{Path.GetFileName(tmpDecFile)}\"";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var enc = source;
|
||||||
|
// ffmpeg实时解密 手动构造文件
|
||||||
|
if (init != "")
|
||||||
|
{
|
||||||
|
tmpFile = Path.ChangeExtension(source, ".itmp");
|
||||||
|
MergeUtil.CombineMultipleFilesIntoSingleFile([init, source], tmpFile);
|
||||||
|
enc = tmpFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
await RunCommandAsync(bin, cmd);
|
cmd = $"-loglevel error -nostdin -decryption_key {keyPair.Split(':')[1]} -i \"{enc}\" -c copy \"{dest}\"";
|
||||||
|
}
|
||||||
|
|
||||||
if (File.Exists(dest) && new FileInfo(dest).Length > 0)
|
var isSuccess = await RunCommandAsync(bin, cmd, workDir);
|
||||||
|
|
||||||
|
// mp4decrypt 还原文件改名操作
|
||||||
|
if (workDir is not null)
|
||||||
|
{
|
||||||
|
if (File.Exists(tmpEncFile)) File.Move(tmpEncFile, source);
|
||||||
|
if (File.Exists(tmpDecFile)) File.Move(tmpDecFile, dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSuccess)
|
||||||
{
|
{
|
||||||
if (tmpFile != "" && File.Exists(tmpFile)) File.Delete(tmpFile);
|
if (tmpFile != "" && File.Exists(tmpFile)) File.Delete(tmpFile);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Error(ResString.decryptionFailed);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task RunCommandAsync(string name, string arg)
|
private static async Task<bool> RunCommandAsync(string name, string arg, string? workDir = null)
|
||||||
{
|
{
|
||||||
Logger.DebugMarkUp($"FileName: {name}");
|
Logger.DebugMarkUp($"FileName: {name}");
|
||||||
Logger.DebugMarkUp($"Arguments: {arg}");
|
Logger.DebugMarkUp($"Arguments: {arg}");
|
||||||
await Process.Start(new ProcessStartInfo()
|
var process = Process.Start(new ProcessStartInfo()
|
||||||
{
|
{
|
||||||
FileName = name,
|
FileName = name,
|
||||||
Arguments = arg,
|
Arguments = arg,
|
||||||
// RedirectStandardOutput = true,
|
// RedirectStandardOutput = true,
|
||||||
// RedirectStandardError = true,
|
// RedirectStandardError = true,
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
UseShellExecute = false
|
UseShellExecute = false,
|
||||||
})!.WaitForExitAsync();
|
WorkingDirectory = workDir
|
||||||
|
});
|
||||||
|
await process!.WaitForExitAsync();
|
||||||
|
return process.ExitCode == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -116,16 +157,14 @@ namespace N_m3u8DL_RE.Util
|
|||||||
Logger.InfoMarkUp(ResString.searchKey);
|
Logger.InfoMarkUp(ResString.searchKey);
|
||||||
using var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
using var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
using var reader = new StreamReader(stream);
|
using var reader = new StreamReader(stream);
|
||||||
var line = "";
|
while (await reader.ReadLineAsync() is { } line)
|
||||||
while ((line = await reader.ReadLineAsync()) != null)
|
|
||||||
{
|
|
||||||
if (line.Trim().StartsWith(kid))
|
|
||||||
{
|
{
|
||||||
|
if (!line.Trim().StartsWith(kid)) continue;
|
||||||
|
|
||||||
Logger.InfoMarkUp($"[green]OK[/] [grey]{line.Trim()}[/]");
|
Logger.InfoMarkUp($"[green]OK[/] [grey]{line.Trim()}[/]");
|
||||||
return line.Trim();
|
return line.Trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.ErrorMarkUp(ex.Message);
|
Logger.ErrorMarkUp(ex.Message);
|
||||||
@ -144,17 +183,15 @@ namespace N_m3u8DL_RE.Util
|
|||||||
|
|
||||||
public static ParsedMP4Info GetMP4Info(string output)
|
public static ParsedMP4Info GetMP4Info(string output)
|
||||||
{
|
{
|
||||||
using (var fs = File.OpenRead(output))
|
using var fs = File.OpenRead(output);
|
||||||
{
|
|
||||||
var header = new byte[1 * 1024 * 1024]; // 1MB
|
var header = new byte[1 * 1024 * 1024]; // 1MB
|
||||||
fs.Read(header);
|
_ = fs.Read(header);
|
||||||
return GetMP4Info(header);
|
return GetMP4Info(header);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static string? ReadInitShaka(string output, string bin)
|
public static string? ReadInitShaka(string output, string bin)
|
||||||
{
|
{
|
||||||
Regex ShakaKeyIDRegex = new Regex("Key for key_id=([0-9a-f]+) was not found");
|
Regex shakaKeyIdRegex = KidOutputRegex();
|
||||||
|
|
||||||
// TODO: handle the case that shaka packager actually decrypted (key ID == ZeroKid)
|
// TODO: handle the case that shaka packager actually decrypted (key ID == ZeroKid)
|
||||||
// - stop process
|
// - stop process
|
||||||
@ -174,7 +211,9 @@ namespace N_m3u8DL_RE.Util
|
|||||||
p.Start();
|
p.Start();
|
||||||
var errorOutput = p.StandardError.ReadToEnd();
|
var errorOutput = p.StandardError.ReadToEnd();
|
||||||
p.WaitForExit();
|
p.WaitForExit();
|
||||||
return ShakaKeyIDRegex.Match(errorOutput).Groups[1].Value;
|
return shakaKeyIdRegex.Match(errorOutput).Groups[1].Value;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex("Key for key_id=([0-9a-f]+) was not found")]
|
||||||
|
private static partial Regex KidOutputRegex();
|
||||||
}
|
}
|
@ -1,36 +1,30 @@
|
|||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util
|
namespace N_m3u8DL_RE.Util;
|
||||||
{
|
|
||||||
internal partial class MediainfoUtil
|
internal static partial class MediainfoUtil
|
||||||
{
|
{
|
||||||
[GeneratedRegex(" Stream #.*")]
|
[GeneratedRegex(" Stream #.*")]
|
||||||
private static partial Regex TextRegex();
|
private static partial Regex TextRegex();
|
||||||
[GeneratedRegex("#0:\\d(\\[0x\\w+?\\])")]
|
[GeneratedRegex(@"#0:\d(\[0x\w+?\])")]
|
||||||
private static partial Regex IdRegex();
|
private static partial Regex IdRegex();
|
||||||
[GeneratedRegex(": (\\w+): (.*)")]
|
[GeneratedRegex(": (\\w+): (.*)")]
|
||||||
private static partial Regex TypeRegex();
|
private static partial Regex TypeRegex();
|
||||||
[GeneratedRegex("(.*?)(,|$)")]
|
[GeneratedRegex("(.*?)(,|$)")]
|
||||||
private static partial Regex BaseInfoRegex();
|
private static partial Regex BaseInfoRegex();
|
||||||
[GeneratedRegex(" \\/ 0x\\w+")]
|
[GeneratedRegex(@" \/ 0x\w+")]
|
||||||
private static partial Regex ReplaceRegex();
|
private static partial Regex ReplaceRegex();
|
||||||
[GeneratedRegex("\\d{2,}x\\d+")]
|
[GeneratedRegex(@"\d{2,}x\d+")]
|
||||||
private static partial Regex ResRegex();
|
private static partial Regex ResRegex();
|
||||||
[GeneratedRegex("\\d+ kb\\/s")]
|
[GeneratedRegex(@"\d+ kb\/s")]
|
||||||
private static partial Regex BitrateRegex();
|
private static partial Regex BitrateRegex();
|
||||||
[GeneratedRegex("(\\d+(\\.\\d+)?) fps")]
|
[GeneratedRegex(@"(\d+(\.\d+)?) fps")]
|
||||||
private static partial Regex FpsRegex();
|
private static partial Regex FpsRegex();
|
||||||
[GeneratedRegex("DOVI configuration record.*profile: (\\d).*compatibility id: (\\d)")]
|
[GeneratedRegex(@"DOVI configuration record.*profile: (\d).*compatibility id: (\d)")]
|
||||||
private static partial Regex DoViRegex();
|
private static partial Regex DoViRegex();
|
||||||
[GeneratedRegex("Duration.*?start: (\\d+\\.?\\d{0,3})")]
|
[GeneratedRegex(@"Duration.*?start: (\d+\.?\d{0,3})")]
|
||||||
private static partial Regex StartRegex();
|
private static partial Regex StartRegex();
|
||||||
|
|
||||||
public static async Task<List<Mediainfo>> ReadInfoAsync(string binary, string file)
|
public static async Task<List<Mediainfo>> ReadInfoAsync(string binary, string file)
|
||||||
@ -48,7 +42,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
RedirectStandardError = true,
|
RedirectStandardError = true,
|
||||||
UseShellExecute = false
|
UseShellExecute = false
|
||||||
})!;
|
})!;
|
||||||
var output = p.StandardError.ReadToEnd();
|
var output = await p.StandardError.ReadToEndAsync();
|
||||||
await p.WaitForExitAsync();
|
await p.WaitForExitAsync();
|
||||||
|
|
||||||
foreach (Match stream in TextRegex().Matches(output))
|
foreach (Match stream in TextRegex().Matches(output))
|
||||||
@ -87,7 +81,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
|
|
||||||
if (result.Count == 0)
|
if (result.Count == 0)
|
||||||
{
|
{
|
||||||
result.Add(new Mediainfo()
|
result.Add(new Mediainfo
|
||||||
{
|
{
|
||||||
Type = "Unknown"
|
Type = "Unknown"
|
||||||
});
|
});
|
||||||
@ -96,4 +90,3 @@ namespace N_m3u8DL_RE.Util
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,20 +1,13 @@
|
|||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Entity;
|
using N_m3u8DL_RE.Entity;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.CommandLine;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using N_m3u8DL_RE.Enum;
|
||||||
using System.Xml;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util
|
namespace N_m3u8DL_RE.Util;
|
||||||
{
|
|
||||||
internal class MergeUtil
|
internal static class MergeUtil
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 输入一堆已存在的文件,合并到新文件
|
/// 输入一堆已存在的文件,合并到新文件
|
||||||
@ -34,20 +27,16 @@ namespace N_m3u8DL_RE.Util
|
|||||||
if (!Directory.Exists(Path.GetDirectoryName(outputFilePath)))
|
if (!Directory.Exists(Path.GetDirectoryName(outputFilePath)))
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outputFilePath)!);
|
Directory.CreateDirectory(Path.GetDirectoryName(outputFilePath)!);
|
||||||
|
|
||||||
string[] inputFilePaths = files;
|
var inputFilePaths = files;
|
||||||
using (var outputStream = File.Create(outputFilePath))
|
using var outputStream = File.Create(outputFilePath);
|
||||||
{
|
|
||||||
foreach (var inputFilePath in inputFilePaths)
|
foreach (var inputFilePath in inputFilePaths)
|
||||||
{
|
{
|
||||||
if (inputFilePath == "")
|
if (inputFilePath == "")
|
||||||
continue;
|
continue;
|
||||||
using (var inputStream = File.OpenRead(inputFilePath))
|
using var inputStream = File.OpenRead(inputFilePath);
|
||||||
{
|
|
||||||
inputStream.CopyTo(outputStream);
|
inputStream.CopyTo(outputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int InvokeFFmpeg(string binary, string command, string workingDirectory)
|
private static int InvokeFFmpeg(string binary, string command, string workingDirectory)
|
||||||
{
|
{
|
||||||
@ -79,20 +68,16 @@ namespace N_m3u8DL_RE.Util
|
|||||||
public static string[] PartialCombineMultipleFiles(string[] files)
|
public static string[] PartialCombineMultipleFiles(string[] files)
|
||||||
{
|
{
|
||||||
var newFiles = new List<string>();
|
var newFiles = new List<string>();
|
||||||
int div = 0;
|
var div = files.Length <= 90000 ? 100 : 200;
|
||||||
if (files.Length <= 90000)
|
|
||||||
div = 100;
|
|
||||||
else
|
|
||||||
div = 200;
|
|
||||||
|
|
||||||
string outputName = Path.Combine(Path.GetDirectoryName(files[0])!, "T");
|
var outputName = Path.Combine(Path.GetDirectoryName(files[0])!, "T");
|
||||||
int index = 0; //序号
|
var index = 0; // 序号
|
||||||
|
|
||||||
// 按照div的容量分割为小数组
|
// 按照div的容量分割为小数组
|
||||||
string[][] li = Enumerable.Range(0, files.Count() / div + 1).Select(x => files.Skip(x * div).Take(div).ToArray()).ToArray();
|
var li = Enumerable.Range(0, files.Length / div + 1).Select(x => files.Skip(x * div).Take(div).ToArray()).ToArray();
|
||||||
foreach (var items in li)
|
foreach (var items in li)
|
||||||
{
|
{
|
||||||
if (items.Count() == 0)
|
if (items.Length == 0)
|
||||||
continue;
|
continue;
|
||||||
var output = outputName + index.ToString("0000") + ".ts";
|
var output = outputName + index.ToString("0000") + ".ts";
|
||||||
CombineMultipleFilesIntoSingleFile(items, output);
|
CombineMultipleFilesIntoSingleFile(items, output);
|
||||||
@ -186,9 +171,9 @@ namespace N_m3u8DL_RE.Util
|
|||||||
return code == 0;
|
return code == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool MuxInputsByFFmpeg(string binary, OutputFile[] files, string outputPath, bool mp4, bool dateinfo)
|
public static bool MuxInputsByFFmpeg(string binary, OutputFile[] files, string outputPath, MuxFormat muxFormat, bool dateinfo)
|
||||||
{
|
{
|
||||||
var ext = mp4 ? "mp4" : "mkv";
|
var ext = OtherUtil.GetMuxExtension(muxFormat);
|
||||||
string dateString = DateTime.Now.ToString("o");
|
string dateString = DateTime.Now.ToString("o");
|
||||||
StringBuilder command = new StringBuilder("-loglevel warning -nostdin -y -dn ");
|
StringBuilder command = new StringBuilder("-loglevel warning -nostdin -y -dn ");
|
||||||
|
|
||||||
@ -206,10 +191,13 @@ namespace N_m3u8DL_RE.Util
|
|||||||
|
|
||||||
var srt = files.Any(x => x.FilePath.EndsWith(".srt"));
|
var srt = files.Any(x => x.FilePath.EndsWith(".srt"));
|
||||||
|
|
||||||
if (mp4)
|
if (muxFormat == MuxFormat.MP4)
|
||||||
command.Append($" -strict unofficial -c:a copy -c:v copy -c:s mov_text "); // mp4不支持vtt/srt字幕,必须转换格式
|
command.Append($" -strict unofficial -c:a copy -c:v copy -c:s mov_text "); // mp4不支持vtt/srt字幕,必须转换格式
|
||||||
else
|
else if (muxFormat == MuxFormat.TS)
|
||||||
|
command.Append($" -strict unofficial -c:a copy -c:v copy ");
|
||||||
|
else if (muxFormat == MuxFormat.MKV)
|
||||||
command.Append($" -strict unofficial -c:a copy -c:v copy -c:s {(srt ? "srt" : "webvtt")} ");
|
command.Append($" -strict unofficial -c:a copy -c:v copy -c:s {(srt ? "srt" : "webvtt")} ");
|
||||||
|
else throw new ArgumentException($"unknown format: {muxFormat}");
|
||||||
|
|
||||||
// CLEAN
|
// CLEAN
|
||||||
command.Append(" -map_metadata -1 ");
|
command.Append(" -map_metadata -1 ");
|
||||||
@ -254,7 +242,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
|
|
||||||
if (dateinfo) command.Append($" -metadata date=\"{dateString}\" ");
|
if (dateinfo) command.Append($" -metadata date=\"{dateString}\" ");
|
||||||
command.Append($" -ignore_unknown -copy_unknown ");
|
command.Append($" -ignore_unknown -copy_unknown ");
|
||||||
command.Append($" \"{outputPath}.{ext}\"");
|
command.Append($" \"{outputPath}{ext}\"");
|
||||||
|
|
||||||
var code = InvokeFFmpeg(binary, command.ToString(), Environment.CurrentDirectory);
|
var code = InvokeFFmpeg(binary, command.ToString(), Environment.CurrentDirectory);
|
||||||
|
|
||||||
@ -295,4 +283,3 @@ namespace N_m3u8DL_RE.Util
|
|||||||
return code == 0;
|
return code == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Enum;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
|
||||||
using N_m3u8DL_RE.Enum;
|
|
||||||
using System.CommandLine;
|
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util
|
namespace N_m3u8DL_RE.Util;
|
||||||
{
|
|
||||||
internal class OtherUtil
|
internal static partial class OtherUtil
|
||||||
{
|
{
|
||||||
public static Dictionary<string, string> SplitHeaderArrayToDic(string[]? headers)
|
public static Dictionary<string, string> SplitHeaderArrayToDic(string[]? headers)
|
||||||
{
|
{
|
||||||
Dictionary<string, string> dic = new();
|
Dictionary<string, string> dic = new();
|
||||||
|
if (headers == null) return dic;
|
||||||
|
|
||||||
if (headers != null)
|
|
||||||
{
|
|
||||||
foreach (string header in headers)
|
foreach (string header in headers)
|
||||||
{
|
{
|
||||||
var index = header.IndexOf(':');
|
var index = header.IndexOf(':');
|
||||||
@ -24,20 +19,15 @@ namespace N_m3u8DL_RE.Util
|
|||||||
dic[header[..index].Trim().ToLower()] = header[(index + 1)..].Trim();
|
dic[header[..index].Trim().ToLower()] = header[(index + 1)..].Trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return dic;
|
return dic;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static char[] InvalidChars = "34,60,62,124,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,26,27,28,29,30,31,58,42,63,92,47"
|
private static readonly char[] InvalidChars = "34,60,62,124,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,26,27,28,29,30,31,58,42,63,92,47"
|
||||||
.Split(',').Select(s => (char)int.Parse(s)).ToArray();
|
.Split(',').Select(s => (char)int.Parse(s)).ToArray();
|
||||||
public static string GetValidFileName(string input, string re = ".", bool filterSlash = false)
|
public static string GetValidFileName(string input, string re = "_", bool filterSlash = false)
|
||||||
{
|
{
|
||||||
string title = input;
|
var title = InvalidChars.Aggregate(input, (current, invalidChar) => current.Replace(invalidChar.ToString(), re));
|
||||||
foreach (char invalidChar in InvalidChars)
|
|
||||||
{
|
|
||||||
title = title.Replace(invalidChar.ToString(), re);
|
|
||||||
}
|
|
||||||
if (filterSlash)
|
if (filterSlash)
|
||||||
{
|
{
|
||||||
title = title.Replace("/", re);
|
title = title.Replace("/", re);
|
||||||
@ -50,6 +40,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
/// 从输入自动获取文件名
|
/// 从输入自动获取文件名
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input"></param>
|
/// <param name="input"></param>
|
||||||
|
/// <param name="addSuffix"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string GetFileNameFromInput(string input, bool addSuffix = true)
|
public static string GetFileNameFromInput(string input, bool addSuffix = true)
|
||||||
{
|
{
|
||||||
@ -103,7 +94,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
/// <exception cref="ArgumentException"></exception>
|
/// <exception cref="ArgumentException"></exception>
|
||||||
public static double ParseSeconds(string timeStr)
|
public static double ParseSeconds(string timeStr)
|
||||||
{
|
{
|
||||||
var pattern = new Regex(@"^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$");
|
var pattern = TimeStrRegex();
|
||||||
|
|
||||||
var match = pattern.Match(timeStr);
|
var match = pattern.Match(timeStr);
|
||||||
|
|
||||||
@ -143,15 +134,15 @@ namespace N_m3u8DL_RE.Util
|
|||||||
/// <param name="filePath"></param>
|
/// <param name="filePath"></param>
|
||||||
public static async Task DeGzipFileAsync(string filePath)
|
public static async Task DeGzipFileAsync(string filePath)
|
||||||
{
|
{
|
||||||
string deGzipFile = Path.ChangeExtension(filePath, ".tmp");
|
var deGzipFile = Path.ChangeExtension(filePath, ".dezip_tmp");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var fileToDecompressAsStream = File.OpenRead(filePath))
|
await using (var fileToDecompressAsStream = File.OpenRead(filePath))
|
||||||
{
|
{
|
||||||
using var decompressedStream = File.Create(deGzipFile);
|
await using var decompressedStream = File.Create(deGzipFile);
|
||||||
using var decompressionStream = new GZipStream(fileToDecompressAsStream, CompressionMode.Decompress);
|
await using var decompressionStream = new GZipStream(fileToDecompressAsStream, CompressionMode.Decompress);
|
||||||
await decompressionStream.CopyToAsync(decompressedStream);
|
await decompressionStream.CopyToAsync(decompressedStream);
|
||||||
}
|
};
|
||||||
File.Delete(filePath);
|
File.Delete(filePath);
|
||||||
File.Move(deGzipFile, filePath);
|
File.Move(deGzipFile, filePath);
|
||||||
}
|
}
|
||||||
@ -165,5 +156,18 @@ namespace N_m3u8DL_RE.Util
|
|||||||
{
|
{
|
||||||
return Environment.GetEnvironmentVariable(key) ?? defaultValue;
|
return Environment.GetEnvironmentVariable(key) ?? defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetMuxExtension(MuxFormat muxFormat)
|
||||||
|
{
|
||||||
|
return muxFormat switch
|
||||||
|
{
|
||||||
|
MuxFormat.MP4 => ".mp4",
|
||||||
|
MuxFormat.MKV => ".mkv",
|
||||||
|
MuxFormat.TS => ".ts",
|
||||||
|
_ => throw new ArgumentException($"unknown format: {muxFormat}")
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$")]
|
||||||
|
private static partial Regex TimeStrRegex();
|
||||||
}
|
}
|
@ -1,18 +1,12 @@
|
|||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.CommandLine;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util
|
namespace N_m3u8DL_RE.Util;
|
||||||
{
|
|
||||||
internal class PipeUtil
|
internal static class PipeUtil
|
||||||
{
|
{
|
||||||
public static Stream CreatePipe(string pipeName)
|
public static Stream CreatePipe(string pipeName)
|
||||||
{
|
{
|
||||||
@ -20,8 +14,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
{
|
{
|
||||||
return new NamedPipeServerStream(pipeName, PipeDirection.InOut);
|
return new NamedPipeServerStream(pipeName, PipeDirection.InOut);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
var path = Path.Combine(Path.GetTempPath(), pipeName);
|
var path = Path.Combine(Path.GetTempPath(), pipeName);
|
||||||
using var p = new Process();
|
using var p = new Process();
|
||||||
p.StartInfo = new ProcessStartInfo()
|
p.StartInfo = new ProcessStartInfo()
|
||||||
@ -38,7 +31,6 @@ namespace N_m3u8DL_RE.Util
|
|||||||
Thread.Sleep(200);
|
Thread.Sleep(200);
|
||||||
return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
|
return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<bool> StartPipeMuxAsync(string binary, string[] pipeNames, string outputPath)
|
public static async Task<bool> StartPipeMuxAsync(string binary, string[] pipeNames, string outputPath)
|
||||||
{
|
{
|
||||||
@ -83,7 +75,7 @@ namespace N_m3u8DL_RE.Util
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(customDest))
|
if (!string.IsNullOrEmpty(customDest))
|
||||||
{
|
{
|
||||||
if (customDest.Trim().StartsWith("-"))
|
if (customDest.Trim().StartsWith('-'))
|
||||||
command.Append(customDest);
|
command.Append(customDest);
|
||||||
else
|
else
|
||||||
command.Append($" -f mpegts -shortest \"{customDest}\"");
|
command.Append($" -f mpegts -shortest \"{customDest}\"");
|
||||||
@ -110,4 +102,3 @@ namespace N_m3u8DL_RE.Util
|
|||||||
return p.ExitCode == 0;
|
return p.ExitCode == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,15 +1,10 @@
|
|||||||
using N_m3u8DL_RE.Common.Entity;
|
using N_m3u8DL_RE.Common.Entity;
|
||||||
using N_m3u8DL_RE.Common.Log;
|
using N_m3u8DL_RE.Common.Log;
|
||||||
using N_m3u8DL_RE.Common.Resource;
|
using N_m3u8DL_RE.Common.Resource;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace N_m3u8DL_RE.Util
|
namespace N_m3u8DL_RE.Util;
|
||||||
{
|
|
||||||
internal class SubtitleUtil
|
internal static class SubtitleUtil
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 写出图形字幕PNG文件
|
/// 写出图形字幕PNG文件
|
||||||
@ -17,24 +12,21 @@ namespace N_m3u8DL_RE.Util
|
|||||||
/// <param name="finalVtt"></param>
|
/// <param name="finalVtt"></param>
|
||||||
/// <param name="tmpDir">临时目录</param>
|
/// <param name="tmpDir">临时目录</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static async Task<bool> TryWriteImagePngsAsync(WebVttSub? finalVtt, string tmpDir)
|
public static async Task TryWriteImagePngsAsync(WebVttSub? finalVtt, string tmpDir)
|
||||||
{
|
{
|
||||||
if (finalVtt != null && finalVtt.Cues.Any(v => v.Payload.StartsWith("Base64::")))
|
if (finalVtt != null && finalVtt.Cues.Any(v => v.Payload.StartsWith("Base64::")))
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp(ResString.processImageSub);
|
Logger.WarnMarkUp(ResString.processImageSub);
|
||||||
var _i = 0;
|
var i = 0;
|
||||||
foreach (var img in finalVtt.Cues.Where(v => v.Payload.StartsWith("Base64::")))
|
foreach (var img in finalVtt.Cues.Where(v => v.Payload.StartsWith("Base64::")))
|
||||||
{
|
{
|
||||||
var name = $"{_i++}.png";
|
var name = $"{i++}.png";
|
||||||
var dest = "";
|
var dest = "";
|
||||||
for (; File.Exists(dest = Path.Combine(tmpDir, name)); name = $"{_i++}.png") ;
|
for (; File.Exists(dest = Path.Combine(tmpDir, name)); name = $"{i++}.png") ;
|
||||||
var base64 = img.Payload[8..];
|
var base64 = img.Payload[8..];
|
||||||
await File.WriteAllBytesAsync(dest, Convert.FromBase64String(base64));
|
await File.WriteAllBytesAsync(dest, Convert.FromBase64String(base64));
|
||||||
img.Payload = name;
|
img.Payload = name;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user