Browse Source

Initial commit

master
Dylan Baker 4 years ago
commit
f1a5afca46
4 changed files with 192 additions and 0 deletions
  1. 1
    0
      .gitignore
  2. 11
    0
      Gemfile
  3. 54
    0
      Gemfile.lock
  4. 126
    0
      cc.rb

+ 1
- 0
.gitignore View File

@@ -0,0 +1 @@
1
+.env

+ 11
- 0
Gemfile View File

@@ -0,0 +1,11 @@
1
+# frozen_string_literal: true
2
+
3
+source "https://rubygems.org"
4
+
5
+git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
6
+
7
+# gem "rails"
8
+
9
+gem "dotenv", "~> 2.7"
10
+
11
+gem "google-api-client", "~> 0.30.8"

+ 54
- 0
Gemfile.lock View File

@@ -0,0 +1,54 @@
1
+GEM
2
+  remote: https://rubygems.org/
3
+  specs:
4
+    addressable (2.6.0)
5
+      public_suffix (>= 2.0.2, < 4.0)
6
+    declarative (0.0.10)
7
+    declarative-option (0.1.0)
8
+    dotenv (2.7.5)
9
+    faraday (0.15.4)
10
+      multipart-post (>= 1.2, < 3)
11
+    google-api-client (0.30.8)
12
+      addressable (~> 2.5, >= 2.5.1)
13
+      googleauth (>= 0.5, < 0.10.0)
14
+      httpclient (>= 2.8.1, < 3.0)
15
+      mini_mime (~> 1.0)
16
+      representable (~> 3.0)
17
+      retriable (>= 2.0, < 4.0)
18
+      signet (~> 0.10)
19
+    googleauth (0.9.0)
20
+      faraday (~> 0.12)
21
+      jwt (>= 1.4, < 3.0)
22
+      memoist (~> 0.16)
23
+      multi_json (~> 1.11)
24
+      os (>= 0.9, < 2.0)
25
+      signet (~> 0.7)
26
+    httpclient (2.8.3)
27
+    jwt (2.2.1)
28
+    memoist (0.16.0)
29
+    mini_mime (1.0.2)
30
+    multi_json (1.13.1)
31
+    multipart-post (2.1.1)
32
+    os (1.0.1)
33
+    public_suffix (3.1.1)
34
+    representable (3.0.4)
35
+      declarative (< 0.1.0)
36
+      declarative-option (< 0.2.0)
37
+      uber (< 0.2.0)
38
+    retriable (3.1.2)
39
+    signet (0.11.0)
40
+      addressable (~> 2.3)
41
+      faraday (~> 0.9)
42
+      jwt (>= 1.5, < 3.0)
43
+      multi_json (~> 1.10)
44
+    uber (0.1.0)
45
+
46
+PLATFORMS
47
+  ruby
48
+
49
+DEPENDENCIES
50
+  dotenv (~> 2.7)
51
+  google-api-client (~> 0.30.8)
52
+
53
+BUNDLED WITH
54
+   2.0.1

+ 126
- 0
cc.rb View File

@@ -0,0 +1,126 @@
1
+require 'dotenv/load'
2
+require 'google/apis/youtube_v3'
3
+require 'json'
4
+require 'open-uri'
5
+require 'twitter'
6
+
7
+Dotenv.load
8
+
9
+YOUTUBE_API_KEY = ENV["YOUTUBE_API_KEY"]
10
+
11
+TWITTER_CONSUMER_KEY = ENV["TWITTER_CONSUMER_KEY"]
12
+TWITTER_CONSUMER_SECRET = ENV["TWITTER_CONSUMER_SECRET"]
13
+TWITTER_ACCESS_TOKEN = ENV["TWITTER_ACCESS_TOKEN"]
14
+TWITTER_ACCESS_TOKEN_SECRET = ENV["TWITTER_ACCESS_TOKEN_SECRET"]
15
+
16
+YOUTUBE_DL_PATH = `which youtube-dl`.chomp
17
+FFMPEG_PATH = `which ffmpeg`.chomp
18
+FFPROBE_PATH = `which ffprobe`.chomp
19
+
20
+throw "YOUTUBE_API_KEY is required" unless YOUTUBE_API_KEY
21
+throw "TWITTER_CONSUMER_KEY is required" unless TWITTER_CONSUMER_KEY
22
+throw "TWITTER_CONSUMER_SECRET is required" unless TWITTER_CONSUMER_SECRET
23
+throw "TWITTER_ACCESS_TOKEN is required" unless TWITTER_ACCESS_TOKEN
24
+throw "TWITTER_ACCESS_TOKEN_SECRET is required" unless TWITTER_ACCESS_TOKEN_SECRET
25
+
26
+throw "Can't find youtube-dl" if YOUTUBE_DL_PATH.empty?
27
+throw "Can't find ffprobe" if FFPROBE_PATH.empty?
28
+throw "Can't find ffmpeg" if FFMPEG_PATH.empty?
29
+
30
+class YouTubeService
31
+  @@RESULTS_PER_PAGE = 50.0
32
+  @@PLAYLIST_OPTIONS = {
33
+    fields: 'items/snippet/resourceId/videoId,nextPageToken,page_info',
34
+    max_results: @@RESULTS_PER_PAGE,
35
+  }
36
+
37
+  def initialize
38
+    @youtube = Google::Apis::YoutubeV3::YouTubeService.new
39
+    @youtube.key = YOUTUBE_API_KEY
40
+  end
41
+
42
+  def fetch_random_frame_from_random_video
43
+    video_id = fetch_random_video_from_channel('ComputerChroniclesYT')
44
+    video_path = download_video(video_id)
45
+    frame_path = extract_frame(video_path)
46
+    delete_file(video_path)
47
+    frame_path
48
+  end
49
+
50
+
51
+  def fetch_random_video_from_channel(channel_name)
52
+    uploads_playlist_id = fetch_channel_uploads_playlist(channel_name)
53
+    response = fetch_playlist_videos(uploads_playlist_id)
54
+    extract_random_video(uploads_playlist_id, response)
55
+  end
56
+
57
+  def fetch_channel_uploads_playlist(channel_name)
58
+    channel = @youtube.list_channels('contentDetails', for_username: channel_name)
59
+    throw "No channel found" unless channel.items.first
60
+    channel.items.first.content_details.related_playlists.uploads
61
+  end
62
+
63
+  def fetch_playlist_videos(playlist_id)
64
+    @youtube.list_playlist_items('snippet', @@PLAYLIST_OPTIONS.merge(playlist_id: playlist_id))
65
+  end
66
+
67
+  def extract_random_video(playlist_id, response)
68
+    number_of_videos = response.page_info.total_results
69
+    number_of_pages = (number_of_videos / @@RESULTS_PER_PAGE).ceil
70
+    random_page_number = rand(1..number_of_pages)
71
+
72
+    (1..random_page_number).each do
73
+      response = @youtube.list_playlist_items(
74
+        'snippet',
75
+        @@PLAYLIST_OPTIONS.merge(
76
+          page_token: response.next_page_token,
77
+          playlist_id: playlist_id
78
+        )
79
+      )
80
+    end
81
+
82
+    response.items.sample.snippet.resource_id.video_id
83
+  end
84
+
85
+  def download_video(video_id)
86
+    output_path = "/tmp/video.mp4"
87
+    `#{YOUTUBE_DL_PATH} -f mp4 -o /tmp/video.mp4 #{video_id}`
88
+    output_path
89
+  end
90
+
91
+  def extract_frame(video_path)
92
+    output_path = "/tmp/frame.png"
93
+    length = get_video_length(video_path)
94
+    random_second = rand(0..length)
95
+    `#{FFMPEG_PATH} -v error -ss #{random_second} -i #{video_path} -frames 1 -y #{output_path}`
96
+    output_path
97
+  end
98
+
99
+  def get_video_length(video_path)
100
+    `#{FFPROBE_PATH} -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 #{video_path}`.chomp.to_i
101
+  end
102
+
103
+  def delete_file(path)
104
+    `rm #{path}`
105
+  end
106
+end
107
+
108
+class TwitterService
109
+  def initialize
110
+    @twitter = Twitter::REST::Client.new do |config|
111
+      config.consumer_key = TWITTER_CONSUMER_KEY
112
+      config.consumer_secret = TWITTER_CONSUMER_SECRET
113
+      config.access_token = TWITTER_ACCESS_TOKEN
114
+      config.access_token_secret = TWITTER_ACCESS_TOKEN_SECRET
115
+    end
116
+  end
117
+
118
+  def tweet_image(filepath)
119
+    @twitter.update_with_media(String.new, File.new(filepath))
120
+  end
121
+end
122
+
123
+youtube = YouTubeService.new
124
+filepath = youtube.fetch_random_frame_from_random_video
125
+TwitterService.new.tweet_image(filepath)
126
+youtube.delete_file(filepath)

Loading…
Cancel
Save