Skip to content

Instantly share code, notes, and snippets.

@jmarsh24
Last active November 15, 2020 10:45
Show Gist options
  • Save jmarsh24/8f59ba83c94f4b230a0c25d422cb1a94 to your computer and use it in GitHub Desktop.
Save jmarsh24/8f59ba83c94f4b230a0c25d422cb1a94 to your computer and use it in GitHub Desktop.
Houndify Sound Match
require "openssl"
require "securerandom"
require "net/http"
require "json"
require "base64"
module Houndify
attr_reader :secrets
def self.set_secrets(id, key)
@secrets = {
id: id,
key: Base64.urlsafe_decode64(key)
}
end
def self.url
"https://api.houndify.com/v1/audio"
end
def self.secrets
@secrets
end
class Client
attr_reader :userID
attr_reader :options
attr_accessor :requestID
attr_reader :time_stamp
def initialize(user_id, options = {})
@userID = user_id
@options = options
@requestID = options[:requestID] || SecureRandom.uuid
end
def request(query, options = {})
@time_stamp = Time.now.to_i.to_s
@requestID = SecureRandom.uuid
uri = URI.parse(Houndify.url)
headers = generate_headers(Latitude: -27.4519, Longitude: 153.0178)
params = {query: query}
uri.query = URI.encode_www_form(params)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
code = http.head(uri.path, headers).code.to_i
JSON.parse(http.get(uri, headers).body)
end
def sound_match(file_path)
@time_stamp = Time.now.to_i.to_s
@requestID = SecureRandom.uuid
uri = URI.parse(Houndify.url)
headers = generate_headers('Transfer-Encoding': 'chunked')
connection = Faraday.new(Houndify.url) { |builder|
builder.request :multipart
builder.request :url_encoded
builder.adapter :net_http
}
payload = {file: Faraday::UploadIO.new(file_path, "audio/wav"), headers: headers}
connection.post do |req|
req.headers = payload[:headers]
req.body = payload[:file]
end
end
def generate_headers(options = {})
raise "No Client ID saved" if Houndify.secrets[:id].nil?
raise "No Client Key saved" if Houndify.secrets[:key].nil?
request_data = @userID + ";" + @requestID
encoded_data = sign_key(Houndify.secrets[:key], request_data + @time_stamp)
request_info = {
ClientID: Houndify.secrets[:id],
UserID: @userID,
RequestID: @requestID,
Clue: 'Music',
AutoListen: 'True',
LaunchSoundHoundAppResult: 'True'
}.merge(options)
{
"Content-Type" => "application/json",
"Transfer-Encoding" => "chunked",
"Hound-Request-Authentication" => request_data,
"Hound-Client-Authentication" => Houndify.secrets[:id] + ";" + @time_stamp + ";" + encoded_data,
"Hound-Request-Info" => request_info.to_json
}
end
private :generate_headers
def sign_key(client_key, message)
h = OpenSSL::HMAC.new(client_key, OpenSSL::Digest.new("sha256"))
h.update(message)
Base64.urlsafe_encode64(h.digest)
end
private :sign_key
end
end
# == Schema Information
#
# Table name: videos
#
# id :bigint not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# title :text
# youtube_id :string
# leader_id :bigint
# follower_id :bigint
# description :string
# channel :string
# channel_id :string
# duration :integer
# upload_date :date
# view_count :integer
# avg_rating :integer
# tags :string
# song_id :bigint
# youtube_song :string
# youtube_artist :string
# performance_date :datetime
# performance_number :integer
# performance_total :integer
# videotype_id :bigint
# event_id :bigint
#
class Video < ApplicationRecord
include Filterable
include Houndify
require "openssl"
require "base64"
require "net/http/post/multipart"
require "irb"
require "json"
require "securerandom"
require "houndify"
# validates :leader, presence: true
# validates :follower, presence: true
# validates :song, presence: true
# validates :artist, presence: true
# validates :youtube_id, presence: true, uniqueness: true
# validates :title, presence: true
belongs_to :leader, required: false
belongs_to :follower, required: false
belongs_to :song, required: false
belongs_to :videotype, required: false
belongs_to :event, required: false
scope :genre, ->(genre) { joins(:song).where(songs: {genre: genre}) }
scope :videotype, ->(videotype) { joins(:videotype).where(videotypes: {name: videotype}) }
scope :leader, ->(leader) { joins(:leader).where(leaders: {name: leader}) }
scope :follower, ->(follower) { joins(:follower).where(followers: {name: follower}) }
scope :event, ->(event) { joins(:event).where(events: {name: event}) }
scope :channel, ->(channel) { where(channel: channel) }
scope :paginate, lambda { |page, per_page|
offset(per_page * page).limit(per_page)
}
class << self
def search(query)
if query
where('leaders.name ILIKE :query or
followers.name ILIKE :query or
songs.genre ILIKE :query or
songs.title ILIKE :query or
songs.artist ILIKE :query or
videotypes.name ILIKE :query',
query: "%#{query.downcase}%")
else
all
end
end
# To fetch video, run this from the console:
# Video.parse_json('data/030tango_channel_data_json')
# Video.parse_json('/Users/justin/desktop/environment/data/channel_json')
def parse_json(file_path)
json_file = Dir.glob("#{file_path}/**/*.json").map
json_file.each do |youtube_video|
video = JSON.parse(File.read(youtube_video))
video = Video.new(
youtube_id: video["id"],
title: video["title"],
description: video["description"],
youtube_song: video["track"],
youtube_artist: video["artist"],
upload_date: video["upload_date"],
channel: video["uploader"],
duration: video["duration"],
channel_id: video["uploader_id"],
view_count: video["view_count"],
avg_rating: video["average_rating"],
tags: video["tags"]
)
# video.grep_title
video.save
end
end
# To fetch video, run this from the console:
# Video.for_channel('UCtdgMR0bmogczrZNpPaO66Q')
def for_channel(id)
channel = Yt::Channel.new id: id
channel.videos.each do |youtube_video|
video = Video.new(youtube_id: youtube_video.id, title: youtube_video.title)
video.grep_title
video.save
end
end
# Submits ACRCloud HTTP request
def song_match(file_name)
requrl = "http://identify-eu-west-1.acrcloud.com/v1/identify"
access_key = ENV["ACRCLOUD_ACCESS_KEY"]
access_secret = ENV["ACRCLOUD_SECRET_KEY"]
http_method = "POST"
http_uri = "/v1/identify"
data_type = "audio"
signature_version = "1"
timestamp = Time.now.utc.to_i.to_s
string_to_sign = http_method + "\n" + http_uri + "\n" + access_key + "\n" + data_type + "\n" + signature_version + "\n" + timestamp
digest = OpenSSL::Digest.new("sha1")
signature = Base64.encode64(OpenSSL::HMAC.digest(digest, access_secret, string_to_sign))
sample_bytes = File.size(file_name)
url = URI.parse(requrl)
File.open(file_name) do |file|
req = Net::HTTP::Post::Multipart.new url.path,
"sample" => UploadIO.new(file, "audio/mp3", file_name),
"access_key" => access_key,
"data_type" => data_type,
"signature_version" => signature_version,
"signature" => signature,
"sample_bytes" => sample_bytes,
"timestamp" => timestamp
res = Net::HTTP.start(url.host, url.port) { |http|
http.request(req)
}
body = res.body.force_encoding("utf-8")
body
end
end
def ask_hound(user_id, query)
@regex_hastag = /#\w+/
query = query.gsub(@regex_hastag, '')
info query
client = Houndify::Client.new(user_id)
response = client.request(query)
info response
publish 'hound-bot', response
response['AllResults'][0]['SpokenResponseLong']
end
def houndify_sound_match(user_id, file_path)
Houndify.set_secrets(ENV['HOUNDIFY_CLIENT_ID'],
ENV['HOUNDIFY_CLIENT_KEY'])
client = Houndify::Client.new(user_id)
response = client.sound_match(file_path)
response
ap JSON.parse(response.body)
end
# Submits Houndify HTTP request
# def houndify(file_path)
# http_method = "POST"
# http_uri = "/v1/audio"
# data_type = "audio"
# timestamp = Time.now.utc.to_i.to_s
# requrl = "https://api.houndify.com/v1/audio"
# client_id = ENV["HOUNDIFY_CLIENT_ID"]
# client_key = ENV["HOUNDIFY_CLIENT_KEY"]
# user_id = "1"
# request_id = "1"
# request_data = user_id + ";" + request_id
# digest = OpenSSL::Digest.new("sha1")
# encoded_data = Base64.encode64(OpenSSL::HMAC.digest(digest, client_key, request_data))
# hound_request = {
# Clue: "music",
# RequestID: request_id,
# SessionID: user_id,
# TimeStamp: timestamp,
# ClientID: client_id
# }
# headers = {
# 'Hound-Request-Authentication': request_data,
# 'Hound-Client-Authentication': client_id + ";" + timestamp + ";" + encoded_data,
# 'Hound-Request-Info': JSON.parse(hound_request.to_json)
# }
# url = URI.parse(requrl)
# File.open(file_path) do |file|
# req = Net::HTTP::Post::Multipart.new url.path,
# "file" => UploadIO.new(file, "audio/wav", file_path),
# "headers" => headers
# res = Net::HTTP.start(url.host, url.port) { |http|
# binding.irb
# http.request(req)
# }
# body = res.body
# body
# end
# end
# def houndify2(file_path)
# http_method = "POST"
# http_uri = "/v1/audio"
# data_type = "audio"
# timestamp = Time.now.utc.to_i.to_s
# requrl = "https://api.houndify.com/v1/audio"
# client_id = ENV["HOUNDIFY_CLIENT_ID"]
# client_key = ENV["HOUNDIFY_CLIENT_KEY"]
# user_id = "1"
# request_id = "1"
# request_data = user_id + ";" + request_id
# digest = OpenSSL::Digest.new("sha1")
# encoded_data = Base64.encode64(OpenSSL::HMAC.digest(digest, client_key, request_data))
# headers = {
# 'Hound-Request-Authentication': request_data,
# 'Hound-Client-Authentication': client_id + ";" + timestamp + ";" + encoded_data
# }
# field_file_url = "https://api.houndify.com/v1/audio"
# connection = Faraday.new(field_file_url) { |builder|
# builder.request :multipart
# builder.request :url_encoded
# builder.adapter :net_http
# }
# payload = {file: Faraday::UploadIO.new(file_path, "audio/wav"), headers: headers}
# connection.post do |req|
# # binding.irb
# req.body = payload
# end
# end
def song_match_all(count, offset)
Video.limit(count).offset(offset).each do |youtube_video|
youtube_audio_full = YoutubeDL.download(
"https://www.youtube.com/watch?v=#{youtube_video.youtube_id}",
{format: "140", output: "~/environment/data/audio/%(id)s.wav"}
)
song = FFMPEG::Movie.new(youtube_audio_full.filename.to_s)
output_file_path = youtube_audio_full.filename.gsub(/.wav/, "_15s.wav")
song_transcoded = song.transcode(output_file_path,
{audio_codec: "pcm_s16le", audio_channels: 1, audio_bitrate: 16, audio_sample_rate: 16000, custom: %w[-ss 00:01:30.00 -t 00:00:15.00]})
song_match_output = Video.houndify(output_file_path)
video = JSON.parse(song_match_output).extend Hashie::Extensions::DeepFind
spotify_album_id = video.deep_find("spotify")["album"]["id"]
spotify_album_name = RSpotify::Album.find(spotify_album_id).name
spotify_artist_id = video.deep_find("spotify")["artists"][0]["id"]
spotify_artist_name = RSpotify::Artist.find(spotify_artist_id).name
if video.deep_find("spotify")["artists"][1].present?
spotify_artist_id_2 = video.deep_find("spotify")["artists"][1]["id"]
end
if video.deep_find("spotify")["artists"][1].present?
spotify_artist_name_2 = RSpotify::Artist.find(spotify_artist_id_2).name
end
spotify_track_id = video.deep_find("spotify")["track"]["id"]
spotify_track_name = RSpotify::Track.find(spotify_track_id).name
youtube_song_id = video.deep_find("youtube")["vid"] if video.deep_find("youtube").present?
youtube_video.update(
spotify_album_id: spotify_album_id,
spotify_album_name: spotify_album_name,
spotify_artist_id: spotify_artist_id,
spotify_artist_name: spotify_artist_name,
spotify_artist_id_2: spotify_artist_id_2,
spotify_artist_name_2: spotify_artist_name_2,
spotify_track_id: spotify_track_id,
spotify_track_name: spotify_track_name,
youtube_song_id: youtube_song_id
)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment