Skip to content

Commit

Permalink
v2.4.0 (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
moshfeu authored Oct 28, 2019
1 parent 3637470 commit 02f4fc8
Show file tree
Hide file tree
Showing 17 changed files with 1,458 additions and 1,299 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"**/*.js.map": true,
"**/*.bundle.js": true
},
"javascript.implicitProjectConfig.experimentalDecorators": true
"javascript.implicitProjectConfig.experimentalDecorators": true,
"typescript.tsdk": "node_modules/typescript/lib"
}
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
[![Travis-ci build Status](https://travis-ci.com/moshfeu/y2mp3.svg?branch=master)](https://travis-ci.com/moshfeu/y2mp3)
[![Wallaby.js](https://img.shields.io/badge/wallaby.js-configured-green.svg)](https://wallabyjs.com)
[![Release version](https://img.shields.io/github/release/moshfeu/y2mp3.svg)](https://github.com/moshfeu/y2mp3/releases/latest)
[![Github All Releases](https://img.shields.io/github/downloads/moshfeu/y2mp3/total.svg)](https://github.com/moshfeu/y2mp3/releases)

<img src="app-resources/logo-128.png" alt="logo" />

Expand Down Expand Up @@ -39,6 +40,11 @@ Find your download: [https://github.com/moshfeu/y2mp3/releases/latest](https://g

## Change log

##### 2.4.0

- Allow to stop active video download
- Scroll the screen to a download when it starts

##### 2.3.0

- Allow to remove videos from the list
Expand Down
2 changes: 1 addition & 1 deletion main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function createWindow() {

win.on('closed', () => {
win = null;
tray.destroy();
tray?.destroy();
tray = null;
}).on('focus', () => {
win.webContents.send(EWindowEvents.WINDOW_FOCUS);
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "y2mp3",
"appname": "y2mp3",
"productName": "y2mp3",
"version": "2.3.0",
"version": "2.4.0",
"main": "main.js",
"author": {
"name": "MosheF",
Expand Down Expand Up @@ -61,7 +61,7 @@
"@types/electron-is-dev": "^0.3.0",
"@types/enzyme": "^3.1.15",
"@types/fluent-ffmpeg": "2.1.2",
"@types/jest": "^23.3.13",
"@types/jest": "^24.0.20",
"@types/mkdirp": "^0.5.2",
"@types/node": "^11.13.9",
"@types/react": "^16.9.5",
Expand All @@ -77,7 +77,8 @@
"enzyme-adapter-react-16": "^1.7.1",
"ffbinaries": "^1.1.0",
"file-loader": "^2.0.0",
"jest": "^23.6.0",
"flush-promises": "^1.0.2",
"jest": "^24.9.0",
"node-sass": "^4.12.0",
"npm-run-all": "^4.1.5",
"object-assign": "^4.1.1",
Expand All @@ -86,7 +87,7 @@
"sinon": "^7.2.3",
"style-loader": "^0.23.1",
"ts-loader": "^5.3.3",
"typescript": "^3.2.4",
"typescript": "^3.7.0-dev.20191018",
"webpack": "^4.28.4",
"webpack-cli": "^3.2.1"
},
Expand Down
4 changes: 3 additions & 1 deletion src/components/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface IFormState {
}

export class Form extends React.Component<IFormProps, IFormState> {
input: HTMLInputElement;
constructor(props: IFormProps) {
super(props);
this.state = {
Expand All @@ -40,6 +41,7 @@ export class Form extends React.Component<IFormProps, IFormState> {
this.setState({
terms: clipboardContent
});
this.input.focus();
}
});
}
Expand Down Expand Up @@ -88,7 +90,7 @@ export class Form extends React.Component<IFormProps, IFormState> {
<div className={['search-wrapper', containerActive && 'active' || '', hasResult && '-has-result' || '', inProcess && '-in-process' || ''].join(' ')}>
<form className="search-form" onSubmit={this.onSubmit}>
<div className="input-holder">
<input className="search-input" type="url" placeholder="https://www.youtube.com/..." value={terms} onChange={e => this.setState({terms: e.target.value})} />
<input ref={ref => this.input = ref} className="search-input" type="url" placeholder="https://www.youtube.com/..." value={terms} onChange={e => this.setState({terms: e.target.value})} />
<button type="button" className="search-icon" onClick={this.searchClick} disabled={inProcess}>
<span></span>
</button>
Expand Down
2 changes: 1 addition & 1 deletion src/components/preferences-modal/preferences-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export class PreferencesModal extends React.Component<IModalProps, IPreferencesM
</Form.Field>
<Form.Field inline>
<label>
Download with the video thumbnail
Set the video's thumnail as album art (for mp3 only)
</label>
<label>
<Checkbox id="albumArt" slider onChange={this.handleFieldChange} checked={albumArt} />
Expand Down
34 changes: 22 additions & 12 deletions src/components/video.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { observe } from 'mobx';
import { observer } from 'mobx-react';
import { IVideoEntity, EVideoStatus, IButtonProgressOptions } from '../types';
import { ButtonProgress } from './button-progress';
Expand All @@ -24,6 +25,8 @@ interface IVideoProps {

@observer
export class Video extends React.Component<IVideoProps, any> {
containerNode: HTMLDivElement;

constructor(props) {
super(props);
}
Expand All @@ -33,7 +36,6 @@ export class Video extends React.Component<IVideoProps, any> {
}

onClickTitle = () => {
const { video:{id} } = this.props;
shell.openExternal(`https://www.youtube.com/watch?v=${this.props.video.id}`);
}

Expand All @@ -42,6 +44,17 @@ export class Video extends React.Component<IVideoProps, any> {
settingsManager.downloadFormat = format;
}

componentDidMount() {
observe(this.props.video, 'status', status => {
if (status.newValue === EVideoStatus.GETTING_INFO) {
// wait for removed video to disappear
setTimeout(() => {
this.containerNode.scrollIntoView({behavior: 'smooth'});
}, 0);
}
});
}

render() {
const { video, onVideoDownloadClick, style } = this.props;
const { backgroundImage } = this;
Expand All @@ -50,17 +63,14 @@ export class Video extends React.Component<IVideoProps, any> {
const { downloadFormat } = settingsManager;

return (
<div className="video" style={{backgroundImage, ...style}}>
{
(video.status === EVideoStatus.NOT_STARTED || video.status === EVideoStatus.DONE) &&
<Popup
trigger={
<Button className="remove" color="red" circular basic icon="close" onClick={() => removeVideo(video.id)}></Button>
}
content="Remove"
inverted
/>
}
<div className="video" ref={elm => this.containerNode = elm} style={{backgroundImage, ...style}}>
<Popup
trigger={
<Button className="remove" color="red" circular basic icon="close" onClick={() => removeVideo(video.id)}></Button>
}
content="Remove"
inverted
/>
<div className="details">
<Popup
trigger={<div className="name"
Expand Down
17 changes: 10 additions & 7 deletions src/mobx/store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { observable, computed, action, toJS } from 'mobx';
import { IVideoEntity, EVideoStatus, IMessage } from '../types';
import { fetchVideos } from '../services/api';
import { fetchVideos, removeAllVideos } from '../services/api';
import { IVideoTask } from '../services/youtube-mp3-downloader';
import { isFFMpegInstalled } from '../services/ffmpeg-installer';
import { showTermsIsInvalid, hideMessage, showNoInternet } from '../services/modalsAndAlerts';
Expand All @@ -26,11 +26,14 @@ class Store {
this.searchTerm = term;
this.searchInProgress = true;
hideMessage();
removeAllVideos();
try {
this.videos = await fetchVideos(this.searchTerm);
if (!this.videos.length) {
const videos = await fetchVideos(this.searchTerm);;
if (!videos.length) {
showTermsIsInvalid();
return;
}
this.videos = videos;
} catch (error) {
if (error.code === 'ENOTFOUND') {
showNoInternet();
Expand All @@ -53,22 +56,23 @@ class Store {
const video = this.getVideo(videoId);
video.status = EVideoStatus.PENDING;
video.progress = 0;
console.log(toJS(this.videos), 'addToQueue');
console.log('addToQueue', toJS(video));
}

@action gettingInfo = (videoId: string) => {
const video = this.getVideo(videoId);
if (!video) {
return;
}
video.status = EVideoStatus.GETTING_INFO;
this.countProgressUntil(video, 19);
console.log(toJS(this.videos), 'gettingInfo');
}

@action progress = ({videoId, progress}: IVideoTask, video: IVideoEntity = this.getVideo(videoId)) => {
// in case of searching while the download in progress
if (video) {
video.status = EVideoStatus.DOWNLOADING;
video.progress = 20 + Math.floor(progress.percentage * 0.8);
console.log(toJS(this.videos), progress, 'progress');
}
}

Expand All @@ -78,7 +82,6 @@ class Store {
if (video) {
video.status = EVideoStatus.DONE;
video.progress = 0;
console.log('done');
}
}

Expand Down
102 changes: 68 additions & 34 deletions src/services/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import YoutubeMp3Downloader from './youtube-mp3-downloader';
import { YoutubeMp3Downloader, DownloadTaskState } from './youtube-mp3-downloader';
import * as ytlist from 'youtube-playlist';
import store from '../mobx/store';
import { IVideoEntity, IPlaylistYoutube, IDownloadProgress } from '../types';
Expand All @@ -12,7 +12,9 @@ import { sync } from 'mkdirp';
import { existsSync } from 'fs';
import { join } from 'path';
import { sync as commandExistsSync } from 'command-exists';
import { downloading, gettingInfo, inResult } from './tray-messanger';
import { downloading, gettingInfo, inResult, clear } from './tray-messanger';
import { IVideoTask } from './youtube-mp3-downloader';
import { showCustomError } from './modalsAndAlerts';

export function isFfmpegInPath() {
return commandExistsSync('ffmpeg');
Expand All @@ -22,38 +24,10 @@ export const downloader = new YoutubeMp3Downloader({
ffmpegPath: ffmpegPath(), // Where is the FFmpeg binary located?
outputPath: settingsManager.downloadsFolder, // Where should the downloaded and encoded files be stored?
youtubeVideoQuality: settingsManager.audioQuality, // What video quality should be used?
queueParallelism: 1, // How many parallel downloads/encodes should be started?
progressTimeout: 1000, // How long should be the interval of the progress reports
filter: 'audio',
format: settingsManager.downloadFormat,
})
.on('addToQueue', videoId => store.addToQueue(videoId))
.on('gettingInfo', videoId => {
gettingInfo(videoId);
store.gettingInfo(videoId)
})
.on('progress', ({videoId, progress}: {videoId: string, progress: IDownloadProgress}) => {
const video = store.getVideo(videoId);
if (video) {
downloading(videoId, progress.speed, progress.eta);
store.progress({videoId, progress}, video);
}
})
.on('finished', (err, { videoId, thumbnail, videoTitle }) => {
store.finished(err, { videoId })
if (videoTitle && settingsManager.notificationWhenDone) {
new Notification('Download completed', {
icon: './app-resources/logo-128.png',
body: `The video "${videoTitle}" downloaded successfully`,
image: thumbnail
});
}
})
.on('error', (err, { videoId }) => {
alert(`Sorry, something went wrong.\nPlease contact the author using "support" menu and just copy / paste the error:\n${err}\n Thanks!`);
console.error(err);
finishVideoOnError(err, videoId);
});
progressTimeout: 1000,
});

function finishVideoOnError(err, videoId: string) {
store.progress({videoId, progress: {
Expand Down Expand Up @@ -117,11 +91,20 @@ export function download(videoOrVideos: IVideoEntity | IVideoEntity[]) {
}
}

export function removeAllVideos() {
store.videos.forEach(video => downloader.cancelDownload(video.id));
store.videos = [];
}

// not in use currently
export function removeVideo(videoId: string) {
store.removeVideo(videoId);
downloader.cancelDownload(videoId);
inResult();
if (store.videos.length) {
inResult();
} else {
clear();
}
}

export async function search(url: string) {
Expand All @@ -148,5 +131,56 @@ function setVideoDownloadPath(video: IVideoEntity) {

function performDownload(video: IVideoEntity) {
setVideoDownloadPath(video);
downloader.download(video.id);
downloader.download(video.id, downloadReducer);
}

function downloadReducer(state: DownloadTaskState, ...args: any[]) {
switch (state) {
case 'added': {
const [videoId] = args;
store.addToQueue(videoId);
} break;
case 'getting info': {
const [videoId] = args;
gettingInfo(videoId);
store.gettingInfo(videoId);
} break;
case 'downloading': {
const [{videoId, progress}] = args as [{videoId: string, progress: IDownloadProgress}];
const video = store.getVideo(videoId);
if (video) {
downloading(videoId, progress.speed, progress.eta);
store.progress({videoId, progress}, video);
}
} break;
case 'done': {
const [{ videoId, thumbnail, videoTitle }] = args as [{videoId: string, thumbnail: string; videoTitle: string}];

store.finished(null, { videoId })
if (videoTitle && settingsManager.notificationWhenDone) {
new Notification('Download completed', {
icon: './app-resources/logo-128.png',
body: `The video "${videoTitle}" downloaded successfully`,
image: thumbnail
});
}
} break;
case 'error': {
const [err, {videoId}] = args as [Error, IVideoTask];
finishVideoOnError(err, videoId);
if (isCustomError(err)) {
showCustomError(err.message);
break;
} else {
alert(`Sorry, something went wrong.\nPlease contact the author using "support" menu and just copy / paste the error:\n${err}\n Thanks!`);
console.error(err);
}
} break;
}
}

function isCustomError(error: Error | object) {
return error instanceof Error &&
// https://commons.wikimedia.org/wiki/File:YouTube_blocked_UMG_country_en.png
error.message.includes('UMG')
}
2 changes: 1 addition & 1 deletion src/services/tray-messanger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import store from '../mobx/store';
const CHANNEL_NAME = 'tray';

function getVideoName(videoId: string): string {
return store.videos.find(v => v.id == videoId).name;
return store.videos.find(v => v.id == videoId)?.name || '';
}

export function inResult() {
Expand Down
Loading

0 comments on commit 02f4fc8

Please sign in to comment.