Benoit Sida 3 년 전
부모
커밋
fe371b8ecd
9개의 변경된 파일100개의 추가작업 그리고 37개의 파일을 삭제
  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 파일 보기

@@ -29,3 +29,5 @@ meteorhacks:npm
29 29
 
30 30
 npm-container
31 31
 momentjs:moment
32
+iron:router
33
+udondan:jszip

+ 9
- 0
.meteor/versions 파일 보기

@@ -36,6 +36,14 @@ html-tools@1.0.11
36 36
 htmljs@1.0.11
37 37
 http@1.2.11
38 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 47
 jquery@1.11.10
40 48
 launch-screen@1.1.1
41 49
 less@2.7.9
@@ -86,6 +94,7 @@ templating-runtime@1.3.0
86 94
 templating-tools@1.1.0
87 95
 tmeasday:check-npm-versions@0.2.0
88 96
 tracker@1.1.2
97
+udondan:jszip@2.4.0_1
89 98
 ui@1.0.12
90 99
 underscore@1.0.10
91 100
 url@1.1.0

+ 2
- 2
client/main.js 파일 보기

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

+ 20
- 6
imports/api/links.js 파일 보기

@@ -27,7 +27,10 @@ const convert = (url, filename, id) => {
27 27
 			if (err) {
28 28
 				console.log(url);
29 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 34
 			} else {
32 35
 				const totalSeconds = info ? info.length_seconds : 0;
33 36
 				const dl = ytdl(url);
@@ -35,7 +38,7 @@ const convert = (url, filename, id) => {
35 38
 				ffmpeg(dl)
36 39
 					.noVideo()
37 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 43
 					.on('progress', Meteor.bindEnvironment((params) => {
41 44
 						const time = moment(new Date(...[0, 0, 0, ...params.timemark.split(':')]));
@@ -43,7 +46,10 @@ const convert = (url, filename, id) => {
43 46
 						let percent = ((seconds / totalSeconds) * 100).toFixed(0);
44 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 53
 					.format('mp3')
48 54
 					.output(fs.createWriteStream(audioPath))
49 55
 					.run();
@@ -57,9 +63,12 @@ const convert = (url, filename, id) => {
57 63
 						Converted.update({ _id: id }, { $set: { video_progress: (percent * 100).toFixed(0) } });
58 64
 					}));
59 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,19 +82,24 @@ Meteor.methods({
73 82
 	check(type, Match.Where(t => ['audio'].includes(t)));
74 83
 
75 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 90
 	if (!converted) {
80 91
 		const filename = video.title.replace(/ /g, '_');
92
+		Converted.remove({url: video.url});
81 93
 		var inserted_id = Converted.insert({
82 94
 			id: video.id,
83 95
 			url: video.url,
84 96
 			title: video.title,
85 97
 			video: '',
86 98
 			video_progress: 0,
99
+			error_video: '',
87 100
 			audio: '',
88 101
 			audio_progress: 0,
102
+			error_audio: '',
89 103
 			createdAt: new Date()
90 104
 		});
91 105
 		convert(video.url, filename, inserted_id);

+ 49
- 0
imports/startup/routes.js 파일 보기

@@ -0,0 +1,49 @@
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 파일 보기

@@ -1,6 +1,5 @@
1 1
 import React, { Component, PropTypes } from 'react';
2 2
 import { Meteor } from 'meteor/meteor';
3
-import { Tracker } from 'meteor/tracker';
4 3
 import { createContainer } from 'meteor/react-meteor-data';
5 4
 import { Converted } from '../api/links';
6 5
 import Links from './Links';
@@ -15,12 +14,12 @@ class App extends Component {
15 14
 		super(props);
16 15
 		this.state = { result: [], links: [], value: '' };
17 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 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,20 +40,8 @@ class App extends Component {
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 47
 	inputChange(text) {

+ 10
- 9
imports/ui/Options.jsx 파일 보기

@@ -14,24 +14,25 @@ export default class Options extends Component {
14 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 29
 	render() {
29 30
 		return (
30 31
 			<div>
31 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 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 36
 						<div className="option-progress" style={{width: this.getProgress(h.type) + '%'}}></div>
36 37
 						<i className="fa fa-download"/> {h.type}
37 38
 					</button>

+ 0
- 1
imports/ui/Video.js 파일 보기

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

+ 2
- 0
server/main.js 파일 보기

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