tubemp 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ <div class="large-12 columns">
2
+ <form method="get" action="/tags">
3
+ <div class="row collapse">
4
+ <div class="large-10 columns">
5
+ <input type="text" name="v" placeholder="Youtube URL, ID, or embed-code">
6
+ </div>
7
+ <div class="large-2 columns">
8
+ <input type="submit" class="button prefix" value="Create code" />
9
+ </div>
10
+ </div>
11
+ <p class="message">For example <em>http://youtu.be/D80QdsFWdcQ</em>.</p>
12
+ </form>
13
+ </div>
14
+ <div class="large-12 columns">
15
+ <h2>How does it work?</h2>
16
+ </div>
17
+ <div class="large-4 columns">
18
+ <div class="panel">
19
+ <h2>1. <small>Provide the YouTube-code</small></h2>
20
+ <p>Paste your originial YouTube embed-code, the ID or the video URL in the field above, and press the button.</p>
21
+ <p>tubemp detects what video you want, downloads the thumbnail for it and re-creates a thumbnail from it.</p>
22
+ <p>There is a simple <a href="https://github.com/berkes/tubemp#json">JSON API version</a></p>
23
+ </div>
24
+ </div>
25
+ <div class="large-4 columns"><div class="panel">
26
+ <h2>2. <small>tubemp creates an image that looks like a youtube-player</small></h2>
27
+ <p>Copy and paste the code you get onto your site. You can either choose to place a simple image, or one that has a YouTube play icon sticked to it.<br/>
28
+ This code links to the original video-page on YouTube. The image is served from the tubemp server and domain.
29
+ </p>
30
+ </div>
31
+ </div>
32
+ <div class="large-4 columns"><div class="panel">
33
+ <h2>3. <small>Third party trackers?</small></h2>
34
+ <p>When you place the default YouTube-embed-code on your site, Google (who owns YouTube) can, and will, track all the visitors of <em>your</em> site!<br />
35
+ You, or your users may not like that. In many countries there are even laws and regulations that don't allow you to place things (like embed-codes, ads) on your
36
+ site that allow <em>third parties</em> to track your visitors.<br />
37
+ </p>
38
+ </div>
39
+ </div>
40
+
@@ -0,0 +1,73 @@
1
+ <!DOCTYPE html>
2
+ <!--[if IE 8]> <html class="no-js lt-ie9" lang="en"> <![endif]-->
3
+ <!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
4
+
5
+ <head>
6
+ <meta charset="utf-8" />
7
+ <meta name="viewport" content="width=device-width" />
8
+ <title>tubemp | <%= title %></title>
9
+ <link rel="stylesheet" href="css/foundation.min.css" />
10
+ <script src="js/vendor/custom.modernizr.js"></script>
11
+ </head>
12
+ <body>
13
+
14
+ <div class="row">
15
+ <div class="large-4 large-centered columns">
16
+ <a href="/"><img src="img/logo.png" alt="tubemp logo" title="tubemp" class="logo" id="name" /></a>
17
+ </div>
18
+ <div class="large-6 large-centered columns">
19
+ <h2 class="subheader"><small>YouTube embeds without third party trackers.</small></h2>
20
+ </div>
21
+ </div>
22
+
23
+ <div class="row">
24
+ <div class="large-12 columns">
25
+ <hr />
26
+ <h2><%= title %></h2>
27
+ </div>
28
+ <%= yield %>
29
+ </div>
30
+
31
+ <div class="footer row">
32
+ <hr/>
33
+ <div class="large-6 columns">
34
+ <h2><small>Install it yourself...</small></h2>
35
+ <p>Only the site who created the new embed-code can track your visitors, because this site serves the images. You probably don't want that either,
36
+ so you probably want to <a href="https://github.com/berkes/tubemp#installation">install this tubemp</a> on your own server and domain, because it is <a href="https://github.com/berkes/tubemp">Open Source Software</a>.
37
+ </p>
38
+ </div>
39
+ <div class="large-6 columns">
40
+ <h2><small>...or, get me to install it for you.</small></h2>
41
+ <p>You can find my contact details at <a href="http://berk.es/about.html">my website</a>. Or you can email me at <a href='ma&#105;lto&#58;be&#114;&#64;&#37;77%65&#98;&#37;73ch%&#55;5u%72&#46;c%6Fm'>be&#114;&#64;we&#98;schuu&#114;&#46;c&#111;m</a> to discuss the options.</p>
42
+ <p>I can assist in anything, from getting a server or hoster to installation and customisation.</p>
43
+ </div>
44
+ <div class="large-12 columns">
45
+ <p>The name tubemp is a play on the word <a href="https://en.wikipedia.org/wiki/Electromagnetic_pulse">EMP</a> and tube. Tube, referring to YouTube, EMP being a military (side)effect, which disables many electronic devises, also electronics that spy on you.</p>
46
+ </div>
47
+ </div>
48
+
49
+ <script>
50
+ document.write('<script src=' +
51
+ ('__proto__' in {} ? 'js/vendor/zepto' : 'js/vendor/jquery') +
52
+ '.js><\/script>')
53
+ </script>
54
+ <script src="js/foundation.min.js"></script>
55
+ <!--
56
+ <script src="js/foundation/foundation.js"></script>
57
+ <script src="js/foundation/foundation.forms.js"></script>
58
+ -->
59
+ <script>
60
+ $(document).foundation();
61
+ </script>
62
+
63
+ <script type="text/javascript" src="zeroclipboard/ZeroClipboard.js"></script>
64
+ <script>
65
+ ZeroClipboard.setDefaults( { moviePath: '/zeroclipboard/ZeroClipboard.swf' } );
66
+ $(document).ready(function() {
67
+ /* Init zero-clipboard */
68
+ $("input.copy_button").each(function() { new ZeroClipboard($(this)); });
69
+ });
70
+ </script>
71
+ </body>
72
+ </html>
73
+
@@ -0,0 +1,3 @@
1
+ <div class="large-12 columns">
2
+ Youtube video with id <em><%=h id %></em> not found.
3
+ </div>
@@ -0,0 +1,17 @@
1
+ <% tags.each do |key, tag| %>
2
+ <div class="large-6 columns">
3
+ <%= tag %><br />
4
+ <div class="row collapse">
5
+ <div class="large-10 columns">
6
+ <input type="text" class="copy_value" id="copy_tag_<%= key %>" value="<%= html_escape tag %>" />
7
+ </div>
8
+ <div class="large-2 columns">
9
+ <input type="button" class="button prefix copy_button" id="copy_button_<%= key %>" data-clipboard-target="copy_tag_<%= key %>" value="Copy" />
10
+ </div>
11
+ </div>
12
+ </div>
13
+ <% end %>
14
+ <div class="large-12 columns">
15
+ <a href="/">Create another one</a>
16
+ </div>
17
+
@@ -0,0 +1,78 @@
1
+ require 'RMagick'
2
+ require 'video_info'
3
+ require 'net/http'
4
+
5
+ class YouTube
6
+ def initialize identifier
7
+ @id = find_id(identifier)
8
+
9
+ @meta = nil
10
+ end
11
+
12
+ def title
13
+ parse
14
+ @meta.title
15
+ end
16
+
17
+ def href
18
+ "http://www.youtube.com/watch?v=#{@id}"
19
+ end
20
+
21
+ def tags(uri)
22
+ parse
23
+
24
+ thumbs = get_thumbs.map do |key,thumb|
25
+ uri.path = thumb.uri_path
26
+ [key, %Q{<a href="#{href}"><img src="#{uri}" alt="#{title}"/></a>}]
27
+ end
28
+ Hash[thumbs]
29
+ end
30
+
31
+ def valid?
32
+ parse
33
+ not @meta.nil?
34
+ end
35
+
36
+ def id
37
+ @id
38
+ end
39
+
40
+ private
41
+ def find_id identifier
42
+ id_part = "[a-zA-Z0-9]{4,16}"
43
+ if matches = identifier.match("^#{id_part}$")
44
+ matches[0]
45
+ elsif matches = identifier.match("^http(?:s)?:\/\/(?:www\.)?youtube\.com\/watch\\?v=(#{id_part})")
46
+ matches[1]
47
+ elsif matches = identifier.match("^http(?:s)?:\/\/youtu.be\/(#{id_part})")
48
+ matches[1]
49
+ elsif matches = identifier.match("\<iframe(?:.*)src=\"http://www.youtube.com/embed/(#{id_part}).+")
50
+ matches[1]
51
+ else
52
+ nil
53
+ end
54
+ end
55
+
56
+ def parse
57
+ @meta ||= VideoInfo.get(href)
58
+ end
59
+
60
+ def get_thumbs
61
+ img = Net::HTTP.get(URI(@meta.thumbnail_large))
62
+ tmpfile = Tempfile.new(["tubemp", ".jpg"])
63
+
64
+ thumbs = {}
65
+ begin
66
+ tmpfile.binmode
67
+ tmpfile.write(img)
68
+ tmpfile.close
69
+
70
+ thumbs["basic"] = Thumbnail.new(id, tmpfile).write
71
+ thumbs["overlay"] = Thumbnail.new(id, tmpfile).add_overlay(File.join(File.dirname(__FILE__), "..", "assets","overlay.png")).write(@uri)
72
+ ensure
73
+ images = nil
74
+ tmpfile.unlink
75
+ end
76
+ thumbs
77
+ end
78
+ end
Binary file
@@ -0,0 +1,21 @@
1
+ require 'rack/test'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'tubemp.rb')
3
+
4
+ module RSpecMixin
5
+ include Rack::Test::Methods
6
+ def app() Tubemp end
7
+
8
+ def root_path()
9
+ Pathname.new(File.realpath(File.join(File.dirname(__FILE__), '..')))
10
+ end
11
+
12
+ def stub_info
13
+ info = mock("video");
14
+ info.stub(:title).and_return("Tony Tribe , Red Red Wine")
15
+ info.stub(:thumbnail_large).and_return("http://i.ytimg.com/vi/D80QdsFWdcQ/hqdefault.jpg")
16
+ VideoInfo.stub(:get).and_return info
17
+ end
18
+
19
+ end
20
+
21
+ RSpec.configure { |c| c.include RSpecMixin }
@@ -0,0 +1,92 @@
1
+ require "spec_helper"
2
+ require File.join(File.dirname(__FILE__), "..", "lib", "thumbnail")
3
+
4
+ describe Thumbnail do
5
+
6
+ before do
7
+ @id = "D80QdsFWdcQ"
8
+ @slice = @id.slice(0,2)
9
+ @container = File.join("lib", "public", "thumbs", @slice)
10
+ @filename = File.join(@container, "#{@id}.png")
11
+ @tmpfile = File.open(File.join(File.dirname(__FILE__), "fixtures", "thumb.jpg"))
12
+ @thumbnail = Thumbnail.new(@id, @tmpfile)
13
+ end
14
+
15
+ describe "#write" do
16
+ it 'should create a simple png' do
17
+ @thumbnail.write
18
+ File.should exist(@filename)
19
+ end
20
+
21
+ it 'should include overlay filenames in filename' do
22
+ @thumbnail.add_overlay @tmpfile #contains the name "thumb"
23
+ @thumbnail.write
24
+ File.should exist(File.join(@container, "#{@id}_thumb.png"))
25
+ end
26
+
27
+ it 'should be chainable' do
28
+ @thumbnail.write.should be_kind_of(Thumbnail)
29
+ end
30
+
31
+ it 'should overwrite existing files' do
32
+ FileUtils.cp(root_path.join("spec", "fixtures", "thumb.jpg"), @filename)
33
+ existing = File.stat(@filename)
34
+ @thumbnail.write
35
+ existing.should_not eq File.stat(@filename)
36
+ end
37
+
38
+ describe 'subdirs' do
39
+ before do
40
+ @container = File.join("lib", "public", "thumbs")
41
+ FileUtils.rm_rf(@container)
42
+ end
43
+
44
+ it 'should create the thumbs container dir' do
45
+ @thumbnail.write
46
+ File.should exist(@container)
47
+ end
48
+
49
+ it 'to a subdirectory of the first two characters of the id' do
50
+ @thumbnail.write
51
+ File.should exist(File.join(@container, @slice))
52
+ end
53
+ end
54
+ end
55
+
56
+ describe "#images" do
57
+ it 'should be an ImageList' do
58
+ @thumbnail.images.should be_kind_of Magick::ImageList
59
+ end
60
+ it 'should include the tmpfile' do
61
+ expected = Magick::ImageList.new(@tmpfile.path)[0]
62
+ @thumbnail.images.should include expected
63
+ end
64
+ end
65
+
66
+ describe "#add_overlay" do
67
+ before do
68
+ @overlay_name = File.join("assets", "overlay.png")
69
+ @expected = Magick::ImageList.new(@overlay_name)[0]
70
+ @thumbnail.add_overlay @overlay_name
71
+ end
72
+
73
+ it 'should add files to ImageList stack' do
74
+ @thumbnail.images.should include @expected
75
+ end
76
+
77
+ it 'should call combine the images to one' do
78
+ Magick::Image.any_instance.should_receive(:composite).once.and_return(@expected)
79
+ @thumbnail.write
80
+ end
81
+
82
+ it 'should be chainable' do
83
+ @thumbnail.add_overlay(@overlay_name).should be_kind_of(Thumbnail)
84
+ end
85
+ end
86
+
87
+ describe "uri_path" do
88
+ it 'should return an absolute path from within "/public".' do
89
+ @thumbnail.uri_path.should eq "/thumbs/#{@slice}/#{@id}.png"
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe "tubemp tags" do
4
+ it "should have a page for a youtube ID" do
5
+ get '/tags?v=D80QdsFWdcQ'
6
+ last_response.should be_ok
7
+ end
8
+
9
+ it 'should give a 404 on an invalid ID' do
10
+ get 'tags?v=INVALID'
11
+ last_response.should be_not_found
12
+ end
13
+ end
14
+
15
+ describe 'tubemp tags.json' do
16
+ before do
17
+ get 'tags.json?v=D80QdsFWdcQ'
18
+ end
19
+
20
+ it 'should have a json-version' do
21
+ last_response.should be_ok
22
+ end
23
+
24
+ it 'should send json content-type' do
25
+ last_response.content_type.should match /.*application\/json.*/
26
+ end
27
+
28
+ it 'should return the tags in json' do
29
+ tags = JSON.parse(last_response.body.strip)
30
+
31
+ tags.should be_kind_of(Hash)
32
+ ["basic", "overlay"].each do |variation|
33
+ tags[variation].should match IMAGE_RE
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ describe "tubemp /" do
40
+ before do
41
+ get '/'
42
+ end
43
+
44
+ it 'should have an index page' do
45
+ last_response.should be_ok
46
+ end
47
+ end
@@ -0,0 +1,123 @@
1
+ require "spec_helper"
2
+ require File.join(File.dirname(__FILE__), "..", "lib", "youtube")
3
+ require File.join(File.dirname(__FILE__), "..", "lib", "thumbnail")
4
+
5
+ IMAGE_RE = /\<img.*src="(.*)".*alt="(.*).*\/\>/
6
+ LINK_RE = /\<a.*href="(.*)".*\>/
7
+
8
+ describe YouTube do
9
+ context "valid ID" do
10
+ before do
11
+ stub_info
12
+ @id = "D80QdsFWdcQ"
13
+ @yt = YouTube.new @id
14
+ @filename = File.join("public", "thumbs", "#{@id}.png")
15
+
16
+ @thumb = mock(Thumbnail)
17
+ @thumb.stub(:uri_path => "/thumbs/#{@id}.png")
18
+ @thumb.stub(:add_overlay).and_return @thumb
19
+ @thumb.stub(:write).and_return @thumb
20
+
21
+ @uri = URI("http://example.com")
22
+ end
23
+
24
+ describe '#tags' do
25
+ before do
26
+ @tags = @yt.tags(@uri)
27
+ @basic = @tags["basic"]
28
+ end
29
+ it 'should render a list of valid image tags' do
30
+ @tags.each {|key,t| t.should match IMAGE_RE }
31
+ end
32
+
33
+ it 'should have an overlayed image' do
34
+ Thumbnail.any_instance.should_receive(:add_overlay).and_return @thumb
35
+ @yt.tags(@uri)
36
+ end
37
+
38
+ it 'should link to a thumbnail in PNG format' do
39
+ @basic.match(IMAGE_RE)[1].should match /#{@id}\.png/
40
+ end
41
+
42
+ it 'should link to an absolute URL' do
43
+ @basic.match(IMAGE_RE)[1].should match /^http:\/\/.*$/
44
+ end
45
+
46
+ it 'should have the title as alt attribute' do
47
+ @basic.match(IMAGE_RE)[2].should match "Tony Tribe , Red Red Wine"
48
+ end
49
+
50
+ it 'should have a link pointing to the youtube video' do
51
+ @basic.match(LINK_RE)[1].should match /http:\/\/www\.youtube\.com/
52
+ end
53
+ end
54
+
55
+ describe "#title" do
56
+ it "should render a title" do
57
+ @yt.title.should eq "Tony Tribe , Red Red Wine"
58
+ end
59
+ end
60
+
61
+ describe "#href" do
62
+ it 'should render a link' do
63
+ @yt.href.should eq "http://www.youtube.com/watch?v=#{@id}"
64
+ end
65
+ end
66
+
67
+ describe "#valid?" do
68
+ it 'should return true for a valid youtube-ID' do
69
+ @yt = YouTube.new("D80QdsFWdcQ")
70
+ @yt.should be_valid
71
+ end
72
+ end
73
+
74
+ it 'should get the large thumbnail' do
75
+ # re-stub to allow message-expectation
76
+ info = mock("video")
77
+ info.stub(:title)
78
+ info.should_receive(:thumbnail_large).and_return("http://i.ytimg.com/vi/D80QdsFWdcQ/hqdefault.jpg")
79
+ VideoInfo.stub(:get).and_return info
80
+ @yt.tags(@uri)
81
+ end
82
+ end #end valid-ID
83
+
84
+ describe "invalid-ID" do
85
+ before do
86
+ VideoInfo.stub(:get).and_return nil
87
+ @yt = YouTube.new("INVALID")
88
+ end
89
+
90
+ describe "#valid?" do
91
+ it 'should return false for an invalid youtube-ID' do
92
+ @yt = YouTube.new("INVALID")
93
+ @yt.should_not be_valid
94
+ end
95
+ end
96
+ end
97
+
98
+ describe "liberal IDs" do
99
+ before do
100
+ @id = "D80QdsFWdcQ"
101
+ end
102
+
103
+ it "should allow an ID" do
104
+ @yt = YouTube.new(@id)
105
+ @yt.id.should eq @id
106
+ end
107
+
108
+ it 'should allow an URL' do
109
+ @yt = YouTube.new("http://www.youtube.com/watch?v=#{@id}")
110
+ @yt.id.should eq @id
111
+ end
112
+
113
+ it 'should allow a shortened URL' do
114
+ @yt = YouTube.new("http://youtu.be/#{@id}")
115
+ @yt.id.should eq @id
116
+ end
117
+
118
+ it 'should allow an embed-code' do
119
+ @yt = YouTube.new("<iframe src=\"http://www.youtube.com/embed/D80QdsFWdcQ\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"></iframe>")
120
+ @yt.id.should eq @id
121
+ end
122
+ end
123
+ end