Skip to content
Merged

push #90

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
12 changes: 9 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

| <img height="20" src="https://github.com/user-attachments/assets/340d360e-79b1-4c70-bfab-d944085f75df" /> Windows | <img height="20" src="https://github.com/user-attachments/assets/42d7e887-4616-4e8c-b1d3-e44e01340f8c" /> macOS | <img height="20" src="https://github.com/user-attachments/assets/e0cc4f33-4516-408b-9c5c-be71a3ac316b" /> Linux |
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **EXE:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0/ROSI-Windows-x64.exe) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0/ROSI-Windows-arm64.exe) | **[Universal DMG](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0/ROSI-MacOS-universal.dmg)** | **AppImage:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0/ROSI-Linux-x86_64.AppImage) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0/ROSI-Linux-arm64.AppImage) |
| <div align="center"><a href="https://apps.microsoft.com/detail/9p4q134b2jw3?referrer=appbadge&mode=direct"><img src="https://get.microsoft.com/images/en-us%20dark.svg" width="150"/></a></div> | **[Universal ZIP](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0/ROSI-MacOS-universal.zip)** | **DEB:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0/ROSI-Linux-amd64.deb) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0/ROSI-Linux-arm64.deb) |
| | | **RPM:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0/ROSI-Linux-x86_64.rpm) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.0/ROSI-Linux-aarch64.rpm) |
| **EXE:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.1/ROSI-Windows-x64.exe) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.1/ROSI-Windows-arm64.exe) | **[Universal DMG](https://github.com/BurntToasters/ROSI/releases/download/v4.1.1/ROSI-MacOS-universal.dmg)** | **AppImage:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.1/ROSI-Linux-x86_64.AppImage) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.1/ROSI-Linux-arm64.AppImage) |
| <div align="center"><a href="https://apps.microsoft.com/detail/9p4q134b2jw3?referrer=appbadge&mode=direct"><img src="https://get.microsoft.com/images/en-us%20dark.svg" width="150"/></a></div> | **[Universal ZIP](https://github.com/BurntToasters/ROSI/releases/download/v4.1.1/ROSI-MacOS-universal.zip)** | **DEB:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.1/ROSI-Linux-amd64.deb) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.1/ROSI-Linux-arm64.deb) |
| | | **RPM:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.1/ROSI-Linux-x86_64.rpm) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.1.1/ROSI-Linux-aarch64.rpm) |

> [!IMPORTANT]
> The `.sig` files in this repo are NOT normal GPG signatures — they are for ROSI's built-in updater to verify the integrity of updates before downloading and installing.
Expand All @@ -30,6 +30,12 @@

---

## Changes in `v4.1.1:`

_What's a new feature update without a major bugfix am I right ;)_

- **FFMPEG/YT-DLP:** Fixed an issue where yt-dlp args were being passes with an extra `--`.

## Changes in `v4.1.0:`

- **NEW - Preview:** Added video preview before downloading so ROSI can show the title, uploader, duration, thumbnail, playlist info, and other basic metadata before saving.
Expand Down
2 changes: 1 addition & 1 deletion com.burnttoasters.rosi.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</screenshot>
</screenshots>
<releases>
<release version="4.1.0" date="2026-06-13"/>
<release version="4.1.1" date="2026-06-13"/>
</releases>
<content_rating type="oars-1.1"/>
</component>
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rosi",
"version": "4.1.0",
"version": "4.1.1",
"private": true,
"description": "Electron GUI for yt-dlp",
"desktopName": "com.burnttoasters.rosi.desktop",
Expand Down
25 changes: 17 additions & 8 deletions src/main/download/commandBuilders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ export interface BuildYtdlpArgsResult {
statusMessages: string[];
}

function insertBeforeUrlSeparator(args: string[], ...items: string[]): void {
const separatorIndex = args.indexOf('--');
if (separatorIndex === -1) {
args.splice(args.length - 1, 0, ...items);
return;
}
args.splice(separatorIndex, 0, ...items);
}

