Last active
March 31, 2026 20:19
-
-
Save UCyborg/318f4e28fc94b6c7b4735592be038935 to your computer and use it in GitHub Desktop.
AVD 2.4.7.50 - Patched yt.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // yt.js, 2013-2016 | |
| // author ICh | |
| // contributor ED | |
| // namespace antvd | |
| // | |
| var antvd = (function(antvd) | |
| { | |
| if (!antvd.AntLib) | |
| { | |
| antvd.AntLib = AntLib; | |
| } | |
| Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); | |
| Components.utils.import("resource://gre/modules/Downloads.jsm"); | |
| Components.utils.import("resource://gre/modules/Task.jsm"); | |
| Components.utils.import("resource://gre/modules/FileUtils.jsm"); | |
| Components.utils.import("resource://gre/modules/Promise.jsm"); | |
| Components.utils.import("resource://gre/modules/NetUtil.jsm"); | |
| const au = "AUDIO"; | |
| const vi = "VIDEO"; | |
| const av = "COMPLETE"; | |
| // class YtVideoRequest | |
| var YtVideoRequest = function() {}; | |
| YtVideoRequest.prototype = | |
| { | |
| get displayName() | |
| { | |
| return this._base.displayName; | |
| }, | |
| get size() | |
| { | |
| return this._base.size; | |
| }, | |
| // type YtStream | |
| _video: null, | |
| _audio: null, | |
| _id: null, | |
| _tag: null, | |
| // since 2.4.7.23 | |
| // type MediaRequest | |
| _base: null, | |
| // param {YtStream} video | |
| // param {YtStream} audio | |
| init: function(video, audio) | |
| { | |
| this._base = new antvd.MediaRequest( | |
| video.origin.documentURIObject, | |
| video.origin.referrer, | |
| YtVideoRequest.getCleanName(video.name), | |
| video.length + audio.length | |
| ); | |
| this._base.addStream(video.uri); | |
| this._base.addStream(audio.uri); | |
| this._video = video; | |
| this._audio = audio; | |
| this._id = video.id; | |
| this._tag = video.tag; | |
| }, | |
| // Downloads remote media to the local disk | |
| // member download | |
| // param {MediaLibrary} library | |
| // returns {Promise} | |
| download: function(library) | |
| { | |
| // type YtVideoRequest | |
| var ctx = this; | |
| var converterConf = antvd.ConverterPackage.getDefault(); | |
| try | |
| { | |
| library.ensureConfigured(); | |
| converterConf.ensureConfigured(); | |
| } | |
| catch (ex) | |
| { | |
| return Promise.reject(ex); | |
| } | |
| return Task.spawn(function () | |
| { | |
| // type DownloadResult | |
| try | |
| { | |
| antvd.AntLib.toLog("YtVideoRequest.download (yt.js)", "Downloading video stream " + ctx._video.uri.spec); | |
| antvd.AntLib.toLog("YtVideoRequest.download (yt.js)", "Downloading audio stream " + ctx._audio.uri.spec); | |
| let vdr = library.download( | |
| ctx._video.uri, | |
| antvd.AntLib.sprintf("video-stream-%s", ctx.displayName).replace(/ /g, "_"), | |
| true | |
| ); | |
| let adr = library.download( | |
| ctx._audio.uri, | |
| antvd.AntLib.sprintf("audio-stream-%s", ctx.displayName).replace(/ /g, "_"), | |
| true | |
| ); | |
| var svideo = yield vdr; | |
| ctx._base.setStreamMetadata( | |
| svideo.source, | |
| { | |
| size: svideo.size, | |
| time: svideo.downloadTime | |
| } | |
| ); | |
| var saudio = yield adr; | |
| ctx._base.setStreamMetadata( | |
| saudio.source, | |
| { | |
| size: saudio.size, | |
| time: saudio.downloadTime | |
| } | |
| ); | |
| } | |
| catch (ex) | |
| { | |
| antvd.AntLib.logError("YtVideoRequest.download (yt.js)", "Failed to download streams", ex); | |
| throw ex; | |
| } | |
| let converter = new antvd.Converter(converterConf); | |
| converter.setName(ctx._getFileName()); | |
| try | |
| { | |
| antvd.AntLib.toLog( | |
| "YtVideoRequest.download (yt.js)", | |
| antvd.AntLib.sprintf( | |
| "Converting\n video %s\n audio %s\nto %s", | |
| svideo.target, saudio.target, converter.getFileName() | |
| ) | |
| ); | |
| yield converter.join(svideo.target, saudio.target, ctx._video.ctype, ctx._audio.ctype); | |
| } | |
| catch (ex) | |
| { | |
| antvd.AntLib.logError("YtVideoRequest.download (yt.js)", "Failed to merge streams", ex); | |
| throw ex; | |
| } | |
| try | |
| { | |
| antvd.AntLib.toLog( | |
| "YtVideoRequest.download (yt.js)", | |
| "Saving video to disk\nuri............" + converter.getUri() + | |
| "\nfilename......." + converter.getFileName() + | |
| "\norigin/url....." + ctx._base._originUrl + | |
| "\norigin/title..." + ctx.displayName | |
| ); | |
| yield library.save( | |
| { | |
| uri: converter.getUri(), | |
| filename: converter.getFileName(), | |
| origin: | |
| { | |
| url: ctx._base._originUrl, | |
| title: ctx.displayName, | |
| domain: antvd.AntLib.getDomain(ctx._base._originUrl) | |
| } | |
| } | |
| ); | |
| converter.finalize(); | |
| } | |
| finally | |
| { | |
| try | |
| { | |
| // Do not clean up streams if converter stop flag is set | |
| if (AntLib.isDebugConverterStop() == false) | |
| { | |
| // TODO(ICh): Add a shared function which would perform the "nothrow" removal | |
| FileUtils.File(svideo.target).remove(false); | |
| FileUtils.File(saudio.target).remove(false); | |
| antvd.AntLib.toLog( | |
| "YtVideoRequest.download (yt.js)", | |
| "Removed temporary files\n " + svideo.target + "\n " + saudio.target | |
| ); | |
| } | |
| } | |
| catch (_e0) | |
| { | |
| antvd.AntLib.logWarning("YtVideoRequest.download (yt.js)", "Failed to clean up temporary files", _e0); | |
| } | |
| } | |
| }); | |
| }, | |
| // returns {Promise} | |
| reportDownload: function() | |
| { | |
| return this._base.reportDownload(); | |
| }, | |
| // deprecated To be renamed to 'equals' | |
| compare: function(request) | |
| { | |
| if (!request) | |
| { | |
| return false; | |
| } | |
| return (request._id == this._id) && (request._tag == this._tag); | |
| }, | |
| release: function() {}, | |
| _getFileName: function(extension) | |
| { | |
| return antvd.AntLib.mangleFileName(YtVideoRequest.getCleanName(this.displayName), extension); | |
| } | |
| }; | |
| // param {String} dirtyName | |
| YtVideoRequest.getCleanName = function(dirtyName) | |
| { | |
| return antvd.AntLib.sanitize(dirtyName).replace(/[,:()\[\]"'.`~▶]/ig,"").trim(); | |
| }; | |
| // const | |
| YtVideoRequest.TEMP_FILE_NAME = "yt-stream-chunk"; | |
| // typedef YtStream~StreamInfo | |
| // property {String} label | |
| // property {String} br | |
| // class YtStream | |
| function YtStream() { }; | |
| YtStream.prototype = | |
| { | |
| id: null, // String | |
| tag: null, // Number | |
| origin: null, // Document | |
| uri: null, // nsIURI | |
| length: null, // Number | |
| ctype: null, // String | |
| name: null, // String | |
| isInitialized: false, // Boolean | |
| media: null, // YtStream~StreamInfo | |
| // @member asyncFetch | |
| // @param {Function} complete Callback to be called in case of success | |
| asyncFetch: function(complete) | |
| { | |
| // type YtStream | |
| var ctx = this; | |
| var hr = new XMLHttpRequest(); | |
| hr.onreadystatechange = function() | |
| { | |
| if (hr.readyState == 4) | |
| { | |
| let clength = -1; | |
| try | |
| { | |
| clength = Number(hr.getResponseHeader("Content-Length")); | |
| } | |
| catch (ex) | |
| { | |
| antvd.AntLib.logError("YtStream.asyncFetch (yt.js)", "Failed to acquire the size of a stream", ex); | |
| } | |
| let ctype = null; | |
| try | |
| { | |
| ctype = hr.getResponseHeader("Content-Type"); | |
| } | |
| catch (ex) | |
| { | |
| antvd.AntLib.logError("YtStream.asyncFetch (yt.js)", "Failed to acquire content type a stream", ex); | |
| } | |
| ctx.ctype = ctype; | |
| ctx.length = clength; | |
| ctx.isInitialized = true; | |
| complete(); | |
| } | |
| }; | |
| hr.open("HEAD", this.uri.spec, true); | |
| hr.send(); | |
| }, | |
| // param {YtStream} stream | |
| // returns {Boolean} Whether the objects point to the same stream | |
| equal: function(stream) | |
| { | |
| if (this == stream) | |
| { | |
| return true; | |
| } | |
| return (stream.id == this.id) && (stream.tag == this.tag); | |
| }, | |
| // param {YtStream} stream | |
| // returns {YtVideoRequest} | |
| join: function(stream) | |
| { | |
| if (this.equal(stream)) | |
| { | |
| return null; | |
| } | |
| if (stream.id != this.id || stream.origin != this.origin) | |
| { | |
| return null; | |
| } | |
| if (!this.media || !stream.media) | |
| { | |
| return null; | |
| } | |
| if (this.media.label == stream.media.label) | |
| { | |
| return null; | |
| } | |
| var streams = {}; | |
| streams[this.media.label] = this; | |
| streams[stream.media.label] = stream; | |
| var vr = new YtVideoRequest(); | |
| vr.init(streams[vi], streams[au]); | |
| return vr; | |
| }, | |
| // returns {Boolean} | |
| isComplete: function() | |
| { | |
| return (!this.media) || (this.media.label == av); | |
| }, | |
| // returns {MediaRequest} | |
| toRequest: function() | |
| { | |
| var vr = new antvd.DefaultMediaRequest(); | |
| vr.init(this.uri, this.origin, this.length, this.ctype); | |
| return vr; | |
| }, | |
| // @returns {String} | |
| toString: function() | |
| { | |
| return "Complete: " + (this.isComplete() ? "true" : "false") | |
| + "\nLength: " + ((this.length >= 0) ? this.length : "N/A") | |
| + "\nType: " + (this.ctype ? this.ctype : "N/A") | |
| + "\nUri: " + this.uri.spec; | |
| } | |
| }; | |
| (function(me) | |
| { | |
| // Create a stream | |
| // | |
| // static | |
| // param {Document} origin Request initiator | |
| // param {nsIChannel} channel Underlying request | |
| // returns {YtStream?} | |
| me.create = function(origin, channel) | |
| { | |
| // type String | |
| var url = channel.URI.spec; | |
| var mimeMatch = /mime=(\w+(?:%2F|\/)[-.\w]+(?:\+[-.\w]+)?)/i.exec(url); | |
| if (!mimeMatch || mimeMatch.length != 2) | |
| { | |
| return null; | |
| } | |
| var mime = decodeURIComponent(mimeMatch[1]); | |
| if (!/\b(?:audio|video)\b/.test(mime)) | |
| { | |
| return null; | |
| } | |
| var tagMatch = /itag=(\d+)/i.exec(url); | |
| if (!tagMatch || tagMatch.length != 2) | |
| { | |
| return null; | |
| } | |
| var idMatch = /id=([\w\-_]+)/i.exec(url); | |
| if (!idMatch || idMatch.length != 2) | |
| { | |
| return null; | |
| } | |
| var id = idMatch[1]; | |
| var tag = Number(tagMatch[1]); | |
| // type String | |
| var unboundUrl = url.replace(/range=[^&#]+/i, "") | |
| .replace(/rn=[^&#]+/i, "") | |
| .replace(/rbuf=[^&#]+/i, "") | |
| .replace("ump=1", "ump=0") | |
| .replace(/&+$/i, "") | |
| .replaceAll("&&", "&"); | |
| var stream = new YtStream(); | |
| stream.ctype = channel.contentType; | |
| stream.uri = NetUtil.newURI(unboundUrl); | |
| stream.origin = origin; | |
| stream.id = id; | |
| stream.tag = tag; | |
| stream.name = origin.title; | |
| stream.media = getCodecForTag(tag); | |
| return stream; | |
| }; | |
| // returns {YtStream~StreamInfo} | |
| var getCodecForTag = function(tag) | |
| { | |
| const media = | |
| { | |
| 5: { label: av, br: "240p-FLV" }, | |
| 6: { label: av, br: "270p-FLV" }, | |
| 13: { label: av, br: "144p-3GP" }, | |
| 17: { label: av, br: "144p-3GP" }, | |
| 18: { label: av, br: "360p-MP4" }, | |
| 22: { label: av, br: "720p-MP4" }, | |
| 37: { label: av, br: "1080p-MP4" }, | |
| 38: { label: av, br: "3072p-MP4" }, | |
| 43: { label: av, br: "360p-WEBM" }, | |
| 44: { label: av, br: "480p-WEBM" }, | |
| 45: { label: av, br: "720p-WEBM" }, | |
| 46: { label: av, br: "1080p-WEBM" }, | |
| 133: { label: vi, br: "240p-MP4" }, | |
| 134: { label: vi, br: "360p-MP4" }, | |
| 135: { label: vi, br: "480p-MP4" }, | |
| 136: { label: vi, br: "720p-MP4" }, | |
| 137: { label: vi, br: "1080p-MP4" }, | |
| 138: { label: vi, br: "2160p-MP4" }, | |
| 139: { label: au }, | |
| 140: { label: au }, | |
| 141: { label: au }, | |
| 160: { label: vi, br: "144p-MP4" }, | |
| 171: { label: au }, | |
| 172: { label: au }, | |
| 242: { label: vi, br: "240p-WEBM" }, | |
| 243: { label: vi, br: "360p-WEBM" }, | |
| 244: { label: vi, br: "480p-WEBM" }, | |
| 247: { label: vi, br: "720p-WEBM" }, | |
| 248: { label: vi, br: "1080p-WEBM" }, | |
| 249: { label: au }, | |
| 250: { label: au }, | |
| 251: { label: au }, | |
| 256: { label: au }, | |
| 258: { label: au }, | |
| 264: { label: vi, br: "1440p-MP4" }, | |
| 266: { label: vi, br: "2160p-MP4" }, | |
| 271: { label: vi, br: "1440p-WEBM" }, | |
| 272: { label: vi, br: "2160p-WEBM" }, | |
| 278: { label: vi, br: "144p-WEBM" }, | |
| 298: { label: vi, br: "720p60-MP4" }, | |
| 299: { label: vi, br: "1080p60-MP4" }, | |
| 302: { label: vi, br: "720p-WEBM" }, | |
| 303: { label: vi, br: "1080p60-WEBM" }, | |
| 304: { label: vi, br: "1080p60-MP4" }, | |
| 308: { label: vi, br: "1440p60-WEBM" }, | |
| 313: { label: vi, br: "2160p-WEBM" }, | |
| 314: { label: vi, br: "2160p-WEBM" }, | |
| 315: { label: vi, br: "2160p60-WEBM" }, | |
| 327: { label: au }, | |
| 338: { label: au }, | |
| 339: { label: au } | |
| }; | |
| return media[tag]; | |
| }; | |
| })(YtStream); | |
| // class YtSearchStrategy | |
| // implements ISearchStrategy | |
| antvd.YtSearchStrategy = function() | |
| { | |
| const rePage = /.*?youtube\.com/i; | |
| const reHost = /.*?googlevideo\.com/i; | |
| // type Array.<YtStream> | |
| var streams = []; | |
| // ISearchStrategy implementation | |
| // member isApplicable | |
| // param {Document} document | |
| // param {nsIHttpChannel} channel | |
| // returns {Boolean} | |
| this.isApplicable = function(document, channel) | |
| { | |
| var docUri = document.documentURIObject; | |
| var reqUri = channel.URI; | |
| var _skip = (docUri != null && docUri.spec.indexOf("mpd_version") != -1) || | |
| (reqUri != null && reqUri.spec.indexOf("mpd_version") != -1); | |
| if (_skip) | |
| { | |
| return false; | |
| } | |
| try | |
| { | |
| if (docUri.host.match(rePage)) | |
| { | |
| return true; | |
| } | |
| } | |
| catch (e) | |
| {} | |
| try | |
| { | |
| // document's uri could be 'about:blank' and so on | |
| // in that case the `host accessor will throw an exception | |
| if (reqUri.host.match(reHost)) | |
| { | |
| return true; | |
| } | |
| } | |
| catch (e) | |
| {} | |
| return false; | |
| }; | |
| // member search | |
| // param {Document} document Owning document | |
| // param {nsIHttpChannel} channel Request's channel to analyze | |
| // param {Function} found See {AntGrabber#foundFlvLink} for details | |
| // returns {undefined} nothing | |
| this.search = function(document, channel, found) | |
| { | |
| if (!document || document.readyState != "complete" | |
| || !channel | |
| || !found | |
| || !channel.URI.host.match(reHost)) | |
| { | |
| return; | |
| } | |
| if (new URL(document.URL).searchParams.has("search_query")) | |
| { | |
| return; | |
| } | |
| var sr = YtStream.create(document, channel); | |
| if (!sr) | |
| { | |
| return; | |
| } | |
| // Save the stream for the future use | |
| var streams = AntTabMan.getAntData(document).ytstreams; | |
| for (let s in streams) | |
| { | |
| if (sr.equal(streams[s])) | |
| { | |
| return; | |
| } | |
| } | |
| antvd.AntLib.toLog( | |
| "YtSearchStrategy.search (yt.js)", | |
| "Created and pushed stream instance" + | |
| "\nctype...." + sr.ctype + | |
| "\nuri......" + sr.uri.spec + | |
| "\nid......." + sr.id + | |
| "\ntag......" + sr.tag + | |
| "\nname....." + sr.name | |
| ); | |
| streams.push(sr); | |
| sr.asyncFetch(function() | |
| { | |
| if (sr.isComplete()) | |
| { | |
| found(sr.toRequest()); | |
| return; | |
| } | |
| // Check whether there is a matching stream detected | |
| for (let s in streams) | |
| { | |
| if (!streams[s].isInitialized) | |
| { | |
| continue; | |
| } | |
| let vr = sr.join(streams[s]); | |
| if (vr) | |
| { | |
| found(vr); | |
| } | |
| } | |
| }); | |
| }; | |
| }; | |
| return antvd; | |
| })(antvd); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment