Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with: { dotnet-version: '8.0.x' }
- uses: actions/setup-node@v4
with: { node-version: '24.x' }
- run: dotnet restore Ripmedia.sln
- run: dotnet build src/Ripmedia.Cli/Ripmedia.Cli.csproj --no-restore
- run: dotnet test Ripmedia.sln --configuration Release --no-restore
- run: dotnet publish src/Ripmedia.Cli/Ripmedia.Cli.csproj --configuration Release --runtime win-x64 --self-contained true -p:PublishSingleFile=true --output artifacts/app
- run: artifacts/app/ripmedia.exe version
37 changes: 37 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Deploy Pages

on:
push:
branches: [main]
paths:
- site/**
- .github/workflows/pages.yml
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Configure Pages
uses: actions/configure-pages@v5
- name: Upload site artifact
uses: actions/upload-pages-artifact@v4
with:
path: site
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
15 changes: 15 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with: { dotnet-version: '8.0.x' }
- uses: actions/setup-node@v4
with: { node-version: '24.x' }
- run: dotnet publish src/Ripmedia.Cli/Ripmedia.Cli.csproj --configuration Release --runtime win-x64 --self-contained true -p:PublishSingleFile=true --output artifacts/ripmedia
- name: Add download engines
shell: pwsh
Expand All @@ -18,10 +20,23 @@ jobs:
New-Item -ItemType Directory artifacts/ripmedia/tools | Out-Null
Invoke-WebRequest $deps.ytDlp.url -OutFile artifacts/ripmedia/tools/yt-dlp.exe
Invoke-WebRequest $deps.ffmpeg.url -OutFile artifacts/ffmpeg.zip
Invoke-WebRequest $deps.youtubePoTokenProvider.pluginUrl -OutFile artifacts/bgutil-ytdlp-pot-provider.zip
if ((Get-FileHash artifacts/ripmedia/tools/yt-dlp.exe -Algorithm SHA256).Hash.ToLowerInvariant() -ne $deps.ytDlp.sha256) { throw 'yt-dlp checksum mismatch' }
if ((Get-FileHash artifacts/ffmpeg.zip -Algorithm SHA256).Hash.ToLowerInvariant() -ne $deps.ffmpeg.sha256) { throw 'FFmpeg checksum mismatch' }
if ((Get-FileHash artifacts/bgutil-ytdlp-pot-provider.zip -Algorithm SHA256).Hash.ToLowerInvariant() -ne $deps.youtubePoTokenProvider.pluginSha256) { throw 'YouTube PO-token plugin checksum mismatch' }
Expand-Archive artifacts/ffmpeg.zip artifacts/ffmpeg
Copy-Item (Get-ChildItem artifacts/ffmpeg -Recurse -Filter ffmpeg.exe | Select-Object -First 1).FullName artifacts/ripmedia/tools/ffmpeg.exe
New-Item -ItemType Directory artifacts/ripmedia/tools/yt-dlp-plugins | Out-Null
Copy-Item artifacts/bgutil-ytdlp-pot-provider.zip artifacts/ripmedia/tools/yt-dlp-plugins/bgutil-ytdlp-pot-provider.zip
git clone --no-checkout $deps.youtubePoTokenProvider.repository artifacts/pot-provider
git -C artifacts/pot-provider checkout $deps.youtubePoTokenProvider.commit
Push-Location artifacts/pot-provider/server
npm ci --include=dev
npx --no-install tsc
Pop-Location
New-Item -ItemType Directory artifacts/ripmedia/tools/pot-provider | Out-Null
Copy-Item artifacts/pot-provider/server artifacts/ripmedia/tools/pot-provider/server -Recurse
Copy-Item (Get-Command node.exe).Source artifacts/ripmedia/tools/pot-provider/node.exe
- name: Package
shell: pwsh
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ bin/
obj/
artifacts/
TestResults/

src/Ripmedia.Cli/tools/
Binary file added CutoutLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added FullAppIconLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,23 @@ The installer downloads a verified GitHub Release bundle. It includes ripmedia,
```powershell
ripmedia "https://soundcloud.com/artist/track"
ripmedia download "https://example.com/video" --format mp4
ripmedia "https://example.com/video" --mp4
ripmedia "https://example.com/audio" --mp3
ripmedia info "https://example.com/video" --formats
ripmedia cookies
```

By default ripmedia saves the best source-provided combined file to Downloads without transcoding. `--audio` selects source audio; `--mp3` and `--format` explicitly convert using FFmpeg. Supported conversion targets are `mp3`, `m4a`, `aac`, `flac`, `ogg`, `opus`, `wav`, `mp4`, `mkv`, and `webm`.
By default ripmedia saves the highest-quality source video and audio streams available to Downloads, merging them with FFmpeg when needed, without transcoding. When you select a browser profile with `ripmedia cookies`, YouTube downloads use that profile's authorized session and the bundled local token provider to access high-quality streams. `--audio` selects the best source audio; `--mp3`, `--mp4`, and `--format` explicitly convert using FFmpeg. Supported conversion targets are `mp3`, `m4a`, `aac`, `flac`, `ogg`, `opus`, `wav`, `mp4`, `mkv`, and `webm`.

Run `ripmedia config` to open `settings.json` in your default JSON or text editor. Leave `DefaultAudioFormat` and `DefaultVideoFormat` blank to keep source formats; set them to a supported audio or video format to convert downloads of that media type. Per-download `--format`, `--mp3`, and `--mp4` options take precedence.

SoundCloud playlists are saved as numbered collection folders and tagged with available metadata and artwork. TikTok downloads the best stream made available by the extractor; ripmedia does not remove an embedded watermark or bypass DRM, paywalls, logins, or access controls.

## Commands

`download` (default), `info`, `cookies`, `config`, `update`, `version`, and `help`.
`download` (default), `info`, `cookies`, `config`, `webhost`, `update`, `version`, and `help`.

`ripmedia webhost` opens a minimal local downloader at `http://127.0.0.1:4747`. Paste a URL followed by any download flags and press Enter to queue it (for example, `https://example.com/video --mp3`); further URLs run in parallel. Quote flag values containing spaces, such as `--output-dir "C:\Media Files"`. Keep the terminal open while downloads run; press Ctrl+C to stop the web host. It skips browser-profile cookies because it opens a browser itself; use a configured cookies file if an authorized URL requires authentication.

`ripmedia cookies` shows an interactive browser-profile menu. It stores a profile reference only; it does not export browser cookies. You may also pass `--cookies <netscape.txt>` or `--cookies-from-browser <yt-dlp-spec>` per command.

Expand Down
2 changes: 2 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Ripmedia bundles or invokes these separately licensed components:

- [yt-dlp](https://github.com/yt-dlp/yt-dlp) (Unlicense)
- [BgUtils POT Provider](https://github.com/Brainicism/bgutil-ytdlp-pot-provider) (GPL-3.0)
- [Node.js](https://nodejs.org/) (MIT)
- [FFmpeg](https://ffmpeg.org/legal.html) (LGPL/GPL, depending on the selected build)
- [Spectre.Console](https://spectreconsole.net/) (MIT)
- [TagLibSharp](https://github.com/mono/taglib-sharp) (LGPL-2.1-or-later)
Expand Down
7 changes: 7 additions & 0 deletions eng/dependencies.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,12 @@
"version": "9.0.1",
"url": "https://github.com/GyanD/codexffmpeg/releases/download/9.0.1/ffmpeg-9.0.1-essentials_build.zip",
"sha256": "fec81ae03971d9dd4be3ebe02e263bd2ec1d789483f931bdba5f5715e65da2e9"
},
"youtubePoTokenProvider": {
"version": "1.3.1",
"repository": "https://github.com/Brainicism/bgutil-ytdlp-pot-provider.git",
"commit": "7608dd51ee813b48cf9a6d68c6e42cb197ce10e0",
"pluginUrl": "https://github.com/Brainicism/bgutil-ytdlp-pot-provider/releases/download/1.3.1/bgutil-ytdlp-pot-provider.zip",
"pluginSha256": "b8ceec7f76143da172aaf5ebeec0c2d218e5680c063b931586bca48567069b38"
}
}
7 changes: 5 additions & 2 deletions scripts/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ New-Item -ItemType Directory -Path $temp | Out-Null
try {
$zip = Join-Path $temp 'ripmedia-win-x64.zip'
Invoke-WebRequest $zipAsset.browser_download_url -OutFile $zip
$sums = (Invoke-WebRequest $sumAsset.browser_download_url).Content
# Windows PowerShell 5.1 exposes Invoke-WebRequest .Content as a byte array.
# Invoke-RestMethod decodes this text asset consistently in both Windows
# PowerShell and PowerShell 7+, so the checksum regex sees the actual file.
$sums = Invoke-RestMethod $sumAsset.browser_download_url -Headers @{ 'User-Agent' = 'ripmedia-installer' }
$hash = (Get-FileHash $zip -Algorithm SHA256).Hash.ToLowerInvariant()
if ($sums -notmatch "(?im)^$hash\s+\*?ripmedia-win-x64\.zip$") { throw 'Release checksum verification failed.' }
if ($sums -notmatch "(?im)^$hash\s+\*?ripmedia-win-x64\.zip\r?$") { throw 'Release checksum verification failed.' }
$target = Join-Path $versions $release.tag_name
if (Test-Path -LiteralPath $target) { Remove-Item -LiteralPath $target -Force -Recurse }
New-Item -ItemType Directory -Path $target -Force | Out-Null
Expand Down
78 changes: 78 additions & 0 deletions scripts/restore-tools.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Destination
)

$ErrorActionPreference = 'Stop'

$manifestPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'eng/dependencies.json'
$dependencies = Get-Content -Raw $manifestPath | ConvertFrom-Json

New-Item -ItemType Directory -Path $Destination -Force | Out-Null

function Restore-File {
param(
[Parameter(Mandatory)]$Dependency,
[Parameter(Mandatory)][string]$Path
)

if (Test-Path -LiteralPath $Path) {
$actualHash = (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
if ($actualHash -eq $Dependency.sha256) { return }
Remove-Item -LiteralPath $Path -Force
}

Invoke-WebRequest -Uri $Dependency.url -OutFile $Path
$actualHash = (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
if ($actualHash -ne $Dependency.sha256) {
Remove-Item -LiteralPath $Path -Force
throw "Checksum verification failed for $(Split-Path $Path -Leaf)."
}
}

Restore-File -Dependency $dependencies.ytDlp -Path (Join-Path $Destination 'yt-dlp.exe')

$ffmpegZip = Join-Path ([IO.Path]::GetTempPath()) ("ripmedia-ffmpeg-" + [guid]::NewGuid() + '.zip')
$ffmpegDirectory = Join-Path ([IO.Path]::GetTempPath()) ("ripmedia-ffmpeg-" + [guid]::NewGuid())
try {
Restore-File -Dependency $dependencies.ffmpeg -Path $ffmpegZip
Expand-Archive -LiteralPath $ffmpegZip -DestinationPath $ffmpegDirectory
$ffmpeg = Get-ChildItem -LiteralPath $ffmpegDirectory -Recurse -Filter ffmpeg.exe | Select-Object -First 1
if (-not $ffmpeg) { throw 'The FFmpeg archive did not contain ffmpeg.exe.' }
Copy-Item -LiteralPath $ffmpeg.FullName -Destination (Join-Path $Destination 'ffmpeg.exe') -Force
} finally {
if (Test-Path -LiteralPath $ffmpegZip) { Remove-Item -LiteralPath $ffmpegZip -Force }
if (Test-Path -LiteralPath $ffmpegDirectory) { Remove-Item -LiteralPath $ffmpegDirectory -Force -Recurse }
}

$node = Get-Command node.exe -ErrorAction SilentlyContinue
$npm = Get-Command npm -ErrorAction SilentlyContinue
$git = Get-Command git -ErrorAction SilentlyContinue
if (-not $node -or -not $npm -or -not $git) { throw 'Restoring YouTube high-quality support requires Node.js, npm, and Git.' }

$provider = $dependencies.youtubePoTokenProvider
$providerSource = Join-Path ([IO.Path]::GetTempPath()) ("ripmedia-pot-provider-" + [guid]::NewGuid())
$pluginDirectory = Join-Path $Destination 'yt-dlp-plugins'
$plugin = Join-Path $pluginDirectory 'bgutil-ytdlp-pot-provider.zip'
$providerDirectory = Join-Path $Destination 'pot-provider'
try {
New-Item -ItemType Directory -Path $pluginDirectory -Force | Out-Null
Restore-File -Dependency ([pscustomobject]@{ url = $provider.pluginUrl; sha256 = $provider.pluginSha256 }) -Path $plugin
& $git.Source clone --no-checkout $provider.repository $providerSource
if ($LASTEXITCODE -ne 0) { throw 'Could not download the YouTube PO-token provider source.' }
& $git.Source -C $providerSource checkout $provider.commit
if ($LASTEXITCODE -ne 0) { throw 'Could not verify the YouTube PO-token provider source.' }
Push-Location (Join-Path $providerSource 'server')
try {
& $npm.Source ci --include=dev
if ($LASTEXITCODE -ne 0) { throw 'Could not install the YouTube PO-token provider dependencies.' }
& (Join-Path (Get-Location) 'node_modules\.bin\tsc.cmd')
if ($LASTEXITCODE -ne 0) { throw 'Could not build the YouTube PO-token provider.' }
} finally { Pop-Location }
New-Item -ItemType Directory -Path $providerDirectory -Force | Out-Null
Copy-Item -LiteralPath (Join-Path $providerSource 'server') -Destination (Join-Path $providerDirectory 'server') -Recurse -Force
Copy-Item -LiteralPath $node.Source -Destination (Join-Path $providerDirectory 'node.exe') -Force
} finally {
if (Test-Path -LiteralPath $providerSource) { Remove-Item -LiteralPath $providerSource -Force -Recurse }
}
Binary file added site/assets/CutoutLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added site/assets/FullAppIconLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading