Skip to content

Instantly share code, notes, and snippets.

@obliviusm
Last active November 2, 2018 15:02
Show Gist options
  • Save obliviusm/b9c76980619b9fd182838190b90d54b5 to your computer and use it in GitHub Desktop.
Save obliviusm/b9c76980619b9fd182838190b90d54b5 to your computer and use it in GitHub Desktop.
Rails API sample code to get playlist and watch videos
require 'proxy_list_api_client'
class ExtractYoutubeVideo
def initialize(video_id)
@video_id = video_id
end
def perform
extract_video_stream_uri.presence || extract_video_stream_uri(with_proxy: true)
end
private
def extract_video_stream_uri(with_proxy: false)
proxy_flag = with_proxy ? "--proxy #{fetch_proxy}" : ''
`./bin/youtube-dl -f 22/18/17 #{proxy_flag} -g -- #{@video_id}`.chomp
end
def fetch_proxy
ProxyListApiClient.new.fetch_ua_proxy
end
end
json.array! @videos do |video|
json.id video.id
json.name video.name
json.previewImage video.preview_image_url
json.duration video.duration
end
# frozen_string_literal: true
require 'youtube_api_client'
class Playlist
DEFAULT_ID = ENV.fetch('YOUTUBE_DEFAULT_PLAYLIST_ID') { 'PLAPIZDiVa7x7XgtRMSCcon-EEJsPCtjAw' }
include ActiveModel::Model
attr_accessor :id
def find_video(video_id)
result = videos.find { |video| video.id == video_id }
raise ActiveRecord::RecordNotFound, 'video has not been found' unless result
result
end
def videos
raise AssociationNotFoundError, 'Playlist id is missed' unless id
@_videos ||= begin
videos_attrs = api_client.fetch_playlist(id)
videos_attrs.map do |video_attrs|
build_video(video_attrs)
end
end
end
def build_video(video_attrs)
Video.new({ playlist: self }.merge!(video_attrs))
end
def self.default
new(id: DEFAULT_ID)
end
private
def api_client
YoutubeApiClient.new
end
end
class ProxyListApiClient
include HTTParty
base_uri 'https://api.getproxylist.com'
def fetch_ua_proxy
proxy_details = self.class.get(
'/proxy',
query: {
country: ['UA'],
protocol: ['http'],
minUptime: 75,
maxSecondsToFirstByte: 1,
maxConnectTime: 1
}
)
"#{proxy_details['ip']}:#{proxy_details['port']}"
end
end
json.id @video.id
json.name @video.name
json.uri @video.uri
json.nextVideoId @video.next_video_id
# frozen_string_literal: true
class Video
include ActiveModel::Model
attr_accessor :id, :name, :preview_image_url, :playlist, :uri, :next_video_id
# Lazy loading of uri.
# Uri of the stream is extracted on real page.
# Uri stream is dynamic and often changes that's why we need to use tools to extract it.
def uri
Rails.cache.fetch("#{id}/uri") do
@uri.is_a?(Proc) ? @uri.call : @uri
end
end
# Faked
def duration
'3:05'
end
end
module Api::V1
class VideosController < BaseController
def index
@videos = Playlist.default.videos
end
def show
@video = Playlist.default.find_video(params[:id])
render json: { error: 'Not Found' }, status: :not_found unless @video
end
end
end
require 'extract_youtube_video'
class YoutubeApiClient
include HTTParty
base_uri 'https://www.googleapis.com/youtube/v3'
def initialize
@default_options = {
key: Rails.application.credentials.youtube_api_key,
maxResults: 50
}
end
# @return [
# {
# id:
# next_video_id:
# name:
# preview_image_url:
# uri: lazy uri extraction
# },
# ...
# ]
def fetch_playlist(playlist_id)
raw_playlist_details = self.class.get(
'/playlistItems',
query: { part: 'snippet', playlistId: playlist_id }.merge(@default_options)
)
extract_playlist_details(raw_playlist_details)
end
# TODO: Implement load full details for video
# @return {
# video_id:
# name:
# preview_image_url:
# uri: -> {} lazy loading or video stream uri
# }
# def fetch_full_video_details
# @return String URI to Video Stream on YouTube
def extract_video_uri(video_id)
ExtractYoutubeVideo.new(video_id).perform
end
private
def extract_playlist_details(raw_playlist_details)
result = raw_playlist_details['items'].map do |raw_video_item|
extract_video_details(raw_video_item)
end
fill_next_video_id(result)
result
end
def extract_video_details(item)
snippet = item['snippet']
video_id = snippet['resourceId']['videoId']
{
id: video_id,
name: snippet['title'],
preview_image_url: snippet['thumbnails']['medium']['url'],
uri: -> { extract_video_uri(video_id) }
}
end
def fill_next_video_id(video_attrs)
video_attrs[0...-1].each_with_index { |video, i| video[:next_video_id] = video_attrs[i + 1][:id] }
video_attrs.last[:next_video_id] = video_attrs.first[:id]
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment