Benoit Sida 3 år sedan
förälder
incheckning
fe371b8ecd
9 ändrade filer med 100 tillägg och 37 borttagningar
  1. 2
    0
      .meteor/packages
  2. 9
    0
      .meteor/versions
  3. 2
    2
      client/main.js
  4. 20
    6
      imports/api/links.js
  5. 49
    0
      imports/startup/routes.js
  6. 6
    19
      imports/ui/App.jsx
  7. 10
    9
      imports/ui/Options.jsx
  8. 0
    1
      imports/ui/Video.js
  9. 2
    0
      server/main.js

+ 2
- 0
.meteor/packages Visa fil

29
 
29
 
30
 npm-container
30
 npm-container
31
 momentjs:moment
31
 momentjs:moment
32
+iron:router
33
+udondan:jszip

+ 9
- 0
.meteor/versions Visa fil

36
 htmljs@1.0.11
36
 htmljs@1.0.11
37
 http@1.2.11
37
 http@1.2.11
38
 id-map@1.0.9
38
 id-map@1.0.9
39
+iron:controller@1.0.12
40
+iron:core@1.0.11
41
+iron:dynamic-template@1.0.12
42
+iron:layout@1.0.12
43
+iron:location@1.0.11
44
+iron:middleware-stack@1.1.0
45
+iron:router@1.1.2
46
+iron:url@1.1.0
39
 jquery@1.11.10
47
 jquery@1.11.10
40
 launch-screen@1.1.1
48
 launch-screen@1.1.1
41
 less@2.7.9
49
 less@2.7.9
86
 templating-tools@1.1.0
94
 templating-tools@1.1.0
87
 tmeasday:check-npm-versions@0.2.0
95
 tmeasday:check-npm-versions@0.2.0
88
 tracker@1.1.2
96
 tracker@1.1.2
97
+udondan:jszip@2.4.0_1
89
 ui@1.0.12
98
 ui@1.0.12
90
 underscore@1.0.10
99
 underscore@1.0.10
91
 url@1.1.0
100
 url@1.1.0

+ 2
- 2
client/main.js Visa fil

1
 import React from 'react';
1
 import React from 'react';
2
 import { Meteor } from 'meteor/meteor';
2
 import { Meteor } from 'meteor/meteor';
3
 import { render } from 'react-dom';
3
 import { render } from 'react-dom';
4
-import '../imports/startup/accounts-config.js';
5
 import App from '../imports/ui/App.jsx';
4
 import App from '../imports/ui/App.jsx';
5
+import '../imports/startup/routes';
6
  
6
  
7
 Meteor.startup(() => {
7
 Meteor.startup(() => {
8
-  render(<App />, document.getElementById('main'));
8
+	render(<App />, document.getElementById('main'));
9
 });
9
 });

+ 20
- 6
imports/api/links.js Visa fil

27
 			if (err) {
27
 			if (err) {
28
 				console.log(url);
28
 				console.log(url);
29
 				console.error(err);
29
 				console.error(err);
30
-				Converted.update({ _id: id }, { $set: { error: 'Video is unreachable' } });
30
+				Converted.update({ _id: id }, { $set: {
31
+					error_video: 'Video is unreachable',
32
+					error_audio: 'Video is unreachable',
33
+				} });
31
 			} else {
34
 			} else {
32
 				const totalSeconds = info ? info.length_seconds : 0;
35
 				const totalSeconds = info ? info.length_seconds : 0;
33
 				const dl = ytdl(url);
36
 				const dl = ytdl(url);
35
 				ffmpeg(dl)
38
 				ffmpeg(dl)
36
 					.noVideo()
39
 					.noVideo()
37
 					.on('end', Meteor.bindEnvironment(() => {
40
 					.on('end', Meteor.bindEnvironment(() => {
38
-						Converted.update({ _id: id }, { $set: { audio: audioPath, audio_progress: 100 } });
41
+						Converted.update({ _id: id }, { $set: { audio: filename + '.mp3', audio_progress: 100 } });
39
 					}))
42
 					}))
40
 					.on('progress', Meteor.bindEnvironment((params) => {
43
 					.on('progress', Meteor.bindEnvironment((params) => {
41
 						const time = moment(new Date(...[0, 0, 0, ...params.timemark.split(':')]));
44
 						const time = moment(new Date(...[0, 0, 0, ...params.timemark.split(':')]));
43
 						let percent = ((seconds / totalSeconds) * 100).toFixed(0);
46
 						let percent = ((seconds / totalSeconds) * 100).toFixed(0);
44
 						Converted.update({ _id: id }, { $set: { audio_progress: percent } });
47
 						Converted.update({ _id: id }, { $set: { audio_progress: percent } });
45
 					}))
48
 					}))
46
-					.on('error', (err) => { console.error('MP3 encoding error: ' + err.message); })
49
+					.on('error', Meteor.bindEnvironment((err) => {
50
+						console.error('MP3 encoding error: ' + err.message);
51
+						Converted.update({ _id: id }, { $set: { error_audio: 'Audio cannot be extracted' } });
52
+					}))
47
 					.format('mp3')
53
 					.format('mp3')
48
 					.output(fs.createWriteStream(audioPath))
54
 					.output(fs.createWriteStream(audioPath))
49
 					.run();
55
 					.run();
57
 						Converted.update({ _id: id }, { $set: { video_progress: (percent * 100).toFixed(0) } });
63
 						Converted.update({ _id: id }, { $set: { video_progress: (percent * 100).toFixed(0) } });
58
 					}));
64
 					}));
59
 					res.on('end', Meteor.bindEnvironment(() => {
65
 					res.on('end', Meteor.bindEnvironment(() => {
60
-						Converted.update({ _id: id }, { $set: { video: videoPath, video_progress: 100 } });
66
+						Converted.update({ _id: id }, { $set: { video: filename + '.mp4', video_progress: 100 } });
67
+					}));
68
+					res.on('error', Meteor.bindEnvironment((err) => {
69
+						console.error('Error: ' + err.message)
70
+						Converted.update({ _id: id }, { $set: { error_video: 'Video cannot be saved' } });
61
 					}));
71
 					}));
62
-					res.on('error', (err) => { console.error('Error: ' + err.message) });
63
 				}));
72
 				}));
64
 			}
73
 			}
65
 		}));
74
 		}));
73
 	check(type, Match.Where(t => ['audio'].includes(t)));
82
 	check(type, Match.Where(t => ['audio'].includes(t)));
74
 
83
 
75
  	let converted = Converted.findOne({
84
  	let converted = Converted.findOne({
76
-		url: video.url
85
+		url: video.url,
86
+		error_audio: { $ne: null },
87
+		error_video: { $ne: null },
77
 	});
88
 	});
78
 
89
 
79
 	if (!converted) {
90
 	if (!converted) {
80
 		const filename = video.title.replace(/ /g, '_');
91
 		const filename = video.title.replace(/ /g, '_');
92
+		Converted.remove({url: video.url});
81
 		var inserted_id = Converted.insert({
93
 		var inserted_id = Converted.insert({
82
 			id: video.id,
94
 			id: video.id,
83
 			url: video.url,
95
 			url: video.url,
84
 			title: video.title,
96
 			title: video.title,
85
 			video: '',
97
 			video: '',
86
 			video_progress: 0,
98
 			video_progress: 0,
99
+			error_video: '',
87
 			audio: '',
100
 			audio: '',
88
 			audio_progress: 0,
101
 			audio_progress: 0,
102
+			error_audio: '',
89
 			createdAt: new Date()
103
 			createdAt: new Date()
90
 		});
104
 		});
91
 		convert(video.url, filename, inserted_id);
105
 		convert(video.url, filename, inserted_id);

+ 49
- 0
imports/startup/routes.js Visa fil

1
+import { Converted } from '../api/links';
2
+
3
+const uploadedPath = '/home/kod3/projects/SoundWave/uploaded/';
4
+
5
+Router.route('/');
6
+
7
+Router.route("/:type/batch/:ids", function() {
8
+	const fs = Meteor.npmRequire('fs');
9
+	const path = Meteor.npmRequire('path');
10
+
11
+	let { type, ids } = this.params;
12
+	ids = ids.split(',');
13
+	let file = (type === 'mp3' ? 'audio' : 'video');
14
+	let videos = Converted.find({ id: { $in: ids } }).fetch();
15
+	if (!videos.length) {
16
+		this.response.writeHead(404);
17
+		this.response.end("Files not found");
18
+	} else {
19
+		let zip = new JSZip();
20
+		_.each(videos, video => zip.file(video[file], fs.readFileSync(path.resolve(uploadedPath, video[file]))));
21
+		this.response.setHeader("Content-Type", "application/octet-stream");
22
+		this.response.setHeader("Content-disposition", `attachment; filename=soundwave-${type}.zip`);
23
+		this.response.writeHead(200);
24
+		this.response.end(zip.generate({ type: "nodebuffer", compression: "DEFLATE" }));
25
+	}
26
+}, { where: 'server'});
27
+
28
+
29
+Router.route("/:type/:id", function() {
30
+	const fs = Meteor.npmRequire('fs');
31
+	const path = Meteor.npmRequire('path');
32
+
33
+	let { type, id } = this.params;
34
+	let file = (type === 'mp3' ? 'audio' : 'video');
35
+	let video = Converted.findOne({ id: id });
36
+
37
+	if (!video) {
38
+		this.response.writeHead(404);
39
+		this.response.end("File not found");
40
+	} else {
41
+		const filePath = path.resolve(uploadedPath, video[file]);
42
+		let stat = fs.statSync(filePath);
43
+		this.response.writeHead(200, {
44
+			'Content-Type': (type === 'mp3' ? 'audio/mpeg' : 'video/mp4'),
45
+			'Content-Length': stat.size
46
+		});
47
+		fs.createReadStream(filePath).pipe(this.response);
48
+	}
49
+}, { where: 'server'});

+ 6
- 19
imports/ui/App.jsx Visa fil

1
 import React, { Component, PropTypes } from 'react';
1
 import React, { Component, PropTypes } from 'react';
2
 import { Meteor } from 'meteor/meteor';
2
 import { Meteor } from 'meteor/meteor';
3
-import { Tracker } from 'meteor/tracker';
4
 import { createContainer } from 'meteor/react-meteor-data';
3
 import { createContainer } from 'meteor/react-meteor-data';
5
 import { Converted } from '../api/links';
4
 import { Converted } from '../api/links';
6
 import Links from './Links';
5
 import Links from './Links';
15
 		super(props);
14
 		super(props);
16
 		this.state = { result: [], links: [], value: '' };
15
 		this.state = { result: [], links: [], value: '' };
17
 		this.options = [
16
 		this.options = [
18
-			{ type: 'MP3', handler: this.downloadAudio.bind(this) },
19
-			{ type: 'MP4', handler: this.downloadVideo.bind(this) },
17
+			{ type: 'MP3', handler: (video) => window.open('/mp3/'+ video.id) },
18
+			{ type: 'MP4', handler: (video) => window.open('/mp4/'+ video.id) },
20
 		];
19
 		];
21
 		this.optionsAll = [
20
 		this.optionsAll = [
22
-			{ type: 'MP3', handler: this.downloadAllAudio.bind(this) },
23
-			{ type: 'MP4', handler: this.downloadAllVideo.bind(this) },
21
+			{ type: 'MP3', handler: () => window.open(`/mp3/batch/${this.batchLinks()}`) },
22
+			{ type: 'MP4', handler: () => window.open(`/mp4/batch/${this.batchLinks()}`) },
24
 		];
23
 		];
25
 	}
24
 	}
26
 
25
 
41
 		);
40
 		);
42
 	}
41
 	}
43
 
42
 
44
-	downloadAudio(video) {
45
-		console.log('Requesting mp3 for ' + video.id);
46
-	}
47
-
48
-	downloadVideo(video) {
49
-		console.log('Requesting mp4 for ' + video.id);
50
-	}
51
-
52
-	downloadAllAudio() {
53
-		console.log('Requesting mp3 for all');
54
-	}
55
-
56
-	downloadAllVideo() {
57
-		console.log('Requesting mp4 for all');
43
+	batchLinks() {
44
+		return this.state.links.map(e => e.id).join(',');
58
 	}
45
 	}
59
 
46
 
60
 	inputChange(text) {
47
 	inputChange(text) {

+ 10
- 9
imports/ui/Options.jsx Visa fil

14
 			!this.props.converted[type === 'MP3' ? 'audio' : 'video'];
14
 			!this.props.converted[type === 'MP3' ? 'audio' : 'video'];
15
 	}
15
 	}
16
 
16
 
17
-	getClasses() {
18
-		return classnames({
19
-			option: true,
20
-			unreachable: this.props.converted.error
21
-		});
17
+	getClasses(type) {
18
+		let option = true;
19
+		let unreachable = null;
20
+		if (this.props.converted)
21
+			unreachable = (type === 'MP3' ? this.props.converted.error_audio : this.props.converted.error_video);
22
+		return classnames({ option, unreachable });
22
 	}
23
 	}
23
 
24
 
24
-	getErrorMessage() {
25
-		return this.props.converted.error || null;
25
+	getErrorMessage(type) {
26
+		return (type === 'MP3' ? this.props.converted.error_audio : this.props.converted.error_video) || null;
26
 	}
27
 	}
27
 
28
 
28
 	render() {
29
 	render() {
29
 		return (
30
 		return (
30
 			<div>
31
 			<div>
31
 				{ _.map(this.props.handlers, (h) => (
32
 				{ _.map(this.props.handlers, (h) => (
32
-					<button key={h.type} className={this.getClasses()}
33
+					<button key={h.type} className={this.getClasses(h.type)}
33
 							onClick={() => h.handler(this.props.video)}
34
 							onClick={() => h.handler(this.props.video)}
34
-							disabled={this.isDisabled(h.type)} title={this.getErrorMessage()}>
35
+							disabled={this.isDisabled(h.type)} title={this.getErrorMessage(h.type)}>
35
 						<div className="option-progress" style={{width: this.getProgress(h.type) + '%'}}></div>
36
 						<div className="option-progress" style={{width: this.getProgress(h.type) + '%'}}></div>
36
 						<i className="fa fa-download"/> {h.type}
37
 						<i className="fa fa-download"/> {h.type}
37
 					</button>
38
 					</button>

+ 0
- 1
imports/ui/Video.js Visa fil

18
 	update(stats, duration) {
18
 	update(stats, duration) {
19
 		for (count in stats)
19
 		for (count in stats)
20
 			this.stats[count] = numeral(stats[count]).format('0.[00]a');
20
 			this.stats[count] = numeral(stats[count]).format('0.[00]a');
21
-		console.log(duration);
22
 		const time = moment.utc(moment.duration(duration).asMilliseconds())
21
 		const time = moment.utc(moment.duration(duration).asMilliseconds())
23
 		this.duration = time.format(time.hours() ? 'k:mm:ss' : 'm:ss');
22
 		this.duration = time.format(time.hours() ? 'k:mm:ss' : 'm:ss');
24
 	}
23
 	}

+ 2
- 0
server/main.js Visa fil

1
 import { Meteor } from 'meteor/meteor';
1
 import { Meteor } from 'meteor/meteor';
2
 import '../imports/api/links.js';
2
 import '../imports/api/links.js';
3
+import '../imports/startup/routes';
3
 
4
 
4
 Meteor.startup(() => {
5
 Meteor.startup(() => {
5
   // code to run on server at startup
6
   // code to run on server at startup
6
 });
7
 });
8
+