Merge pull request #14 from Aasim-A/xml2vtt
Added yt-xml2vtt and did the corresponding implementation
This commit is contained in:
commit
f429e346cf
|
@ -33,10 +33,12 @@ module.exports = {
|
||||||
plugins: ['vue'],
|
plugins: ['vue'],
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
'vue/no-v-html': "off",
|
'space-before-function-paren': 0,
|
||||||
|
'comma-dangle': 0,
|
||||||
|
'vue/no-v-html': 'off',
|
||||||
'no-console': 0,
|
'no-console': 0,
|
||||||
'no-unused-vars': 1,
|
'no-unused-vars': 1,
|
||||||
'no-undef': 1,
|
'no-undef': 1,
|
||||||
'vue/no-template-key': 1
|
'vue/no-template-key': 1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -37,7 +37,7 @@
|
||||||
"youtube-comments-fetch": "^1.0.1",
|
"youtube-comments-fetch": "^1.0.1",
|
||||||
"youtube-comments-task": "^1.3.14",
|
"youtube-comments-task": "^1.3.14",
|
||||||
"youtube-suggest": "^1.1.0",
|
"youtube-suggest": "^1.1.0",
|
||||||
"yt-xml2srt": "^1.1.0",
|
"yt-xml2vtt": "^1.0.0",
|
||||||
"ytdl-core": "^2.0.0",
|
"ytdl-core": "^2.0.0",
|
||||||
"ytpl": "^0.1.20",
|
"ytpl": "^0.1.20",
|
||||||
"ytsr": "^0.1.10"
|
"ytsr": "^0.1.10"
|
||||||
|
@ -73,6 +73,7 @@
|
||||||
"fast-glob": "^3.2.2",
|
"fast-glob": "^3.2.2",
|
||||||
"file-loader": "^5.1.0",
|
"file-loader": "^5.1.0",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
"jest": "^25.1.0",
|
||||||
"mini-css-extract-plugin": "^0.9.0",
|
"mini-css-extract-plugin": "^0.9.0",
|
||||||
"node-loader": "^0.6.0",
|
"node-loader": "^0.6.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
|
import xml2vtt from 'yt-xml2vtt'
|
||||||
|
import $ from 'jquery'
|
||||||
import FtLoader from '../../components/ft-loader/ft-loader.vue'
|
import FtLoader from '../../components/ft-loader/ft-loader.vue'
|
||||||
import FtCard from '../../components/ft-card/ft-card.vue'
|
import FtCard from '../../components/ft-card/ft-card.vue'
|
||||||
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
|
import FtElementList from '../../components/ft-element-list/ft-element-list.vue'
|
||||||
|
@ -18,9 +20,9 @@ export default Vue.extend({
|
||||||
'watch-video-info': WatchVideoInfo,
|
'watch-video-info': WatchVideoInfo,
|
||||||
'watch-video-description': WatchVideoDescription,
|
'watch-video-description': WatchVideoDescription,
|
||||||
'watch-video-comments': WatchVideoComments,
|
'watch-video-comments': WatchVideoComments,
|
||||||
'watch-video-recommendations': WatchVideoRecommendations
|
'watch-video-recommendations': WatchVideoRecommendations,
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
firstLoad: true,
|
firstLoad: true,
|
||||||
|
@ -47,44 +49,46 @@ export default Vue.extend({
|
||||||
audioUrl: '',
|
audioUrl: '',
|
||||||
videoSourceList: [],
|
videoSourceList: [],
|
||||||
captionSourceList: [],
|
captionSourceList: [],
|
||||||
recommendedVideos: []
|
recommendedVideos: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
backendPreference: function () {
|
backendPreference: function() {
|
||||||
return this.$store.getters.getBackendPreference
|
return this.$store.getters.getBackendPreference
|
||||||
},
|
},
|
||||||
|
|
||||||
backendFallback: function () {
|
backendFallback: function() {
|
||||||
return this.$store.getters.getBackendFallback
|
return this.$store.getters.getBackendFallback
|
||||||
},
|
},
|
||||||
|
|
||||||
invidiousInstance: function () {
|
invidiousInstance: function() {
|
||||||
return this.$store.getters.getInvidiousInstance
|
return this.$store.getters.getInvidiousInstance
|
||||||
},
|
},
|
||||||
|
|
||||||
videoFormatPreference: function () {
|
videoFormatPreference: function() {
|
||||||
return this.$store.getters.getVideoFormatPreference
|
return this.$store.getters.getVideoFormatPreference
|
||||||
},
|
},
|
||||||
|
|
||||||
videoDashUrl: function () {
|
videoDashUrl: function() {
|
||||||
return `${this.invidiousInstance}/api/manifest/dash/id/${this.videoId}.mpd`
|
return `${this.invidiousInstance}/api/manifest/dash/id/${this.videoId}.mpd`
|
||||||
},
|
},
|
||||||
|
|
||||||
youtubeNoCookieEmbeddedFrame: function () {
|
youtubeNoCookieEmbeddedFrame: function() {
|
||||||
return `<iframe width='560' height='315' src='https://www.youtube-nocookie.com/embed/${this.videoId}?rel=0' frameborder='0' allow='autoplay; encrypted-media' allowfullscreen></iframe>`
|
return `<iframe width='560' height='315' src='https://www.youtube-nocookie.com/embed/${this.videoId}?rel=0' frameborder='0' allow='autoplay; encrypted-media' allowfullscreen></iframe>`
|
||||||
},
|
},
|
||||||
|
|
||||||
dashSrc: function () {
|
dashSrc: function() {
|
||||||
return [{
|
return [
|
||||||
url: `${this.invidiousInstance}/api/manifest/dash/${this.videoId}.mpd`,
|
{
|
||||||
type: 'application/dash+xml',
|
url: `${this.invidiousInstance}/api/manifest/dash/${this.videoId}.mpd`,
|
||||||
label: 'Dash'
|
type: 'application/dash+xml',
|
||||||
}]
|
label: 'Dash',
|
||||||
}
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
$route () {
|
$route() {
|
||||||
// react to route changes...
|
// react to route changes...
|
||||||
this.videoId = this.$route.params.id
|
this.videoId = this.$route.params.id
|
||||||
|
|
||||||
|
@ -98,9 +102,9 @@ export default Vue.extend({
|
||||||
this.getVideoInformationInvidious(this.videoId)
|
this.getVideoInformationInvidious(this.videoId)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function() {
|
||||||
this.videoId = this.$route.params.id
|
this.videoId = this.$route.params.id
|
||||||
this.videoStoryboardSrc = `${this.invidiousInstance}/api/v1/storyboards/${this.videoId}?height=90`
|
this.videoStoryboardSrc = `${this.invidiousInstance}/api/v1/storyboards/${this.videoId}?height=90`
|
||||||
|
|
||||||
|
@ -120,125 +124,130 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleTheatreMode: function () {
|
toggleTheatreMode: function() {
|
||||||
this.useTheatreMode = !this.useTheatreMode
|
this.useTheatreMode = !this.useTheatreMode
|
||||||
},
|
},
|
||||||
|
|
||||||
getVideoInformationLocal: function () {
|
getVideoInformationLocal: function() {
|
||||||
if (this.firstLoad) {
|
if (this.firstLoad) {
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$store.dispatch('ytGetVideoInformation', this.videoId).then((result) => {
|
this.$store
|
||||||
console.log(result)
|
.dispatch('ytGetVideoInformation', this.videoId)
|
||||||
|
.then(result => {
|
||||||
|
this.videoTitle = result.title
|
||||||
|
this.videoViewCount = parseInt(
|
||||||
|
result.player_response.videoDetails.viewCount,
|
||||||
|
10
|
||||||
|
)
|
||||||
|
this.channelId = result.author.id
|
||||||
|
this.channelName = result.author.name
|
||||||
|
this.channelThumbnail = result.author.avatar
|
||||||
|
this.videoPublished = result.published
|
||||||
|
this.videoDescription =
|
||||||
|
result.player_response.videoDetails.shortDescription
|
||||||
|
this.recommendedVideos = result.related_videos
|
||||||
|
this.videoSourceList = result.player_response.streamingData.formats
|
||||||
|
this.videoLikeCount = result.likes
|
||||||
|
this.videoDislikeCount = result.dislikes
|
||||||
|
|
||||||
this.videoTitle = result.title
|
// The response provides a storyboard, however it returns a 403 error.
|
||||||
this.videoViewCount = parseInt(result.player_response.videoDetails.viewCount)
|
// Uncomment this line if that ever changes.
|
||||||
this.channelId = result.author.id
|
// this.videoStoryboardSrc = result.player_response.storyboards.playerStoryboardSpecRenderer.spec
|
||||||
this.channelName = result.author.name
|
|
||||||
this.channelThumbnail = result.author.avatar
|
|
||||||
this.videoPublished = result.published
|
|
||||||
this.videoDescription = result.player_response.videoDetails.shortDescription
|
|
||||||
this.recommendedVideos = result.related_videos
|
|
||||||
this.videoSourceList = result.player_response.streamingData.formats
|
|
||||||
this.videoLikeCount = result.likes
|
|
||||||
this.videoDislikeCount = result.dislikes
|
|
||||||
|
|
||||||
// The response provides a storyboard, however it returns a 403 error.
|
this.captionSourceList =
|
||||||
// Uncomment this line if that ever changes.
|
result.player_response.captions &&
|
||||||
// this.videoStoryboardSrc = result.player_response.storyboards.playerStoryboardSpecRenderer.spec
|
result.player_response.captions.playerCaptionsTracklistRenderer
|
||||||
|
.captionTracks
|
||||||
|
|
||||||
this.captionSourceList = result.player_response.captions.playerCaptionsTracklistRenderer.captionTracks
|
if (typeof this.captionSourceList !== 'undefined') {
|
||||||
|
this.captionSourceList = this.captionSourceList.map(caption => {
|
||||||
|
caption.type = 'text/vtt'
|
||||||
|
caption.charset = 'charset=utf-8'
|
||||||
|
caption.dataSource = 'local'
|
||||||
|
|
||||||
if (typeof (this.captionSourceList) !== 'undefined') {
|
$.get(caption.baseUrl, response => {
|
||||||
this.captionSourceList = this.captionSourceList.map((caption) => {
|
xml2vtt
|
||||||
caption.baseUrl = `${this.invidiousInstance}/api/v1/captions/${this.videoId}?label=${encodeURI(caption.name.simpleText)}`
|
.Parse(new XMLSerializer().serializeToString(response))
|
||||||
|
.then(vtt => {
|
||||||
|
caption.baseUrl = `data:${caption.type};${caption.charset},${vtt}`
|
||||||
|
})
|
||||||
|
.catch(err =>
|
||||||
|
console.log(`Error while converting XML to VTT : ${err}`)
|
||||||
|
)
|
||||||
|
}).fail((xhr, textStatus, error) => {
|
||||||
|
console.log(xhr)
|
||||||
|
console.log(textStatus)
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
|
||||||
|
return caption
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoading = false
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
if (this.backendPreference === 'local' && this.backendFallback) {
|
||||||
|
console.log(
|
||||||
|
'Error getting data with local backend, falling back to Invidious'
|
||||||
|
)
|
||||||
|
this.getVideoInformationInvidious()
|
||||||
|
} else {
|
||||||
|
this.isLoading = false
|
||||||
|
// TODO: Show toast with error message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
getVideoInformationInvidious: function() {
|
||||||
|
if (this.firstLoad) {
|
||||||
|
this.isLoading = true
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$store
|
||||||
|
.dispatch('invidiousGetVideoInformation', this.videoId)
|
||||||
|
.then(result => {
|
||||||
|
console.log(result)
|
||||||
|
|
||||||
|
this.videoTitle = result.title
|
||||||
|
this.videoViewCount = result.viewCount
|
||||||
|
this.videoLikeCount = result.likeCount
|
||||||
|
this.videoDislikeCount = result.dislikeCount
|
||||||
|
this.channelSubscriptionCountText = result.subCountText
|
||||||
|
this.channelId = result.authorId
|
||||||
|
this.channelName = result.author
|
||||||
|
this.channelThumbnail = result.authorThumbnails[1].url
|
||||||
|
this.videoPublished = result.published * 1000
|
||||||
|
this.videoDescriptionHtml = result.descriptionHtml
|
||||||
|
this.recommendedVideos = result.recommendedVideos
|
||||||
|
this.videoSourceList = result.formatStreams.reverse()
|
||||||
|
this.captionSourceList = result.captions.map(caption => {
|
||||||
|
caption.url = this.invidiousInstance + caption.url
|
||||||
|
caption.type = ''
|
||||||
|
caption.dataSource = 'invidious'
|
||||||
return caption
|
return caption
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: The response returns the captions of the video, however they're returned
|
|
||||||
// in XML / TTML. I haven't found a way to properly convert this for use.
|
|
||||||
// There may be another URL that we can use to grab an appropriate format as well.
|
|
||||||
// Video.js requires that the captions are returned in .vtt format. The below code
|
|
||||||
// Converts it to .srt which may work, but I can't get the player to accept the data.
|
|
||||||
|
|
||||||
// this.captionSourceList = this.captionSourceList.map((caption) => {
|
|
||||||
// caption.type = 'application/ttml+xml'
|
|
||||||
// caption.dataSource = 'local'
|
|
||||||
//
|
|
||||||
// $.get(caption.baseUrl, (response) => {
|
|
||||||
// console.log('response')
|
|
||||||
// console.log(response)
|
|
||||||
// console.log()
|
|
||||||
// xml2srt.Parse(new XMLSerializer().serializeToString(response))
|
|
||||||
// .then(srt => {
|
|
||||||
// caption.track = srt
|
|
||||||
// }).catch(err => console.log(`Error while converting XML to SRT : ${err}`))
|
|
||||||
// }).fail((xhr, textStatus, error) => {
|
|
||||||
// console.log(xhr)
|
|
||||||
// console.log(textStatus)
|
|
||||||
// console.log(error)
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// return caption
|
|
||||||
// })
|
|
||||||
|
|
||||||
this.isLoading = false
|
|
||||||
}).catch((err) => {
|
|
||||||
console.log(err)
|
|
||||||
if (this.backendPreference === 'local' && this.backendFallback) {
|
|
||||||
console.log('Error getting data with local backend, falling back to Invidious')
|
|
||||||
this.getVideoInformationInvidious()
|
|
||||||
} else {
|
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
// TODO: Show toast with error message
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
getVideoInformationInvidious: function () {
|
|
||||||
if (this.firstLoad) {
|
|
||||||
this.isLoading = true
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$store.dispatch('invidiousGetVideoInformation', this.videoId).then((result) => {
|
|
||||||
console.log(result)
|
|
||||||
|
|
||||||
this.videoTitle = result.title
|
|
||||||
this.videoViewCount = result.viewCount
|
|
||||||
this.videoLikeCount = result.likeCount
|
|
||||||
this.videoDislikeCount = result.dislikeCount
|
|
||||||
this.channelSubscriptionCountText = result.subCountText
|
|
||||||
this.channelId = result.authorId
|
|
||||||
this.channelName = result.author
|
|
||||||
this.channelThumbnail = result.authorThumbnails[1].url
|
|
||||||
this.videoPublished = result.published * 1000
|
|
||||||
this.videoDescriptionHtml = result.descriptionHtml
|
|
||||||
this.recommendedVideos = result.recommendedVideos
|
|
||||||
this.videoSourceList = result.formatStreams.reverse()
|
|
||||||
this.captionSourceList = result.captions.map((caption) => {
|
|
||||||
caption.url = this.invidiousInstance + caption.url
|
|
||||||
caption.type = ''
|
|
||||||
caption.dataSource = 'invidious'
|
|
||||||
return caption
|
|
||||||
})
|
})
|
||||||
|
.catch(err => {
|
||||||
this.isLoading = false
|
console.log(err)
|
||||||
}).catch((err) => {
|
if (this.backendPreference === 'invidious' && this.backendFallback) {
|
||||||
console.log(err)
|
console.log(
|
||||||
if (this.backendPreference === 'invidious' && this.backendFallback) {
|
'Error getting data with Invidious, falling back to local backend'
|
||||||
console.log('Error getting data with Invidious, falling back to local backend')
|
)
|
||||||
this.getVideoInformationLocal()
|
this.getVideoInformationLocal()
|
||||||
} else {
|
} else {
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
// TODO: Show toast with error message
|
// TODO: Show toast with error message
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
enableDashFormat: function () {
|
enableDashFormat: function() {
|
||||||
if (this.activeFormat === 'dash') {
|
if (this.activeFormat === 'dash') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -246,10 +255,12 @@ export default Vue.extend({
|
||||||
this.activeFormat = 'dash'
|
this.activeFormat = 'dash'
|
||||||
this.hidePlayer = true
|
this.hidePlayer = true
|
||||||
|
|
||||||
setTimeout(() => { this.hidePlayer = false }, 100)
|
setTimeout(() => {
|
||||||
|
this.hidePlayer = false
|
||||||
|
}, 100)
|
||||||
},
|
},
|
||||||
|
|
||||||
enableLegacyFormat: function () {
|
enableLegacyFormat: function() {
|
||||||
if (this.activeFormat === 'legacy') {
|
if (this.activeFormat === 'legacy') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -262,16 +273,18 @@ export default Vue.extend({
|
||||||
}, 100)
|
}, 100)
|
||||||
},
|
},
|
||||||
|
|
||||||
handleVideoError: function (error) {
|
handleVideoError: function(error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
if (error.code === 4) {
|
if (error.code === 4) {
|
||||||
if (this.activeFormat === 'dash') {
|
if (this.activeFormat === 'dash') {
|
||||||
console.log('Unable to play dash formats. Reverting to legacy formats...')
|
console.log(
|
||||||
|
'Unable to play dash formats. Reverting to legacy formats...'
|
||||||
|
)
|
||||||
this.enableLegacyFormat()
|
this.enableLegacyFormat()
|
||||||
} else {
|
} else {
|
||||||
this.enableDashFormat()
|
this.enableDashFormat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue