youtube-data 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,91 +1,102 @@
1
- # frozen_string_literal: true
2
-
3
- module Youtube
4
-
5
- # Used for extracting thumbnails/ images data and bytes stored on `i.ytimg.com`.
6
- #
7
- # @param extractor [Youtube::DataExtractor] The extractor to use when getting initial required video data.
8
- #
9
- class Thumbnail
10
- BASEHOST = URI('https://i.ytimg.com/')
11
-
12
- def initialize(extractor)
13
- @extractor = extractor
14
-
15
- @session = Net::HTTP.start(BASEHOST.hostname, {'use_ssl': true})
16
-
17
- @thumb_json_array = jump_to_in_json
18
- @thumb_default_json = @thumb_json_array[0]
19
- end
20
-
21
- # An array of thumbnail(s) json data (stored as a hash).
22
- #
23
- # @return [Array] The array of hashes about the thumbnail(s).
24
- # @yield [Array] The same as return, but yields to block if given.
25
- #
26
- def thumbnails_json
27
- return @thumb_json_array unless block_given?
28
- yield @thumb_json_array
29
- end
30
-
31
- # The json data for the default thumbnail (stored as a hash).
32
- #
33
- # @return [Hash] The hash form of the default thumbnail's json.
34
- # @yield [Hash] The same as return, but yields to block if given.
35
- #
36
- def default_json
37
- return @thumb_default_json unless block_given?
38
- yield @thumb_default_json
39
- end
40
-
41
- # Destination URL to default thumbnail file on the server which is returned/ yielded as a `URI::HTTPS` instance.
42
- def default_url
43
- return URI(@thumb_default_json['url']) unless block_given?
44
- yield URI(@thumb_default_json['url'])
45
- end
46
-
47
- # The file name for the default thumbnail which is returned/ yielded as `String` instance.
48
- def default_filename
49
- filename = default_url.path[/[A-Za-z0-9]+\.[A-Za-z0-9]+/]
50
- return filename unless block_given?
51
- yield filename
52
- end
53
-
54
- # The bytes contained in the default thumbnail file stored on the server returned/ yielded as `String` instance.
55
- def default_bytes
56
- res = get_raw(default_url.path)
57
- return res.body unless block_given?
58
- yield res.body
59
- end
60
-
61
- # Send a simple get request using the thumbnail session. This should be how the module sends all further requests
62
- # to the `i.ytimg.com` hostname outside of this class also.
63
- #
64
- # @param path [String] Any valid path on the server, prefixed with `/`.
65
- # @return [Net::HTTPResponse] The untouched response sent back from the request.
66
- # @yield [Net::HTTPResponse] Same as return but yielded to block.
67
- #
68
- def get_raw(path)
69
- res = get_request_path(path)
70
- return res unless block_given?
71
- yield res
72
- end
73
-
74
- # Use `get_raw` method it's the same, but is shorter and can yield.
75
- private def get_request_path(path)
76
- if path.class != "".class
77
- raise InvalidPathError, 'Path on server must be type `String\''
78
- end
79
- if path.empty? == false and path[0] != "/"
80
- raise InvalidPathError, 'Path must be prefixed with `/\''
81
- end
82
- return @session.get(path)
83
- end
84
-
85
- private def jump_to_in_json
86
- return @extractor.video_json_untouched['microformat']['playerMicroformatRenderer']['thumbnail']['thumbnails']
87
- end
88
-
89
- end
90
-
91
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Youtube
4
+
5
+ # Used for extracting thumbnails/ images data and bytes stored on `i.ytimg.com`.
6
+ #
7
+ # @param extractor [Youtube::DataExtractor] The extractor to use when getting initial required video data.
8
+ #
9
+ class Thumbnail
10
+ BASEHOST = URI('https://i.ytimg.com/')
11
+
12
+ def initialize(extractor, opts = {})
13
+ @extractor = extractor
14
+
15
+ # The session to use for the extractor allows information to persist during requests/session.
16
+ @session = Net::HTTP.start(BASEHOST.hostname, {'use_ssl': true})
17
+ if opts.include?(:mock_session)
18
+ @session = opts[:mock_session]
19
+ end
20
+
21
+ @thumb_json_array = Youtube::Video.new(@extractor).thumbnails
22
+ @thumb_default_json = @thumb_json_array[0]
23
+ end
24
+
25
+ def inspect
26
+ return itself
27
+ end
28
+
29
+ # An array of thumbnail(s) json data (stored as a hash).
30
+ #
31
+ # @return [Array] The array of hashes about the thumbnail(s).
32
+ # @yield [Array] The same as return, but yields to block if given.
33
+ #
34
+ def thumbnails_json
35
+ return @thumb_json_array unless block_given?
36
+ yield @thumb_json_array
37
+ end
38
+
39
+ # The json data for the default thumbnail (stored as a hash).
40
+ #
41
+ # @return [Hash] The hash form of the default thumbnail's json.
42
+ # @yield [Hash] The same as return, but yields to block if given.
43
+ #
44
+ def default_json
45
+ return @thumb_default_json unless block_given?
46
+ yield @thumb_default_json
47
+ end
48
+
49
+ # @return [URI::HTTPS] Destination of the default thumbnail file on the server.
50
+ def default_url
51
+ return URI(@thumb_default_json['url'])
52
+ end
53
+
54
+ # @return [String] Filename for the default thumbnail.
55
+ def default_filename
56
+ return default_url.path[/[A-Za-z0-9]+\.[A-Za-z0-9]+/]
57
+ end
58
+
59
+ # @return [String] The bytes from the default thumbnail file on the server. Sends one request.
60
+ def default_bytes
61
+ return get_raw(default_url.path).body
62
+ end
63
+
64
+ # Downloads default thumbnail to cwd with it's name from server unless a path/ file name is specified.
65
+ def download_default(file_path=nil)
66
+ download_thumbnail(default_bytes, if file_path.nil? then default_filename else file_path end)
67
+ end
68
+
69
+ # Send a simple get request using the thumbnail session. This should be how the module sends all further requests
70
+ # to the `i.ytimg.com` hostname outside of this class also.
71
+ #
72
+ # @param path [String] Any valid path on the server, prefixed with `/`.
73
+ # @return [Net::HTTPResponse] The untouched response sent back from the request.
74
+ # @yield [Net::HTTPResponse] Same as return but yielded to block.
75
+ #
76
+ def get_raw(path)
77
+ res = get_request_path(path)
78
+ return res unless block_given?
79
+ yield res
80
+ end
81
+
82
+ # Downloads thumbnail bytes (writes to file in byte mode).
83
+ private def download_thumbnail(bytes, file_path=nil)
84
+ File.open(file_path, 'wb') do |file_stream|
85
+ file_stream.write(bytes)
86
+ end
87
+ end
88
+
89
+ # Use `get_raw` method it's the same, but is shorter and can yield.
90
+ private def get_request_path(path)
91
+ if path.class != "".class
92
+ raise InvalidPathError, 'Path on server must be type `String\''
93
+ end
94
+ if path.empty? == false and path[0] != "/"
95
+ raise InvalidPathError, 'Path must be prefixed with `/\''
96
+ end
97
+ return @session.get(path)
98
+ end
99
+
100
+ end
101
+
102
+ end
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
- module Youtube
4
- VERSION = "0.1.0"
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Youtube
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Youtube
4
+
5
+ class Video
6
+
7
+ def initialize(extractor, opts = {})
8
+ @extractor = extractor
9
+ end
10
+
11
+ def inspect
12
+ return itself
13
+ end
14
+
15
+ # @return [Hash] The section of scraped json that contains video information just as is.
16
+ def json
17
+ @video_info_json = @extractor.video_json_untouched['videoDetails']
18
+ return @video_info_json unless block_given?
19
+ yield @video_info_json
20
+ end
21
+
22
+ # @return [Array] An array of different thumbnail image info hashes.
23
+ def thumbnails
24
+ return json['thumbnail']['thumbnails']
25
+ end
26
+
27
+ # @return [String] ID of the channel that owns the video.
28
+ def channel_id
29
+ return json['channelId']
30
+ end
31
+
32
+ # @return [String] ID of the video.
33
+ def id
34
+ return json['videoId']
35
+ end
36
+
37
+ # @return [String] The title of the video.
38
+ def title
39
+ return json['title']
40
+ end
41
+
42
+ # @return [String] The video description.
43
+ def description
44
+ return json['shortDescription']
45
+ end
46
+
47
+ # @return [Integer] Number of views that the video has.
48
+ def views
49
+ return json['viewCount'].to_i
50
+ end
51
+
52
+ # @return [String] The person who uploaded the video.
53
+ def author
54
+ return json['author']
55
+ end
56
+
57
+ # Same as `author` method.
58
+ def uploader
59
+ return author
60
+ end
61
+
62
+ # @return [Array] An array of keywords relating to the video.
63
+ def keywords
64
+ return json['keywords']
65
+ end
66
+
67
+ # @return [Integer] Length of the video in seconds.
68
+ def length_in_seconds
69
+ return json['lengthSeconds'].to_i
70
+ end
71
+
72
+ # @return [TrueClass, FalseClass] Whether or not the video is crawlable in the player.
73
+ def is_crawlable?
74
+ return json['isCrawlable']
75
+ end
76
+
77
+ # @return [TrueClass, FalseClass] Whether or not the video displays likes.
78
+ def allow_ratings?
79
+ return json['allowRatings']
80
+ end
81
+
82
+ # @return [TrueClass, FalseClass] Whether or not the video is a live stream.
83
+ def is_live?
84
+ return json['isLiveContent']
85
+ end
86
+
87
+ end
88
+
89
+ end
data/lib/youtube-data.rb CHANGED
@@ -1,13 +1,25 @@
1
- # frozen_string_literal: true
2
-
3
- require 'uri'
4
- require 'net/http'
5
- require 'json'
6
-
7
- require_relative 'youtube/version'
8
- require_relative 'youtube/extractor'
9
- require_relative 'youtube/thumbnail'
10
-
11
- module Youtube
12
- class Error < StandardError; end
13
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'json'
6
+
7
+ require_relative 'youtube/version'
8
+ require_relative 'youtube/extractor'
9
+ require_relative 'youtube/thumbnail'
10
+ require_relative 'youtube/video'
11
+
12
+ module Youtube
13
+
14
+ class InitExtractorError < RuntimeError
15
+ end
16
+
17
+ class InvalidVideoIDError < StandardError
18
+ end
19
+
20
+ class InvalidPathError < StandardError
21
+ end
22
+
23
+ class Error < StandardError; end
24
+
25
+ end
data/sig/youtube.rbs CHANGED
@@ -1,4 +1,4 @@
1
- module Youtube
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end
1
+ module Youtube
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/bash
2
+ # Get the test html for mocked testing video id = FtutLA63Cp8.
3
+
4
+ cur=$(basename $(pwd))
5
+
6
+ if [ $cur != 'tests' ]
7
+ then
8
+ echo 'This script must be run from within the `tests/` sub-directory.'
9
+ exit -1
10
+ fi
11
+
12
+ curl 'https://www.youtube.com/watch?v=FtutLA63Cp8' -o 'data/raw_video.html'
data/tests/mocks.rb CHANGED
@@ -1,29 +1,29 @@
1
- require 'net/http/response'
2
-
3
- # Mock types for testing the `Youtube` module.
4
- module Mocks
5
-
6
- class MockResponse < Net::HTTPResponse
7
-
8
- def stream_check # Avoid: `stream_check': undefined method `closed?' for nil:NilClass (NoMethodError)
9
- return
10
- end
11
-
12
- end
13
-
14
- class MockSession
15
- def initialize(url)
16
- @res = MockResponse.new(1.0, 200, "OK")
17
- end
18
-
19
- def set_res_body(content)
20
- @res.read_body # Update body content and set.
21
- @res.body = content
22
- end
23
-
24
- def get(path)
25
- return @res
26
- end
27
- end
28
-
29
- end
1
+ require 'net/http/response'
2
+
3
+ # Mock types for testing the `Youtube` module.
4
+ module Mocks
5
+
6
+ class MockResponse < Net::HTTPResponse
7
+
8
+ def stream_check # Avoid: `stream_check': undefined method `closed?' for nil:NilClass (NoMethodError)
9
+ return
10
+ end
11
+
12
+ end
13
+
14
+ class MockSession
15
+ def initialize(url)
16
+ @res = MockResponse.new(1.0, 200, "OK")
17
+ end
18
+
19
+ def set_res_body(content)
20
+ @res.read_body # Update body content and set.
21
+ @res.body = content
22
+ end
23
+
24
+ def get(path)
25
+ return @res
26
+ end
27
+ end
28
+
29
+ end
data/tests/test_all.rb CHANGED
@@ -1,3 +1,10 @@
1
- require 'test/unit'
2
-
3
- require_relative 'test_extractor'
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'test/unit'
5
+
6
+ require_relative 'test_extractor'
7
+ require_relative 'test_thumbnail'
8
+ require_relative 'test_video'
9
+
10
+ #https://ruby-doc.org/stdlib-3.0.2/libdoc/test-unit/rdoc/Test/Unit/Assertions.html
@@ -1,32 +1,45 @@
1
- require 'test/unit'
2
-
3
- require_relative '../lib/youtube-data'
4
- require_relative 'mocks'
5
-
6
-
7
- #https://ruby-doc.org/stdlib-3.0.2/libdoc/test-unit/rdoc/Test/Unit/Assertions.html
8
-
9
-
10
- class TestExtractor < Test::Unit::TestCase
11
- def setup
12
- @mock_session = Mocks::MockSession.new(nil)
13
- # Copy valid video html from file into the mock sessions `get` mock response which acts the same as normal session.
14
- @mock_session.set_res_body(File.read('./data/raw_video.html'))
15
- # Specify mock session to use and replace actual session with `opts[:mock_session]`.
16
- @extractor = Youtube::DataExtractor.new('FtutLA63Cp8', {:mock_session => @mock_session})
17
- end
18
-
19
- def test_response_invalid_path
20
- assert_raise(Youtube::InvalidPathError) do
21
- @extractor.get_raw(nil)
22
- end
23
- assert_raise(Youtube::InvalidPathError) do
24
- @extractor.get_raw("invalid/path")
25
- end
26
- end
27
-
28
- def test_response_valid
29
- res = @extractor.get_raw('/')
30
- assert_instance_of(Mocks::MockResponse, res)
31
- end
32
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'test/unit'
4
+
5
+ require_relative '../lib/youtube-data'
6
+ require_relative 'mocks'
7
+
8
+
9
+ class TestExtractor < Test::Unit::TestCase
10
+ def setup
11
+ @mock_session = Mocks::MockSession.new(nil)
12
+ # Copy valid video html from file into the mock sessions `get` mock response which acts the same as normal session.
13
+ @mock_session.set_res_body(File.read('./data/raw_video.html'))
14
+ # Specify mock session to use and replace actual session with `opts[:mock_session]`.
15
+ @extractor = Youtube::DataExtractor.new('FtutLA63Cp8', {:mock_session => @mock_session})
16
+ end
17
+
18
+ def test_video_uri
19
+ assert_equal(@extractor.video_uri, URI('https://www.youtube.com/watch?v=FtutLA63Cp8'))
20
+ end
21
+
22
+ def test_player_uri
23
+ assert_instance_of(URI::HTTPS, @extractor.player_uri)
24
+ assert_true(@extractor.player_uri.to_s.end_with?('base.js'))
25
+ end
26
+
27
+ def test_response_invalid_path
28
+ assert_raise(Youtube::InvalidPathError) do
29
+ @extractor.get_raw(nil)
30
+ end
31
+ assert_raise(Youtube::InvalidPathError) do
32
+ @extractor.get_raw("invalid/path")
33
+ end
34
+ end
35
+
36
+ def test_response_valid_mock_returned
37
+ res = @extractor.get_raw('/')
38
+ assert_instance_of(Mocks::MockResponse, res)
39
+ end
40
+
41
+ def test_not_raise_untouched_json_data
42
+ @extractor.video_json_untouched # Valid json hash returned.
43
+ end
44
+
45
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test/unit'
4
+
5
+ require_relative '../lib/youtube-data'
6
+ require_relative 'mocks'
7
+
8
+
9
+ class TestThumbnail < Test::Unit::TestCase
10
+ def setup
11
+ @mock_session = Mocks::MockSession.new(nil)
12
+ # Copy valid video html from file into the mock sessions `get` mock response which acts the same as normal session.
13
+ @mock_session.set_res_body(File.read('./data/raw_video.html'))
14
+ # Specify mock session to use and replace actual session with `opts[:mock_session]`.
15
+ @extractor = Youtube::DataExtractor.new('FtutLA63Cp8', {:mock_session => @mock_session})
16
+ @thumbnail = Youtube::Thumbnail.new(@extractor, {:mock_session => @mock_session})
17
+ end
18
+
19
+ def test_response_invalid_path
20
+ assert_raise(Youtube::InvalidPathError) do
21
+ @thumbnail.get_raw(nil)
22
+ end
23
+ assert_raise(Youtube::InvalidPathError) do
24
+ @thumbnail.get_raw("invalid/path")
25
+ end
26
+ end
27
+
28
+ def test_response_valid_mock_returned
29
+ res = @thumbnail.get_raw('/')
30
+ assert_instance_of(Mocks::MockResponse, res)
31
+ end
32
+
33
+ def test_thumbnails_json
34
+ assert_instance_of(Array, @thumbnail.thumbnails_json)
35
+ end
36
+
37
+ def test_default_json
38
+ assert_instance_of(Hash, @thumbnail.default_json)
39
+ end
40
+
41
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test/unit'
4
+
5
+ require_relative '../lib/youtube-data'
6
+ require_relative 'mocks'
7
+
8
+
9
+ class TestVideo < Test::Unit::TestCase
10
+ def setup
11
+ @mock_session = Mocks::MockSession.new(nil)
12
+ # Copy valid video html from file into the mock sessions `get` mock response which acts the same as normal session.
13
+ @mock_session.set_res_body(File.read('./data/raw_video.html'))
14
+ # Specify mock session to use and replace actual session with `opts[:mock_session]`.
15
+ @extractor = Youtube::DataExtractor.new('FtutLA63Cp8', {:mock_session => @mock_session})
16
+ @video = Youtube::Video.new(@extractor)
17
+ end
18
+
19
+ def test_json
20
+ assert_instance_of(Hash, @video.json)
21
+ end
22
+
23
+ def test_thumbnails_array
24
+ assert_instance_of(Array, @video.thumbnails)
25
+ assert_instance_of(Hash, @video.thumbnails[0])
26
+ end
27
+
28
+ def test_channel_id
29
+ assert_equal(@video.channel_id, 'UCEJJbhF5Hod0zsupy-26n_g')
30
+ end
31
+
32
+ def test_id
33
+ assert_equal(@video.id, 'FtutLA63Cp8')
34
+ end
35
+
36
+ def test_title
37
+ assert_equal(@video.title, '【東方】Bad Apple!! PV【影絵】')
38
+ end
39
+
40
+ def test_description
41
+ assert_equal(@video.description, 'sm8628149')
42
+ end
43
+
44
+ def test_views
45
+ assert_instance_of(Integer, @video.views)
46
+ end
47
+
48
+ def test_author
49
+ assert_equal(@video.author, 'kasidid2')
50
+ assert_equal(@video.uploader, 'kasidid2')
51
+ end
52
+
53
+ def test_keywords
54
+ assert_instance_of(Array, @video.keywords)
55
+ assert_equal(@video.keywords.length, 6)
56
+ end
57
+
58
+ def test_length_in_seconds
59
+ assert_equal(@video.length_in_seconds, 219)
60
+ end
61
+
62
+ def test_is_crawlable
63
+ assert_true(@video.is_crawlable?)
64
+ end
65
+
66
+ def test_allow_ratings
67
+ assert_true(@video.allow_ratings?)
68
+ end
69
+
70
+ def test_is_live
71
+ assert_false(@video.is_live?)
72
+ end
73
+
74
+ end