├── lib ├── youkuVideo │ └── version.rb └── youkuVideo.rb ├── Rakefile ├── Gemfile ├── .gitignore ├── youkuVideo.gemspec ├── LICENSE.txt └── README.md /lib/youkuVideo/version.rb: -------------------------------------------------------------------------------- 1 | module YoukuVideo 2 | VERSION = "0.0.2" 3 | end 4 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | task :console do 4 | exec "irb -r youkuVideo -I ./lib" 5 | end -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in youkuVideo.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | -------------------------------------------------------------------------------- /youkuVideo.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'youkuVideo/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "youkuVideo" 8 | spec.version = YoukuVideo::VERSION 9 | spec.authors = ["GonJay"] 10 | spec.email = ["me@gonjay.com"] 11 | spec.summary = %q{解析出 YouKu 视频的 M3U8 播放地址} 12 | spec.description = %q{通过网页或者 vid 解析出 YouKu 视频的 M3U8 播放地址} 13 | spec.homepage = "https://github.com/gonjay/YoukuVideo" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.7" 22 | spec.add_development_dependency "rake", "~> 10.0" 23 | end 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 GonJay 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/youkuVideo.rb: -------------------------------------------------------------------------------- 1 | require "youkuVideo/version" 2 | require "base64" 3 | require "uri" 4 | require "rest-client" 5 | 6 | module YoukuVideo 7 | class M3U8 8 | def self.youkuEncoder(a, c, isToBase64) 9 | result = "" 10 | bytesR = [] 11 | f,h,q = 0,0,0 12 | b = [] 13 | 256.times{|i| b[i]=i} 14 | while h < 256 do 15 | f = (f + b[h] + a[h % a.length].ord) % 256 16 | tmp = b[h] 17 | b[h] = b[f] 18 | b[f] = tmp 19 | h += 1 20 | end 21 | f,h,q = 0,0,0 22 | while q < c.length 23 | h = (h + 1) % 256 24 | f = (f + b[h]) % 256 25 | tmp = b[h] 26 | b[h] = b[f] 27 | b[f] = tmp 28 | bytes = [c[q].ord ^ b[(b[h] + b[f]) % 256]].pack "l" 29 | bytesR.push(bytes[0]) 30 | result += bytes[0] 31 | q += 1 32 | end 33 | if isToBase64 34 | result = Base64.encode64(bytesR.join("")) 35 | end 36 | return result 37 | end 38 | 39 | def self.getEp(vid, ep) 40 | template1 = "becaf9be" 41 | template2 = "bf7e5f01" 42 | bytes = Base64.decode64(ep).split(//) 43 | tmp = youkuEncoder(template1, bytes, false) 44 | sid = tmp.split("_")[0] 45 | token = tmp.split("_")[1] 46 | whole = "#{sid}_#{vid}_#{token}" 47 | newbytes = whole.split("").map { |e| e.ord } 48 | epNew = youkuEncoder(template2, newbytes, true) 49 | return URI.encode(epNew), sid, token 50 | end 51 | 52 | def self.getVideoURL(url, type) 53 | vid = url.split("id_").last.split(".").first 54 | content = RestClient.get("http://v.youku.com/player/getPlayList/VideoIDS/#{vid}/Pf/4/ctype/12/ev/1") 55 | json = JSON.parse(content) 56 | ip = json["data"][0]["ip"] 57 | ep = json["data"][0]["ep"] 58 | ep,sid,token = getEp(vid, ep) 59 | "http://pl.youku.com/playlist/m3u8?ctype=12&ep=#{ep}&ev=1&keyframe=1&oip=#{ip}&sid=#{sid}&token=#{token}&type=#{type}&vid=#{vid}" 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YoukuVideo 2 | 3 | 解析出 YouKu 视频的 M3U8 播放地址 4 | 5 | ## 安装 6 | 7 | 把这行代码加到你的 Gemfile 中 8 | 9 | ```ruby 10 | gem 'youkuVideo' 11 | ``` 12 | 13 | 然后运行: 14 | 15 | $ bundle 16 | 17 | 或者你可以直接安装: 18 | 19 | $ gem install youkuVideo 20 | 21 | ## 使用方法 22 | 23 | ``` 24 | 2.2.0 :007 > YoukuVideo::M3U8.getVideoURL("XNjgzMTU0MzU2", "hd3") 25 | => "http://pl.youku.com/playlist/m3u8?ctype=12&ep=dCaXGECKUs4G4iDagT8bZCu0cX4GXJZ0rEjP/LYHAMZuLerQmzbQwQ==%0A&ev=1&keyframe=1&oip=1899608454&sid=64329740673591237f599&token=2816&type=hd3&vid=XNjgzMTU0MzU2" 26 | ``` 27 | 28 | ## 原理 29 | 30 | 31 | 由于优酷视频地址时间限制,在你访问本篇文章时,下面所属链接有可能已经失效,望见谅。 32 | 33 | 例:http://v.youku.com/v_show/id_XNzk2NTI0MzMy.html 34 | 35 | 1.获取视频vid 36 | 在视频url中标红部分。一个正则表达式即可获取。 37 | 38 | ```ruby 39 | url = "http://v.youku.com/v_show/id_XNzk2NTI0MzMy.html" 40 | vid = url.split("id_").last.split(".").first 41 | ``` 42 | 43 | 2.获取视频元信息 44 | ```ruby 45 | content = RestClient.get("http://v.youku.com/player/getPlayList/VideoIDS/#{vid}/Pf/4/ctype/12/ev/1") 46 | json = JSON.parse(content) 47 | ip = json["data"][0]["ip"] 48 | ep = json["data"][0]["ep"] 49 | ``` 50 | 51 | 上面显示的内容后面都会使用到。其中segs包含hd3,hd2,flv,mp4,3gp等各种格式,并且每种格式下均分为若干段。本次选用清晰度较高的hd2(视频格式为flv) 52 | 53 | 3.拼接m3u8地址 54 | http://pl.youku.com/playlist/m3u8?ctype=12&ep={0}&ev=1&keyframe=1&oip={1}&sid={2}&token={3}&type={4}&vid={5} 55 | 56 | 以上共有6个参数,其中vid和oip已经得到,分别之前的vid和json文件中的ip字段,即(XNzk2NTI0MzMy和1991941296),但是ep,sid,token需要重新计算(json文件中的ep值不能直接使用)。type即为之前选择的segs。 57 | 58 | 3.1计算ep,sid,token 计算方法单纯的为数学计算,下面给出计算的函数。三个参数可一次性计算得到。其中涉及到Base64编码解码知识。 59 | 60 | ```ruby 61 | def self.youkuEncoder(a, c, isToBase64) 62 | result = "" 63 | bytesR = [] 64 | f,h,q = 0,0,0 65 | b = [] 66 | 256.times{|i| b[i]=i} 67 | while h < 256 do 68 | f = (f + b[h] + a[h % a.length].ord) % 256 69 | tmp = b[h] 70 | b[h] = b[f] 71 | b[f] = tmp 72 | h += 1 73 | end 74 | f,h,q = 0,0,0 75 | while q < c.length 76 | h = (h + 1) % 256 77 | f = (f + b[h]) % 256 78 | tmp = b[h] 79 | b[h] = b[f] 80 | b[f] = tmp 81 | bytes = [c[q].ord ^ b[(b[h] + b[f]) % 256]].pack "l" 82 | bytesR.push(bytes[0]) 83 | result += bytes[0] 84 | q += 1 85 | end 86 | if isToBase64 87 | result = Base64.encode64(bytesR.join("")) 88 | end 89 | return result 90 | end 91 | 92 | def self.getEp(vid, ep) 93 | template1 = "becaf9be" 94 | template2 = "bf7e5f01" 95 | bytes = Base64.decode64(ep).split(//) 96 | tmp = youkuEncoder(template1, bytes, false) 97 | sid = tmp.split("_")[0] 98 | token = tmp.split("_")[1] 99 | whole = "#{sid}_#{vid}_#{token}" 100 | newbytes = whole.split("").map { |e| e.ord } 101 | epNew = youkuEncoder(template2, newbytes, true) 102 | return URI.encode(epNew), sid, token 103 | end 104 | ``` 105 | 106 | 计算得到ep,token,sid分别为`cCaVGE6OUc8H4ircjj8bMiuwdH8KXJZ0vESH/7YbAMZuNaHQmjbTwg==`, `3825`, `241273717793612e7b085`。注意,此时ep并不能直接拼接到url中,需要对此做一下url编码URI.encode(epNew)。最终ep为`cCaVGE6OUc8H4ircjj8bMiuwdH8KXJZ0vESH%2f7YbAMZuNaHQmjbTwg%3d%3d` 107 | 108 | 3.2视频格式及清晰度 109 | 视频格式和选择的segs有密切关系。如本文选择的hd2,格式即为flv,下面是segs,视频格式和清晰度的对照。之前对此部分理解有些偏差,多谢削着苹果走路提醒。 110 | 111 | ``` 112 | "segs","视频格式","清晰度" 113 | "hd3", "flv", "1080P" 114 | "hd2", "flv", "超清" 115 | "mp4", "mp4", "高清" 116 | "flvhd", "flv", "高清" 117 | "flv", "flv", "标清" 118 | "3gphd", "3gp", "高清" 119 | ``` 120 | 121 | 3.3拼接地址 122 | 123 | 最后的m3u8地址为 124 | 125 | http://pl.youku.com/playlist/m3u8?ctype=12&ep=cCaVGE6OUc8H4ircjj8bMiuwdH8KXJZ0vESH%2f7YbAMZuNaHQmjbTwg%3d%3d&ev=1&keyframe=1&oip=996949050&sid=241273717793612e7b085&token=3825&type=hd2&vid=XNzk2NTI0MzMy 126 | 127 | ## Contributing 128 | 129 | 1. Fork it ( https://github.com/gonjay/youkuVideo/fork ) 130 | 2. Create your feature branch (`git checkout -b my-new-feature`) 131 | 3. Commit your changes (`git commit -am 'Add some feature'`) 132 | 4. Push to the branch (`git push origin my-new-feature`) 133 | 5. Create a new Pull Request 134 | --------------------------------------------------------------------------------