Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit (import from old songlocator repo)

  • Loading branch information...
commit 8e1282d69948c113ef34ab7cec82aa8b52af47e4 0 parents
Andrey Popp andreypopp authored
15 Makefile
@@ -0,0 +1,15 @@
+SRC = $(shell find . -name '*.coffee' -type f)
+LIB = $(SRC:%.coffee=%.js)
+
+all: $(LIB)
+
+clean:
+ rm -f $(LIB)
+
+publish:
+ git push
+ git push --tags
+ npm publish
+
+%.js: %.coffee
+ coffee --map -bc $<
1  README
@@ -0,0 +1 @@
+SongLocator resovler for YouTube
11 component.json
@@ -0,0 +1,11 @@
+{
+ "name": "songlocator-youtube",
+ "version": "0.1.0",
+ "description": "SongLocator resolver for YouTube",
+ "tags": ["songlocator", "api", "music", "streaming", "audio", "resolver", "youtube"],
+ "author": "Andrey Popp <8mayday@gmail.com>",
+ "main": ["./songlocator-youtube.js"],
+ "dependencies": {
+ "songlocator-base": "~0.1.1"
+ }
+}
21 package.json
@@ -0,0 +1,21 @@
+{
+ "name": "songlocator-youtube",
+ "version": "0.1.0",
+ "description": "SongLocator resovler for YouTube",
+ "main": "songlocator-youtube.js",
+ "dependencies": {
+ "songlocator-base": "~0.1.1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/songlocator/songlocator-youtube.git"
+ },
+ "homepage": "https://github.com/songlocator/songlocator-youtube",
+ "keywords": ["songlocator", "api", "music", "streaming", "audio", "resolver", "youtube"],
+ "author": {
+ "name": "Andrey Popp",
+ "email": "8mayday@gmail.com",
+ "url": "http://andreypopp.com"
+ },
+ "license": "MIT"
+}
224 songlocator-youtube.coffee
@@ -0,0 +1,224 @@
+###
+
+ SongLocator resolver for YouTube.
+
+ 2013 (c) Andrey Popp <8mayday@gmail.com>
+
+ Based on Tomahawk YouTube resolver.
+
+ 2012 (C) Hugo Lindström <hugolm84@gmail.com>
+ 2012 (C) Thierry Göckel <thierry@strayrayday.lu>
+ 2012 (C) Leo Franchi <lfranchi@kde.org>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+###
+
+((root, factory) ->
+ if typeof exports == 'object'
+ SongLocator = require('songlocator-base')
+ module.exports = factory(SongLocator)
+ else if typeof define == 'function' and define.amd
+ define (require) ->
+ SongLocator = require('songlocator-base')
+ root.SongLocator.YouTube = factory(SongLocator)
+ else
+ root.SongLocator.YouTube = factory(SongLocator)
+
+) this, ({BaseResolver, extend}) ->
+
+ regexIndexOf = (s, regex, startpos) ->
+ indexOf = s.substring(startpos || 0).search(regex)
+ return if indexOf >= 0 then indexOf + (startpos || 0) else indexOf
+
+ class Resolver extends BaseResolver
+ name: 'youtube'
+
+ search: (qid, query) ->
+ url = 'http://gdata.youtube.com/feeds/api/videos/'
+ params = {alt: 'jsonc', q: query, 'max-results': this.options.searchMaxResults, v: 2}
+ this.request
+ url: url
+ params: params
+ callback: (error, response) =>
+ return if error
+ return if response.data.totalItems == 0
+ results = for item in response.data.items
+ result = this.item2result(item, query)
+ continue unless result?
+ result
+ this.results(qid, results)
+
+ resolve: (qid, title, artist, album) ->
+ query = [artist or '', title or ''].join(' ').trim()
+ this.search(qid, query)
+
+ item2result: (item, query) ->
+ return unless item.title and item.duration and not item.contentRating
+ return unless this.dirtyCheckTitle(item.title, query)
+
+ parsedTrack = this.cleanupAndParseTrack(item.title, query)
+
+ return if not parsedTrack or not parsedTrack.artist?
+
+ return unless this.getTrack(item.title, query, true)
+
+ {
+ title: parsedTrack.track
+ artist: parsedTrack.artist
+ album: undefined
+
+ source: this.name
+ id: item.id
+
+ linkURL: item.player['default'] + '&hd=1'
+ imageURL: item.thumbnail?.hqDefault or item.thumbnail?.sqDefault
+ audioURL: undefined # we cannot know the direct link to the audio stream
+ audioPreviewURL: undefined
+
+ mimetype: "video/h264"
+ duration: item.duration
+ }
+
+ dirtyCheckTitle: (title, query) ->
+ # dirty check, filters out the most of the unwanted results
+ titleItem = title
+ .replace(/([^A-Za-z0-9\s])/gi, "")
+ .replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g,'')
+ .replace(/\s+/g,'|');
+ queryItem = query
+ .replace(/([^A-Za-z0-9\s])/gi, "")
+ .replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g,'')
+ .replace(/\s+/g,'|');
+ matches = titleItem.match(RegExp(queryItem, 'gi'))
+
+ matches and matches.length == queryItem.split("|").length
+
+ cleanupAndParseTrack: (title, query) ->
+ result = {}
+
+ # For the ease of parsing, remove these
+ # Maybe we could up the score a bit?
+ if regexIndexOf(title, /(?:[([](?=(official))).*?(?:[)\]])|(?:(official|video)).*?(?:(video))/i, 0 ) != -1
+ title = title.replace(/(?:[([](?=(official|video))).*?(?:[)\]])/gi, "")
+ title = title.replace(/(official|video(?:([!:-])))/gi, "")
+ result.isOfficial = 1
+
+ result.query = title
+
+ # Sometimes users separate titles with quotes :
+ # eg, "\"Young Forever\" Jay Z | Mr. Hudson (OFFICIAL VIDEO)"
+ # this will parse out the that title
+ inQuote = title.match(/([""'])(?:(?=(\\?))\2.).*\1/g);
+
+ if inQuote and inQuote != undefined
+ result.track = inQuote[0].substr(1, inQuote[0].length-2)
+ title = title.replace(inQuote[0],'')
+ result.fromQuote = result.track
+
+ result.parsed = this.parseCleanTrack( title )
+
+ if result.parsed
+ result.parsed.track = result.track
+ return result.parsed
+
+ else
+ result.parsed = this.parseCleanTrack(title)
+ if result.parsed
+ return result.parsed
+
+ # Still no luck, lets go deeper
+ if !result.parsed
+ if title.toLowerCase().indexOf(query.toLowerCase()) != -1
+ result.parsed = this.parseCleanTrack(title.replace(RegExp(query, "gi"), query.concat(" :")))
+ else
+ tryMatch = query.replace(/(?:[-|:&])/g, " ")
+ if title.toLowerCase().indexOf(tryMatch.toLowerCase()) != -1
+ replaceWith = if regexIndexOf(title, /(?:[-|:&])/g, 0) != -1
+ query
+ else
+ query.concat(" : ")
+ result.parsed = this.parseCleanTrack( title.replace(RegExp(tryMatch, "gi"), replaceWith))
+
+ if result.fromQuote and result.fromQuote != undefined
+ if result.parsed
+ result.artist = result.parsed.artist
+ result.track = result.fromQuote
+
+ else if result.parsed
+ if result.parsed.artist != undefined
+ result.artist = result.parsed.artist
+ if result.parsed.track != undefined
+ result.track = result.parsed.track
+
+ delete result.parsed
+ result
+
+ parseCleanTrack: (track) ->
+ result = {}
+ result.query = track
+ result.query.replace /.*?(?=([-:|]\s))/g, (param) ->
+ if param != ""
+ if result.artist == undefined
+ result.artist = param
+ else
+ if result.track == undefined
+ result.track = param
+
+ result.query.replace /(?=([-:|]\s)).*/g, (param) ->
+ if param != ""
+ if regexIndexOf(param, /([-|:]\s)/g, 0) == 0
+ if result.track == undefined
+ result.track = param.replace(/([-|:]\s)/g, "")
+ else
+ if tyresult.artist == undefined
+ result.artist = param
+ result.track = result.replace(/([-|:]\s)/g, "")
+
+ if result.track != undefined and result.artist != undefined
+ # Now, lets move featuring to track title, where it belongs
+ ftmatch = result.artist.match(/(?:(\s)(?=(feat.|feat|ft.|ft|featuring)(?=(\s)))).*/gi)
+ if ftmatch
+ result.artist = result.artist.replace(ftmatch, "")
+ result.track += " " + ftmatch
+
+ # Trim
+ result.track = result.track.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g,'').replace(/\s+/g,' ')
+ result.artist = result.artist.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g,'').replace(/\s+/g,' ')
+ return result
+
+ return
+
+ getTrack: (trackTitle, origTitle, isSearch) ->
+ if (this.options.includeCovers == false or this.options.includeCovers == undefined) \
+ and trackTitle.search(/(\Wcover(?!(\w)))/i) != -1 \
+ and origTitle.search(/(\Wcover(?!(\w)))/i) == -1
+ return
+
+ # Allow remix:es in search results?
+ if isSearch == undefined
+ if (this.options.includeRemixes == false or this.options.includeRemixes == undefined) \
+ and trackTitle.search(/(\W(re)*?mix(?!(\w)))/i) != -1 \
+ and origTitle.search(/(\W(re)*?mix(?!(\w)))/i) == -1
+ return
+
+ if (this.options.includeLive == false or this.options.includeLive == undefined) \
+ and trackTitle.search(/(live(?!(\w)))/i) != -1 \
+ and origTitle.search(/(live(?!(\w)))/i) == -1
+ return
+
+ else
+ return trackTitle
+
+ {Resolver}
267 songlocator-youtube.js
@@ -0,0 +1,267 @@
+//@ sourceMappingURL=songlocator-youtube.map
+// Generated by CoffeeScript 1.6.1
+/*
+
+ SongLocator resolver for YouTube.
+
+ 2013 (c) Andrey Popp <8mayday@gmail.com>
+
+ Based on Tomahawk YouTube resolver.
+
+ 2012 (C) Hugo Lindström <hugolm84@gmail.com>
+ 2012 (C) Thierry Göckel <thierry@strayrayday.lu>
+ 2012 (C) Leo Franchi <lfranchi@kde.org>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+var __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+
+(function(root, factory) {
+ var SongLocator;
+ if (typeof exports === 'object') {
+ SongLocator = require('songlocator-base');
+ return module.exports = factory(SongLocator);
+ } else if (typeof define === 'function' && define.amd) {
+ return define(function(require) {
+ SongLocator = require('songlocator-base');
+ return root.SongLocator.YouTube = factory(SongLocator);
+ });
+ } else {
+ return root.SongLocator.YouTube = factory(SongLocator);
+ }
+})(this, function(_arg) {
+ var BaseResolver, Resolver, extend, regexIndexOf;
+ BaseResolver = _arg.BaseResolver, extend = _arg.extend;
+ regexIndexOf = function(s, regex, startpos) {
+ var indexOf;
+ indexOf = s.substring(startpos || 0).search(regex);
+ if (indexOf >= 0) {
+ return indexOf + (startpos || 0);
+ } else {
+ return indexOf;
+ }
+ };
+ Resolver = (function(_super) {
+
+ __extends(Resolver, _super);
+
+ function Resolver() {
+ return Resolver.__super__.constructor.apply(this, arguments);
+ }
+
+ Resolver.prototype.name = 'youtube';
+
+ Resolver.prototype.search = function(qid, query) {
+ var params, url,
+ _this = this;
+ url = 'http://gdata.youtube.com/feeds/api/videos/';
+ params = {
+ alt: 'jsonc',
+ q: query,
+ 'max-results': this.options.searchMaxResults,
+ v: 2
+ };
+ return this.request({
+ url: url,
+ params: params,
+ callback: function(error, response) {
+ var item, result, results;
+ if (error) {
+ return;
+ }
+ if (response.data.totalItems === 0) {
+ return;
+ }
+ results = (function() {
+ var _i, _len, _ref, _results;
+ _ref = response.data.items;
+ _results = [];
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ item = _ref[_i];
+ result = this.item2result(item, query);
+ if (result == null) {
+ continue;
+ }
+ _results.push(result);
+ }
+ return _results;
+ }).call(_this);
+ return _this.results(qid, results);
+ }
+ });
+ };
+
+ Resolver.prototype.resolve = function(qid, title, artist, album) {
+ var query;
+ query = [artist || '', title || ''].join(' ').trim();
+ return this.search(qid, query);
+ };
+
+ Resolver.prototype.item2result = function(item, query) {
+ var parsedTrack, _ref, _ref1;
+ if (!(item.title && item.duration && !item.contentRating)) {
+ return;
+ }
+ if (!this.dirtyCheckTitle(item.title, query)) {
+ return;
+ }
+ parsedTrack = this.cleanupAndParseTrack(item.title, query);
+ if (!parsedTrack || (parsedTrack.artist == null)) {
+ return;
+ }
+ if (!this.getTrack(item.title, query, true)) {
+ return;
+ }
+ return {
+ title: parsedTrack.track,
+ artist: parsedTrack.artist,
+ album: void 0,
+ source: this.name,
+ id: item.id,
+ linkURL: item.player['default'] + '&hd=1',
+ imageURL: ((_ref = item.thumbnail) != null ? _ref.hqDefault : void 0) || ((_ref1 = item.thumbnail) != null ? _ref1.sqDefault : void 0),
+ audioURL: void 0,
+ audioPreviewURL: void 0,
+ mimetype: "video/h264",
+ duration: item.duration
+ };
+ };
+
+ Resolver.prototype.dirtyCheckTitle = function(title, query) {
+ var matches, queryItem, titleItem;
+ titleItem = title.replace(/([^A-Za-z0-9\s])/gi, "").replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g, '').replace(/\s+/g, '|');
+ queryItem = query.replace(/([^A-Za-z0-9\s])/gi, "").replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g, '').replace(/\s+/g, '|');
+ matches = titleItem.match(RegExp(queryItem, 'gi'));
+ return matches && matches.length === queryItem.split("|").length;
+ };
+
+ Resolver.prototype.cleanupAndParseTrack = function(title, query) {
+ var inQuote, replaceWith, result, tryMatch;
+ result = {};
+ if (regexIndexOf(title, /(?:[([](?=(official))).*?(?:[)\]])|(?:(official|video)).*?(?:(video))/i, 0) !== -1) {
+ title = title.replace(/(?:[([](?=(official|video))).*?(?:[)\]])/gi, "");
+ title = title.replace(/(official|video(?:([!:-])))/gi, "");
+ result.isOfficial = 1;
+ }
+ result.query = title;
+ inQuote = title.match(/([""'])(?:(?=(\\?))\2.).*\1/g);
+ if (inQuote && inQuote !== void 0) {
+ result.track = inQuote[0].substr(1, inQuote[0].length - 2);
+ title = title.replace(inQuote[0], '');
+ result.fromQuote = result.track;
+ result.parsed = this.parseCleanTrack(title);
+ if (result.parsed) {
+ result.parsed.track = result.track;
+ return result.parsed;
+ }
+ } else {
+ result.parsed = this.parseCleanTrack(title);
+ if (result.parsed) {
+ return result.parsed;
+ }
+ }
+ if (!result.parsed) {
+ if (title.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
+ result.parsed = this.parseCleanTrack(title.replace(RegExp(query, "gi"), query.concat(" :")));
+ } else {
+ tryMatch = query.replace(/(?:[-|:&])/g, " ");
+ if (title.toLowerCase().indexOf(tryMatch.toLowerCase()) !== -1) {
+ replaceWith = regexIndexOf(title, /(?:[-|:&])/g, 0) !== -1 ? query : query.concat(" : ");
+ result.parsed = this.parseCleanTrack(title.replace(RegExp(tryMatch, "gi"), replaceWith));
+ }
+ }
+ }
+ if (result.fromQuote && result.fromQuote !== void 0) {
+ if (result.parsed) {
+ result.artist = result.parsed.artist;
+ }
+ result.track = result.fromQuote;
+ } else if (result.parsed) {
+ if (result.parsed.artist !== void 0) {
+ result.artist = result.parsed.artist;
+ }
+ if (result.parsed.track !== void 0) {
+ result.track = result.parsed.track;
+ }
+ }
+ delete result.parsed;
+ return result;
+ };
+
+ Resolver.prototype.parseCleanTrack = function(track) {
+ var ftmatch, result;
+ result = {};
+ result.query = track;
+ result.query.replace(/.*?(?=([-:|]\s))/g, function(param) {
+ if (param !== "") {
+ if (result.artist === void 0) {
+ return result.artist = param;
+ } else {
+ if (result.track === void 0) {
+ return result.track = param;
+ }
+ }
+ }
+ });
+ result.query.replace(/(?=([-:|]\s)).*/g, function(param) {
+ if (param !== "") {
+ if (regexIndexOf(param, /([-|:]\s)/g, 0) === 0) {
+ if (result.track === void 0) {
+ return result.track = param.replace(/([-|:]\s)/g, "");
+ }
+ } else {
+ if (tyresult.artist === void 0) {
+ result.artist = param;
+ }
+ return result.track = result.replace(/([-|:]\s)/g, "");
+ }
+ }
+ });
+ if (result.track !== void 0 && result.artist !== void 0) {
+ ftmatch = result.artist.match(/(?:(\s)(?=(feat.|feat|ft.|ft|featuring)(?=(\s)))).*/gi);
+ if (ftmatch) {
+ result.artist = result.artist.replace(ftmatch, "");
+ result.track += " " + ftmatch;
+ }
+ result.track = result.track.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g, '').replace(/\s+/g, ' ');
+ result.artist = result.artist.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g, '').replace(/\s+/g, ' ');
+ return result;
+ }
+ };
+
+ Resolver.prototype.getTrack = function(trackTitle, origTitle, isSearch) {
+ if ((this.options.includeCovers === false || this.options.includeCovers === void 0) && trackTitle.search(/(\Wcover(?!(\w)))/i) !== -1 && origTitle.search(/(\Wcover(?!(\w)))/i) === -1) {
+ return;
+ }
+ if (isSearch === void 0) {
+ if ((this.options.includeRemixes === false || this.options.includeRemixes === void 0) && trackTitle.search(/(\W(re)*?mix(?!(\w)))/i) !== -1 && origTitle.search(/(\W(re)*?mix(?!(\w)))/i) === -1) {
+ return;
+ }
+ }
+ if ((this.options.includeLive === false || this.options.includeLive === void 0) && trackTitle.search(/(live(?!(\w)))/i) !== -1 && origTitle.search(/(live(?!(\w)))/i) === -1) {
+
+ } else {
+ return trackTitle;
+ }
+ };
+
+ return Resolver;
+
+ })(BaseResolver);
+ return {
+ Resolver: Resolver
+ };
+});
10 songlocator-youtube.map
@@ -0,0 +1,10 @@
+{
+ "version": 3,
+ "file": "songlocator-youtube.js",
+ "sourceRoot": "",
+ "sources": [
+ "songlocator-youtube.coffee"
+ ],
+ "names": [],
+ "mappings": "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"
+}
Please sign in to comment.
Something went wrong with that request. Please try again.