tubemp 0.5.0

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.
@@ -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