[WIP] Run youtube-dl locally

This commit is contained in:
Preston 2018-03-26 16:28:52 -04:00
parent 3ea4ad4ddf
commit 13b628a99e
6 changed files with 117 additions and 90 deletions

View File

@ -58,13 +58,16 @@
"electron-winstaller": "^2.6.4"
},
"dependencies": {
"@joegesualdo/get-youtube-subtitles-node": "^0.1.0",
"autolinker": "^1.6.2",
"dashjs": "^2.6.7",
"dateformat": "^3.0.3",
"electron-compile": "^6.4.2",
"electron-squirrel-startup": "^1.0.0",
"jquery": "^3.3.1",
"mustache": "^2.3.0",
"nedb": "^1.8.0",
"opml-to-json": "0.0.3"
"opml-to-json": "0.0.3",
"ytdl-core": "^0.20.2"
}
}

View File

@ -36,6 +36,7 @@ const electron = require('electron');
// is rewritten.
//const asyncLoop = require('node-async-loop');
//const youtubedl = require('youtube-dl');
const ytdl = require('ytdl-core');
const shell = electron.shell; // Used to open external links into the user's native browser.
const localDataStorage = electron.remote.app.getPath('userData'); // Grabs the userdata directory based on the user's OS
const clipboard = electron.clipboard;

View File

@ -28,7 +28,7 @@ along with FreeTube. If not, see <http://www.gnu.org/licenses/>.
*
* @return {Void}
*/
function playVideo(videoId) {
function playVideo(videoId, videoThumbnail = '') {
clearMainContainer();
startLoadingAnimation();
@ -42,32 +42,20 @@ function playVideo(videoId) {
let subtitleHtml = '';
let subtitleLabel;
let subtitleLanguage;
let subtitleCode;
let subtitleUrl;
let defaultUrl;
let defaultQuality;
let channelId;
let videoHtml;
let videoThumbnail;
let videoType = 'video';
let embedPlayer;
let embedPlayer = "<iframe width='560' height='315' src='https://www.youtube-nocookie.com/embed/" + videoId + "?rel=0' frameborder='0' allow='autoplay; encrypted-media' allowfullscreen></iframe>";
let useEmbedPlayer = false;
let validUrl;
// Grab the embeded player. Used as fallback if the video URL cannot be found.
// Also grab the channel ID.
try {
let getInfoFunction = getChannelAndPlayer(videoId);
getInfoFunction.then((data) => {
console.log(data);
embedPlayer = data[0];
channelId = data[1];
});
} catch (ex) {
showToast('Video not found. ID may be invalid.');
stopLoadingAnimation();
return;
}
let videoLikes;
let videoDislikes;
let totalLikes;
let likePercentage;
const checkSavedVideo = videoIsSaved(videoId);
@ -82,59 +70,85 @@ function playVideo(videoId) {
}
});
youtubeAPI('videos', {
part: 'statistics',
id: videoId,
}, function(data) {
console.log(data);
// Figure out the width for the like/dislike bar.
videoLikes = data['items'][0]['statistics']['likeCount'];
videoDislikes = data['items'][0]['statistics']['dislikeCount'];
totalLikes = parseInt(videoLikes) + parseInt(videoDislikes);
likePercentage = parseInt((videoLikes / totalLikes) * 100);
});
var getYoutubeSubtitles = require('@joegesualdo/get-youtube-subtitles-node');
getYoutubeSubtitles(videoId, {type: 'auto'})
.then(subtitles => {
console.log(subtitles)
})
.catch(err => {
console.log(err)
});
/*
* FreeTube calls youtube-dl to grab the direct video URL.
*/
youtubedlGetInfo(videoId, (info) => {
console.log(info);
videoThumbnail = info['thumbnail'];
console.log(videoLikes);
channelId = info['author']['id'];
let channelThumbnail = info['author']['avatar'];
let videoUrls = info['formats'];
// Add commas to the video view count.
const videoViews = info['view_count'].toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
// Format the date to a more readable format.
let dateString = info['upload_date'];
dateString = [dateString.slice(0, 4), '-', dateString.slice(4)].join('');
dateString = [dateString.slice(0, 7), '-', dateString.slice(7)].join('');
let dateString = new Date(info['published']);
dateString.setDate(dateString.getDate() + 1);
const publishedDate = dateFormat(dateString, "mmm dS, yyyy");
// Figure out the width for the like/dislike bar.
const videoLikes = info['like_count'];
const videoDislikes = info['dislike_count'];
const totalLikes = videoLikes + videoDislikes;
const likePercentage = parseInt((videoLikes / totalLikes) * 100);
let description = info['description'];
// Adds clickable links to the description.
description = autolinker.link(description);
if (info['requested_subtitles'] !== null) {
videoSubtitles = info['requested_subtitles'];
/*if (typeof(info['player_response']['captions']['playerCaptionsTracklistRenderer']['captionTracks'] !== 'undefined')) {
videoSubtitles = info['player_response']['captions']['playerCaptionsTracklistRenderer']['captionTracks'];
console.log(videoSubtitles);
// Grab all subtitles
Object.keys(videoSubtitles).forEach((subtitle) => {
//"https://www.youtube.com/api/timedtext?expire=1522055009&v=tdeueWbTr3s&sparams=asr_langs%2Ccaps%2Cv%2Cexpire&signature=2C258351C4497D0A82DDB3C1E61AFD2F153FF94B.BE777570340A6B725647DD00373A58D5126CFC46&asr_langs=de%2Cko%2Cja%2Cfr%2Cen%2Ces%2Cru%2Cnl%2Cit%2Cpt&key=yttt1&hl=en_US&caps=asr&lang=ar"
//https://www.youtube.com/api/timedtext?expire=1522055009&v=tdeueWbTr3s&sparams=asr_langs%2Ccaps%2Cv%2Cexpire&signature=2C258351C4497D0A82DDB3C1E61AFD2F153FF94B.BE777570340A6B725647DD00373A58D5126CFC46&asr_langs=de%2Cko%2Cja%2Cfr%2Cen%2Ces%2Cru%2Cnl%2Cit%2Cpt&key=yttt1&hl=en_US&caps=asr&lang=ar
subtitleLabel = subtitle.toUpperCase();
subtitleUrl = videoSubtitles[subtitle]['url'];
subtitleLabel = videoSubtitles[subtitle]['name']['simpleText'];
subtitleCode = videoSubtitles[subtitle]['languageCode'];
subtitleUrl = videoSubtitles[subtitle]['baseUrl'];
console.log(subtitleUrl);
if (subtitle === 'en') {
subtitleHtml = subtitleHtml + '<track label="' + subtitleLabel + '" kind="subtitles" srclang="' + subtitle + '" src="' + subtitleUrl + '" default>';
subtitleHtml = subtitleHtml + '<track label="' + subtitleLabel + '" kind="captions" srclang="' + subtitleCode + '" src="' + subtitleUrl + '" default>';
} else {
subtitleHtml = subtitleHtml + '<track label="' + subtitleLabel + '" kind="subtitles" srclang="' + subtitle + '" src="' + subtitleUrl + '">';
subtitleHtml = subtitleHtml + '<track label="' + subtitleLabel + '" kind="captions" srclang="' + subtitleCode + '" src="' + subtitleUrl + '">';
}
});
}
}*/
// Search through the returned object to get the 480p and 720p video URLs (If available)
Object.keys(videoUrls).forEach((key) => {
switch (videoUrls[key]['format_id']) {
switch (videoUrls[key]['itag']) {
case '18':
video480p = videoUrls[key]['url'];
console.log(video480p);
break;
case '22':
video720p = videoUrls[key]['url'];
console.log(video720p);
break;
}
});
@ -158,62 +172,62 @@ function playVideo(videoId) {
}
if (!useEmbedPlayer) {
videoHtml = '<video class="videoPlayer" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + defaultUrl + '" poster="' + videoThumbnail + '" autoplay>' + subtitleHtml + '</video>';
videoHtml = '<video data-dashjs-player class="videoPlayer" type="application/x-mpegURL" onmousemove="hideMouseTimeout()" onmouseleave="removeMouseTimeout()" controls="" src="' + defaultUrl + '" poster="' + videoThumbnail + '" autoplay>' + subtitleHtml + '</video>';
}
const checkSubscription = isSubscribed(channelId);
// Change the subscribe button text depending on if the user has subscribed to the channel or not.
checkSubscription.then((results) => {
const subscribeButton = document.getElementById('subscribeButton');
if (results === false) {
subscribeText = 'SUBSCRIBE';
if (subscribeButton != null) {
subscribeButton.innerHTML = 'SUBSCRIBE';
}
} else {
subscribeText = 'UNSUBSCRIBE';
if (subscribeButton != null) {
subscribeButton.innerHTML = 'UNSUBSCRIBE';
}
}
});
// API Request
youtubeAPI('channels', {
'id': channelId,
'part': 'snippet'
}, function(data) {
const channelThumbnail = data['items'][0]['snippet']['thumbnails']['high']['url'];
const playerTemplate = require('./templates/player.html')
mustache.parse(playerTemplate);
const rendered = mustache.render(playerTemplate, {
videoHtml: videoHtml,
videoQuality: defaultQuality,
videoTitle: info['title'],
videoViews: videoViews,
videoThumbnail: videoThumbnail,
channelName: info['uploader'],
videoLikes: videoLikes,
videoDislikes: videoDislikes,
likePercentage: likePercentage,
videoId: videoId,
channelId: channelId,
channelIcon: channelThumbnail,
publishedDate: publishedDate,
description: description,
isSubscribed: subscribeText,
savedText: savedText,
savedIconClass: savedIconClass,
savedIconColor: savedIconColor,
video480p: video480p,
video720p: video720p,
embedPlayer: embedPlayer,
});
$('#main').html(rendered);
stopLoadingAnimation();
if (info['requested_subtitles'] !== null) {
$('.videoPlayer').get(0).textTracks[0].mode = 'hidden';
}
showVideoRecommendations(videoId);
console.log('done');
const playerTemplate = require('./templates/player.html')
mustache.parse(playerTemplate);
const rendered = mustache.render(playerTemplate, {
videoHtml: videoHtml,
videoQuality: defaultQuality,
videoTitle: info['title'],
videoViews: videoViews,
videoThumbnail: videoThumbnail,
channelName: info['author']['name'],
videoLikes: videoLikes,
videoDislikes: videoDislikes,
likePercentage: likePercentage,
videoId: videoId,
channelId: channelId,
channelIcon: channelThumbnail,
publishedDate: publishedDate,
description: description,
isSubscribed: subscribeText,
savedText: savedText,
savedIconClass: savedIconClass,
savedIconColor: savedIconColor,
video480p: video480p,
video720p: video720p,
embedPlayer: embedPlayer,
});
$('#main').html(rendered);
stopLoadingAnimation();
/*if (info['requested_subtitles'] !== null) {
$('.videoPlayer').get(0).textTracks[0].mode = 'hidden';
}*/
showVideoRecommendations(videoId);
console.log('done');
// Sometimes a video URL is found, but the video will not play. I believe the issue is
// that the video has yet to render for that quality, as the video will be available at a later time.
// This will check the URLs and switch video sources if there is an error.

View File

@ -79,6 +79,7 @@ function removeSubscription(channelId) {
*/
function loadSubscriptions() {
clearMainContainer();
showToast('Getting Subscriptions. This may take a while...');
const loading = document.getElementById('loading');
startLoadingAnimation()
@ -128,6 +129,7 @@ function loadSubscriptions() {
list.items.forEach((video) => {
displayVideo(video);
});
stopLoadingAnimation();
});
} else {
console.log(videoList);

View File

@ -31,16 +31,23 @@ function youtubeAPI(resource, params, success) {
* @return {Void}
*/
function youtubedlGetInfo(videoId, callback) {
let url = 'https://stormy-inlet-41826.herokuapp.com/api/info?url=https://www.youtube.com/watch?v=' + videoId + 'flatten=True&writesubtitles=True&geo_bypass=True';
/*let url = 'https://stormy-inlet-41826.herokuapp.com/api/info?url=https://www.youtube.com/watch?v=' + videoId + 'flatten=True&writesubtitles=True&geo_bypass=true';
$.getJSON(url, (response) => {
callback(response.info);
});
//https://stormy-inlet-41826.herokuapp.com/api/info?url=https://youtube.com/watch?v=fc6ODCqepb8flatten=True&writesubtitles=True&geo_bypass=True&write_auto_sub=true&sub_lang=zh-TW
//https://www.youtube.com/watch?v=8YoUxe5ncPo
//https://youtube.com/watch?v=fc6ODCqepb8*/
let url = 'https://youtube.com/watch?v=' + videoId;
let options = ['--all-subs', '--write-subs'];
/*var dashjs = require('dashjs');
var url = "http://dash.edgesuite.net/envivio/EnvivioDash3/manifest.mpd";
var player = dashjs.MediaPlayer().create();
player.initialize(document.querySelector("#videoPlayer"), url, true);*/
/*let url = 'https://youtube.com/watch?v=' + videoId;
let options = ['--all-subs'];
youtubedl.getInfo(url, options, function(err, info) {
ytdl.getInfo(url, options, function(err, info) {
if (err){
showToast('There was an issue calling youtube-dl.');
stopLoadingAnimation();
@ -50,5 +57,5 @@ function youtubedlGetInfo(videoId, callback) {
console.log('Success');
callback(info);
});*/
});
}

View File

@ -9,7 +9,7 @@
</ul>
</div>
<div class='videoThumbnail'>
<img onclick='playVideo("{{videoId}}")' src={{videoThumbnail}} />
<img onclick='playVideo("{{videoId}}", "{{videoThumbnail}}")' src={{videoThumbnail}} />
<p onclick='playVideo("{{videoId}}")' class='videoDuration'>{{videoDuration}}</p>
</div>
<p onclick='playVideo("{{videoId}}")' class='videoTitle'>{{videoTitle}}</p>