export function buildYtdlpArgs({
normalizedDownloadDir,
url,
Expand All @@ -172,11 +181,11 @@ export function buildYtdlpArgs({
const statusMessages: string[] = [];

if (pathOutputFile) {
args.splice(args.indexOf('--'), 0, '--print-to-file', 'after_move:filepath', pathOutputFile);
insertBeforeUrlSeparator(args, '--print-to-file', 'after_move:filepath', pathOutputFile);
}

if (ffmpegLocation) {
args.splice(args.length - 1, 0, '--ffmpeg-location', ffmpegLocation);
insertBeforeUrlSeparator(args, '--ffmpeg-location', ffmpegLocation);
}

const formatFlagIndex = args.indexOf('-f');
Expand All @@ -200,37 +209,37 @@ export function buildYtdlpArgs({
const audioOutputFmt = ALLOWED_AUDIO_FORMATS.has(settings.audioFormat)
? settings.audioFormat
: 'mp3';
args.splice(-1, 0, '-x', '--audio-format', audioOutputFmt, '--audio-quality', '0');
insertBeforeUrlSeparator(args, '-x', '--audio-format', audioOutputFmt, '--audio-quality', '0');
statusMessages.push(`🎵 Audio-only mode enabled (${audioOutputFmt.toUpperCase()})`);
}

if (settings.hookBrowser && settings.browserChoice) {
const normalized = settings.browserChoice.toLowerCase();
if (ALLOWED_BROWSERS.has(normalized)) {
args.splice(-1, 0, '--cookies-from-browser', normalized);
insertBeforeUrlSeparator(args, '--cookies-from-browser', normalized);
}
}

if (settings.writeSubtitles) {
const langs = SUBTITLE_LANGS_PATTERN.test(settings.subtitleLangs)
? settings.subtitleLangs
: 'en';
args.splice(-1, 0, '--write-subs', '--embed-subs', '--sub-langs', langs);
insertBeforeUrlSeparator(args, '--write-subs', '--embed-subs', '--sub-langs', langs);
statusMessages.push(`💬 Subtitles enabled (${langs})`);
}

if (settings.embedThumbnail) {
args.splice(-1, 0, '--embed-thumbnail');
insertBeforeUrlSeparator(args, '--embed-thumbnail');
statusMessages.push('🖼️ Embedding thumbnail');
}

if (settings.embedMetadata) {
args.splice(-1, 0, '--embed-metadata');
insertBeforeUrlSeparator(args, '--embed-metadata');
statusMessages.push('🏷️ Embedding metadata');
}

if (settings.sponsorblockRemove) {
args.splice(-1, 0, '--sponsorblock-remove', 'default');
insertBeforeUrlSeparator(args, '--sponsorblock-remove', 'default');
statusMessages.push('⏭️ SponsorBlock: removing segments');
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/downloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ export function startDownload(
const settings = loadSettings();
const effectiveSettings: Settings = { ...settings };
const ffmpegCommand = getEffectiveFfmpegPath(options.ffmpegPath || settings.ffmpegPath);
const ffmpegLocation = ffmpegCommand !== 'ffmpeg' ? ffmpegCommand : null;
const ffmpegLocation = ffmpegCommand !== 'ffmpeg' ? path.dirname(ffmpegCommand) : null;

if (options.convertFormat !== undefined) {
if (typeof options.convertFormat === 'string' && options.convertFormat.trim() !== '') {
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/splash.html
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ <h1 class="app-title">ROSI</h1>
<div class="loading-label">Loading</div>
</div>
</div>
<div class="version" id="version-display">v4.1.0</div>
<div class="version" id="version-display">v4.1.1</div>
<script>
setTimeout(function () {
var label = document.querySelector('.loading-label');
Expand Down
49 changes: 47 additions & 2 deletions src/tests/commandBuilders.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,17 +268,62 @@ describe('command builders', () => {
videoFormat: '137',
audioFormat: '140',
},
ffmpegLocation: '/usr/bin/ffmpeg',
ffmpegLocation: '/usr/bin',
});

expect(result.args).toContain('--ffmpeg-location');
expect(result.args).toContain('/usr/bin/ffmpeg');
expect(result.args).toContain('/usr/bin');
expect(result.args).toContain('--cookies-from-browser');
expect(result.args).toContain('firefox');
expect(result.args).toContain('137+140');
expect(result.statusMessages).toContain('📹 Using formats: video=137, audio=140');
});

it('places optional flags before the URL separator, not after it', () => {
const url = 'https://example.com/video';
const result = buildYtdlpArgs({
normalizedDownloadDir: '/tmp/downloads',
url,
settings: createSettings({
hookBrowser: true,
browserChoice: 'Firefox',
writeSubtitles: true,
embedThumbnail: true,
embedMetadata: true,
sponsorblockRemove: true,
}),
options: {
url,
outputPath: '/tmp/downloads',
videoFormat: '271',
audioFormat: '251',
},
ffmpegLocation: '/Applications/Rosi.app/Contents/Resources/ffmpeg',
pathOutputFile: '/tmp/rosi-path.txt',
});

const separatorIndex = result.args.indexOf('--');
expect(separatorIndex).toBeGreaterThan(-1);
expect(result.args[separatorIndex + 1]).toBe(url);
expect(result.args[result.args.length - 1]).toBe(url);

for (const flag of [
'--print-to-file',
'--ffmpeg-location',
'--cookies-from-browser',
'--write-subs',
'--embed-thumbnail',
'--embed-metadata',
'--sponsorblock-remove',
]) {
expect(result.args.indexOf(flag)).toBeGreaterThan(-1);
expect(result.args.indexOf(flag)).toBeLessThan(separatorIndex);
}
expect(result.args[result.args.indexOf('--ffmpeg-location') + 1]).toBe(
'/Applications/Rosi.app/Contents/Resources/ffmpeg'
);
});

it('builds yt-dlp args for audio-only mode when no format override is provided', () => {
const result = buildYtdlpArgs({
normalizedDownloadDir: '/tmp/downloads',
Expand Down
Loading