typingpool 0.7.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.
Files changed (90) hide show
  1. data/Rakefile +23 -0
  2. data/bin/tp-assign +240 -0
  3. data/bin/tp-collect +50 -0
  4. data/bin/tp-config +114 -0
  5. data/bin/tp-finish +101 -0
  6. data/bin/tp-make +169 -0
  7. data/bin/tp-review +175 -0
  8. data/lib/typingpool/amazon.rb +732 -0
  9. data/lib/typingpool/app.rb +634 -0
  10. data/lib/typingpool/config.rb +344 -0
  11. data/lib/typingpool/error.rb +22 -0
  12. data/lib/typingpool/filer.rb +396 -0
  13. data/lib/typingpool/project.rb +593 -0
  14. data/lib/typingpool/template.rb +175 -0
  15. data/lib/typingpool/templates/assignment/amazon-init.js +38 -0
  16. data/lib/typingpool/templates/assignment/interview/nameless.html.erb +13 -0
  17. data/lib/typingpool/templates/assignment/interview/noisy.html.erb +12 -0
  18. data/lib/typingpool/templates/assignment/interview/partials/voices.html.erb +10 -0
  19. data/lib/typingpool/templates/assignment/interview/phone.html.erb +12 -0
  20. data/lib/typingpool/templates/assignment/interview.html.erb +11 -0
  21. data/lib/typingpool/templates/assignment/main.css +20 -0
  22. data/lib/typingpool/templates/assignment/partials/entry.html.erb +19 -0
  23. data/lib/typingpool/templates/assignment/partials/footer.html.erb +3 -0
  24. data/lib/typingpool/templates/assignment/partials/header.html.erb +11 -0
  25. data/lib/typingpool/templates/assignment/partials/labeling-example.html.erb +4 -0
  26. data/lib/typingpool/templates/assignment/partials/labeling.html.erb +5 -0
  27. data/lib/typingpool/templates/assignment/partials/length-description.html.erb +6 -0
  28. data/lib/typingpool/templates/assignment/partials/voices.html.erb +10 -0
  29. data/lib/typingpool/templates/assignment/speech.html.erb +11 -0
  30. data/lib/typingpool/templates/config.yml +21 -0
  31. data/lib/typingpool/templates/project/audio/chunks/.empty_directory +0 -0
  32. data/lib/typingpool/templates/project/audio/originals/.empty_directory +0 -0
  33. data/lib/typingpool/templates/project/data/.empty_directory +0 -0
  34. data/lib/typingpool/templates/project/etc/ About these files - read me.txt +8 -0
  35. data/lib/typingpool/templates/project/etc/audio-compat.js +25 -0
  36. data/lib/typingpool/templates/project/etc/player/audio-player.js +4 -0
  37. data/lib/typingpool/templates/project/etc/player/license.txt +19 -0
  38. data/lib/typingpool/templates/project/etc/player/player.swf +0 -0
  39. data/lib/typingpool/templates/project/etc/transcript.css +49 -0
  40. data/lib/typingpool/templates/transcript.html.erb +23 -0
  41. data/lib/typingpool/test/fixtures/amazon-question-html.html +95 -0
  42. data/lib/typingpool/test/fixtures/amazon-question-url.txt +1 -0
  43. data/lib/typingpool/test/fixtures/audio/mp3/interview.1.mp3 +0 -0
  44. data/lib/typingpool/test/fixtures/audio/mp3/interview.2.mp3 +0 -0
  45. data/lib/typingpool/test/fixtures/audio/wma/VN620007.WMA +0 -0
  46. data/lib/typingpool/test/fixtures/audio/wma/VN620052.WMA +0 -0
  47. data/lib/typingpool/test/fixtures/config-1 +20 -0
  48. data/lib/typingpool/test/fixtures/config-2 +25 -0
  49. data/lib/typingpool/test/fixtures/not_yaml.txt +4 -0
  50. data/lib/typingpool/test/fixtures/template-2.html.erb +10 -0
  51. data/lib/typingpool/test/fixtures/template-3.html.erb +22 -0
  52. data/lib/typingpool/test/fixtures/template.html.erb +10 -0
  53. data/lib/typingpool/test/fixtures/tp_collect_id.txt +1 -0
  54. data/lib/typingpool/test/fixtures/tp_collect_sandbox-assignment.csv +8 -0
  55. data/lib/typingpool/test/fixtures/tp_review_id.txt +1 -0
  56. data/lib/typingpool/test/fixtures/tp_review_sandbox-assignment.csv +8 -0
  57. data/lib/typingpool/test/fixtures/transcript-chunks.csv +226 -0
  58. data/lib/typingpool/test/fixtures/utf8_transcript.txt +7 -0
  59. data/lib/typingpool/test/fixtures/vcr/tp-collect-1.yml +2712 -0
  60. data/lib/typingpool/test/fixtures/vcr/tp-collect-2.yml +2718 -0
  61. data/lib/typingpool/test/fixtures/vcr/tp-collect-3.yml +2768 -0
  62. data/lib/typingpool/test/fixtures/vcr/tp-review-1.yml +570 -0
  63. data/lib/typingpool/test/fixtures/vcr/tp-review-2.yml +351 -0
  64. data/lib/typingpool/test.rb +418 -0
  65. data/lib/typingpool/transcript.rb +181 -0
  66. data/lib/typingpool/utility.rb +272 -0
  67. data/lib/typingpool.rb +500 -0
  68. data/test/make_amazon_question_fixture.rb +24 -0
  69. data/test/make_tp_collect_fixture_1.rb +26 -0
  70. data/test/make_tp_collect_fixture_2.rb +16 -0
  71. data/test/make_tp_collect_fixture_3.rb +15 -0
  72. data/test/make_tp_collect_fixture_4.rb +17 -0
  73. data/test/make_tp_review_fixture_1.rb +26 -0
  74. data/test/make_tp_review_fixture_2.rb +30 -0
  75. data/test/make_transcript_chunks_fixture.rb +53 -0
  76. data/test/test_integration_script_1_tp_config.rb +108 -0
  77. data/test/test_integration_script_2_tp_make.rb +119 -0
  78. data/test/test_integration_script_3_tp_assign.rb +152 -0
  79. data/test/test_integration_script_4_tp_review.rb +72 -0
  80. data/test/test_integration_script_5_tp_collect.rb +44 -0
  81. data/test/test_integration_script_6_tp_finish.rb +123 -0
  82. data/test/test_unit_amazon.rb +153 -0
  83. data/test/test_unit_config.rb +94 -0
  84. data/test/test_unit_filer.rb +202 -0
  85. data/test/test_unit_project.rb +168 -0
  86. data/test/test_unit_project_local.rb +68 -0
  87. data/test/test_unit_project_remote.rb +157 -0
  88. data/test/test_unit_template.rb +111 -0
  89. data/test/test_unit_transcript.rb +77 -0
  90. metadata +234 -0
@@ -0,0 +1,175 @@
1
+ module Typingpool
2
+ #Model class that wraps ERB and adds a few Typingpool-specific
3
+ #capabilities: The ability to look in an array of search paths for a
4
+ #particular relative path, neccesary to support the Config#template
5
+ #dir on top of the built-in app template dir. Also makes it easy to
6
+ #pass in a hash and render the template against that hash, rather
7
+ #than against all the variables in the current namespace.
8
+ class Template
9
+ require 'erb'
10
+ class << self
11
+ #Constructor. Takes a relative template path and an optional
12
+ #config file. Default config is Config.file.
13
+ def from_config(path, config=Config.file)
14
+ validate_config(config)
15
+ new(path, look_in_from_config(config))
16
+ end
17
+
18
+ #private
19
+
20
+ def look_in_from_config(config)
21
+ look_in = [File.join(Utility.lib_dir, 'templates'), '']
22
+ look_in.unshift(config.templates) if config.templates
23
+ look_in
24
+ end
25
+
26
+ def validate_config(config)
27
+ if config.templates
28
+ File.exists?(config.templates) or raise Error::File::NotExists, "No such templates dir: #{config.templates}"
29
+ File.directory?(config.templates) or raise Error::File::NotExists, "Templates dir not a directory: #{config.templates}"
30
+ end
31
+ end
32
+ end #class << self
33
+
34
+ #An array of base paths to be searched when we're given a relative
35
+ #path to the template. Normally this includes the user's
36
+ #Config#template attribute, if any, followed by the built-in app
37
+ #template dir.
38
+ attr_reader :look_in
39
+
40
+ #Constructor. Takes a relative path and an array of base paths to
41
+ #search for relative template paths. See look_in docs. Template
42
+ #should be an ERB template.
43
+ def initialize(path, look_in)
44
+ @path = path
45
+ @look_in = look_in
46
+ full_path or raise Error, "Could not find template path '#{path}' in #{look_in.join(',')}"
47
+ end
48
+
49
+ #Takes a hash to pass to an ERB template and returns the text from
50
+ #rendering the template against that hash (the hash becomes the
51
+ #top-level namespace of the template, so the keys are accessed
52
+ #just as you'd normally access a variable in an ERB template).
53
+ def render(hash)
54
+ render_with_binding(Env.new(hash, self).get_binding)
55
+ end
56
+
57
+ #Like render, but takes a binding instead of hash
58
+ def render_with_binding(binding)
59
+ ERB.new(read, nil, '<>').result(binding)
60
+ end
61
+
62
+ #Returns the raw text of the template, unrendered.
63
+ def read
64
+ IO.read(full_path)
65
+ end
66
+
67
+ #Returns the path to the template after searching the various
68
+ #look_in dirs for the relative path. Returns nil if the template
69
+ #cannot be located.
70
+ def full_path
71
+ look_in.each do |dir|
72
+ extensions.each do |ext|
73
+ path = File.join(dir, [@path, ext].join)
74
+ if File.exists?(path) && File.file?(path)
75
+ return path
76
+ end
77
+ end
78
+ end
79
+ return
80
+ end
81
+
82
+ protected
83
+
84
+ def extensions
85
+ ['.html.erb', '.erb', '']
86
+ end
87
+
88
+
89
+ #A Template::Assignment works just like a regular template, except
90
+ #that within each transcript dir (Config#transcript and the
91
+ #built-in app template dir) we search within a subdir called
92
+ #'assignment' first, then, after all the 'assignment' subdirs have
93
+ #been search, we look in the original template dirs.
94
+ class Assignment < Template
95
+ def self.look_in_from_config(*args)
96
+ look_in = super(*args)
97
+ look_in.unshift(look_in.reject{|dir| dir.empty? }.map{|dir| File.join(dir, 'assignment') })
98
+ look_in.flatten
99
+ end
100
+ end #Assignment
101
+
102
+ #This subclass provides two utility methods to all templates:
103
+ #read, for including the text of another template, and render, for
104
+ #rendering another template. Read takes a relative path,
105
+ #documented below. Render is passed the same hash as the parent
106
+ #template, merged with an optional override hash, as documented
107
+ #below.
108
+ #
109
+ #This subclass also makes it easier to use a hash as the top-level
110
+ #variable namespace when rendering ERB templates.
111
+ class Env
112
+
113
+ #Construtor. Takes a hash to be passed to the template and a
114
+ #template (ERB).
115
+ def initialize(hash, template)
116
+ @hash = hash
117
+ @template = template
118
+ end
119
+
120
+ #Method passed into each template. Takes a relative path and
121
+ #returns the text of the file at that path.
122
+ #
123
+ #The relative path is resolved as in look_in above, with the
124
+ #following difference: the current directory and each parent
125
+ #directory of the active template is searched first, up to the
126
+ #root transcript directory (either Config#template, the built-in
127
+ #app template dir, or any dir that has been manually added to
128
+ #look_in).
129
+ def read(path)
130
+ @template.class.new(path, localized_look_in).read.strip
131
+ end
132
+
133
+ #Method passed into each template. Takes a reltive path and
134
+ #returns the *rendered* text of the ERB template at that
135
+ #path. Can also take an optional hash, which will be merged into
136
+ #the parent template's hash and passed to the included
137
+ #template. If the optional hash it not passed, the parent
138
+ #template's hash will be passed to the included template
139
+ #unmodified.
140
+ #
141
+ #The relative path is resolved as described in the docs for
142
+ #Template::Env#read.
143
+ def render(path, hash={})
144
+ @template.class.new(path, localized_look_in).render(@hash.merge(hash)).strip
145
+ end
146
+
147
+ def get_binding
148
+ binding()
149
+ end
150
+
151
+ protected
152
+
153
+ def localized_look_in
154
+ look_in = []
155
+ path = @template.full_path
156
+ until @template.look_in.include? path = File.dirname(path)
157
+ look_in.push(path)
158
+ end
159
+ look_in.push(path, (@template.look_in - [path])).flatten
160
+ end
161
+
162
+ def method_missing(key, value=nil)
163
+ if value
164
+ key = key.to_s.sub(/=$/, '')
165
+ @hash[key.to_sym] = value
166
+ end
167
+ if @hash.has_key? key
168
+ @hash[key]
169
+ elsif @hash.has_key? key.to_s
170
+ @hash[key.to_s]
171
+ end
172
+ end
173
+ end #Env
174
+ end #Template
175
+ end #Typingpool
@@ -0,0 +1,38 @@
1
+
2
+ function setAmazonAssignmentId() {
3
+ var params = window.location.search.substring(1).split('&');
4
+ var i = 0;
5
+ for (i = 0; i < params.length; i++) {
6
+ var param = params[i].split('=');
7
+ var key = param[0];
8
+ var value = param[1];
9
+ if (key === 'assignmentId') {
10
+ if (value === 'ASSIGNMENT_ID_NOT_AVAILABLE') {
11
+ var input = document.getElementById('disable_on_preview');
12
+ if (input) {
13
+ input.setAttribute('disabled', 'disabled');
14
+ var span = document.createElement('span');
15
+ span.setAttribute('class', 'disabled_message');
16
+ span.appendChild(document.createTextNode(' Disabled because you are previewing this HIT.'));
17
+ var inputSibling = input.nextSibling;
18
+ if (inputSibling) {
19
+ input.parentNode.insertBefore(span, inputSibling);
20
+ }
21
+ else {
22
+ input.parentNode.appendChild(span);
23
+ }
24
+ }
25
+ }
26
+ else {
27
+ document.getElementById('assignmentId').value = value;
28
+ }
29
+ }
30
+ else if (key === 'turkSubmitTo') {
31
+ var form = document.getElementById('turkForm');
32
+ if (form) {
33
+ form.setAttribute('action', decodeURIComponent(value) + '/mturk/externalSubmit');
34
+ }
35
+ }
36
+ }
37
+ }
38
+
@@ -0,0 +1,13 @@
1
+ <% self.title = 'Transcribe MP3 of ' + render('partials/length-description') + ' interview' %>
2
+ <%= render 'partials/header' %>
3
+ <p id="description">I split up an interview into <%= render 'partials/length-description' %> parts, you transcribe the audio for one <%= render 'partials/length-description' %> part.</p>
4
+
5
+ <h3>Labeling</h3>
6
+ <p>Label each speaker with a gender and, if needed, a number. Use the labels "MALE:", "FEMALE:", "MALE 2:", "FEMALE 2:", "MALE 3:", "FEMALE 3:", etc., like this:
7
+
8
+ <% self.voice1 = 'MALE' %><% self.voice2 = 'MALE 2' %>
9
+ <%= render 'partials/labeling-example' %>
10
+
11
+ <%= render 'partials/entry' %>
12
+
13
+ <%= render 'partials/footer' %>
@@ -0,0 +1,12 @@
1
+ <% self.title = 'Transcribe MP3 of ' + render('partials/length-description') + ' interview (background noise)' %>
2
+ <%= render 'partials/header' %>
3
+ <p id="description">I split up an interview into <%= render 'partials/length-description' %> parts, you transcribe the audio for one <%= render 'partials/length-description' %> part. There is background noise.</p>
4
+
5
+ <%= render 'partials/voices' %>
6
+
7
+ <%= render 'partials/labeling' %>
8
+
9
+ <% self.important = ['The interview takes place in a noisy environment with audible chatter. You should transcribe <strong>only</strong> the comments from the people involved in the interview.'] %>
10
+ <%= render 'partials/entry' %>
11
+
12
+ <%= render 'partials/footer' %>
@@ -0,0 +1,10 @@
1
+ <% if voices_count > 0 %>
2
+ <h3>Names</h3>
3
+ <p>There are <%= voices_count %> speakers:
4
+ <ul>
5
+ <% 1.upto(voices_count) do |i| %>
6
+ <li><strong><%= send("voice#{i}") %></strong><% if send("voice#{i}title").to_s.empty? %><% if i == 1 %>, the person asking the questions<% end %><% else %>, <%= send("voice#{i}title") %><% end %>.
7
+ <% end %>
8
+ </ul>
9
+ <% end %>
10
+
@@ -0,0 +1,12 @@
1
+ <% self.title = 'Transcribe MP3 of ' + render('partials/length-description') + ' phone call' %>
2
+ <%= render 'partials/header' %>
3
+
4
+ <p id="description">I split up a telephone conversation into <%= render 'partials/length-description' %> parts, you transcribe the audio for one <%= render 'partials/length-description' %> part.</p>
5
+
6
+ <h3>Labeling</h3>
7
+ <p>There are two speakers. The person asking the questions is <strong><%= voice1 %></strong>. The person answering the questions, on the distant end of the telephone line, is <strong><%= voice2 %></strong><% unless voice2title.to_s.empty? %>, <%= voice2title %><% end %>. Label each speaker with a colon, like this:</p>
8
+ <%= render 'partials/labeling-example' %>
9
+
10
+ <%= render 'partials/entry' %>
11
+
12
+ <%= render 'partials/footer' %>
@@ -0,0 +1,11 @@
1
+ <% self.title = 'Transcribe MP3 of ' + render('partials/length-description') + ' interview' %>
2
+ <%= render 'partials/header' %>
3
+ <p id="description">I split up an interview into <%= render 'partials/length-description' %> parts, you transcribe the audio for one <%= render 'partials/length-description' %> part.</p>
4
+
5
+ <%= render 'interview/partials/voices' %>
6
+
7
+ <%= render 'partials/labeling' %>
8
+
9
+ <%= render 'partials/entry' %>
10
+
11
+ <%= render 'partials/footer' %>
@@ -0,0 +1,20 @@
1
+
2
+ body {
3
+ color: black;
4
+ background: white;
5
+ font-family: Georgia, Times, "Times New Roman", serif;
6
+ }
7
+
8
+ h1, h2, h3, h4, h5, .disabled_message {
9
+ font-family: Helvetica, Arial, sans-serif;
10
+ }
11
+
12
+ p {
13
+ line-height: 140%;
14
+ }
15
+
16
+ .disabled_message {
17
+ color: gray;
18
+ font-size: small;
19
+ }
20
+
@@ -0,0 +1,19 @@
1
+ <h3>Important</h3>
2
+ <ul>
3
+ <li><strong>Unusual words</strong> you might hear: <strong><%= unusual %></strong></li>
4
+ <li><strong>Don't</strong> type short or filler responses like <strong>uh</strong>, <strong>you know</strong>, <strong>I mean</strong>, <strong>ya</strong>, <strong>OK</strong>, <strong>Right</strong>, <strong>I see</strong>, <strong>Oh</strong>, and <strong>Ah</strong>.</li>
5
+ <li>For <strong>inaudible</strong> words, type <strong>??</strong></li>
6
+ <% if important %>
7
+ <% important.each do |note| %>
8
+ <li><%= note %></li>
9
+ <% end %>
10
+ <% end %>
11
+ </ul>
12
+
13
+ <h3>Instructions</h3>
14
+ <p>Please <strong>transcribe</strong> this <%= render 'length-description' %> MP3: <a target="_blank" href="<%= audio_url %>"><%= audio_url %></a></p>
15
+ <form action="https://www.mturk.com/mturk/externalSubmit" method="POST" id="turkForm">
16
+ <p><input name="assignmentId" type="hidden" id="assignmentId"><input name="typingpool_url" type="hidden" value="<%= audio_url %>" /><input name="typingpool_project_id" type="hidden" value="<%= project_id %>" />Enter your transcription below:</p>
17
+ <p><textarea name="transcription" cols="80" rows="15"></textarea></p>
18
+ <p><input type="submit" value="Submit" id="disable_on_preview"></p>
19
+ </form>
@@ -0,0 +1,3 @@
1
+
2
+ </body>
3
+ </html>
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <script><%= read 'amazon-init.js' %></script>
6
+ <style><%= read 'main.css' %></style>
7
+ <title><%= title %></title>
8
+ </head>
9
+
10
+ <body onload="setAmazonAssignmentId()">
11
+ <h2><%= title %></h2>
@@ -0,0 +1,4 @@
1
+ <blockquote>
2
+ <p><%= voice1 %>:&nbsp;Do you think it was a mistake to set up Saturn as its own Corporation?</p>
3
+ <p><%= voice2 %>:&nbsp;In hindsight, you can look at it that way, and I think a lot of people do, quite honestly. They don't think GM needed another brand. But at the time this was all developed. It sure seemed like a good idea.</p>
4
+ </blockquote>
@@ -0,0 +1,5 @@
1
+
2
+ <h3>Labeling</h3>
3
+ <p>Label each speaker with a colon, like this:</p>
4
+ <% voice1 ||= 'Male 1' %><% voice2 ||= 'Male 2' %>
5
+ <%= render 'labeling-example' %>
@@ -0,0 +1,6 @@
1
+ <% length_description = [] %>
2
+ <% length_description.push("#{chunk_hours} hour") if chunk_hours %>
3
+ <% length_description.push("#{chunk_minutes} minute") if chunk_minutes %>
4
+ <% length_description.push("#{chunk_seconds} second") if chunk_seconds %>
5
+ <% length_description = length_description.join(' ') %>
6
+ <%= length_description %>
@@ -0,0 +1,10 @@
1
+ <% if voices_count > 0 %>
2
+ <h3>Names</h3>
3
+ <p>There are <%= voices_count %> speakers:
4
+ <ul>
5
+ <% 1.upto(voices_count) do |i| %>
6
+ <li><strong><%= send("voice#{i}") %></strong><% unless send("voice#{i}title").to_s.empty? %>, <%= send("voice#{i}title") %><% end %>.
7
+ <% end %>
8
+ </ul>
9
+ <% end %>
10
+
@@ -0,0 +1,11 @@
1
+ <% self.title = 'Transcribe MP3 of ' + render('partials/length-description') + ' speech' %>
2
+
3
+ <%= render 'partials/header' %>
4
+ <p id="description">I split up a speech into <%= render 'partials/length-description' %> parts, you transcribe the audio for one <%= render 'partials/length-description' %> part.</p>
5
+
6
+ <h3>Formatting</h3>
7
+ <p>There should only be one speaker. Simply type what the person says. No need to label. If the speaker changes, type [SPEAKER CHANGES].</p>
8
+
9
+ <%= render 'partials/entry' %>
10
+
11
+ <%= render 'partials/footer' %>
@@ -0,0 +1,21 @@
1
+ amazon:
2
+ key:
3
+ secret:
4
+ bucket:
5
+ transcripts:
6
+ templates:
7
+ cache: ~/.typingpool.cache
8
+ assign:
9
+ confirm: yes
10
+ reward: 0.75
11
+ deadline: 3h
12
+ approval: 1d
13
+ lifetime: 2d
14
+ qualify:
15
+ - approval_rate >= 95
16
+ - hits_approved >= 100
17
+ keywords:
18
+ - transcription
19
+ - audio
20
+ - mp3
21
+
@@ -0,0 +1,8 @@
1
+ Deleting any files in this folder, in the data folder, or in the
2
+ audio/chunks folder will make it impossible for typingpool to function
3
+ properly.
4
+
5
+ You may safely delete files in the audio/originals folder.
6
+
7
+ You may safely edit transcript.html, etc/transcript.css, and
8
+ data/subtitle.txt.
@@ -0,0 +1,25 @@
1
+ function audioCompat() {
2
+ var audioTag = window.document.createElement('audio');
3
+ //Check for HTML5 audio tag compatibility
4
+ if (!(audioTag.canPlayType && audioTag.canPlayType('audio/mpeg') && (audioTag.canPlayType('audio/mpeg') != 'no'))){
5
+ //not compatible - fallback to Flash
6
+ AudioPlayer.setup("etc/player/player.swf", { width: 290 });
7
+ var audioTags = window.document.getElementsByTagName('audio');
8
+ var audioTagMeta = [];
9
+ for (var i = 0; i < audioTags.length; i++) {
10
+ var tag = audioTags[i];
11
+ if (! tag.id){
12
+ tag.id = '_typingpool_audio_' + i;
13
+ }
14
+ audioTagMeta.push({'id':tag.id, 'src':tag.src});
15
+ }
16
+ for (var i = 0; i < audioTagMeta.length; i++) {
17
+ var tagMeta = audioTagMeta[i];
18
+ AudioPlayer.embed(tagMeta.id, {'soundFile': tagMeta.src, 'noinfo': 'yes'});
19
+ }
20
+ }
21
+ }
22
+
23
+ if (window.addEventListener){ window.addEventListener('load', audioCompat, false) }
24
+ else if (document.addEventListener){ document.addEventListener('load', audioCompat, false) }
25
+ else if (window.attachEvent){ window.attachEvent('onload', audioCompat) }
@@ -0,0 +1,4 @@
1
+ /* SWFObject v2.2 <http://code.google.com/p/swfobject/>
2
+ is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
3
+ */
4
+ var audioplayer_swfobject=function(){var d="undefined",R="object",s="Shockwave Flash",w="ShockwaveFlash.ShockwaveFlash",Q="application/x-shockwave-flash",r="SWFObjectExprInst",X="onreadystatechange",o=window,J=document,T=navigator,t=false,u=[H],O=[],n=[],i=[],L,q,e,b,j=false,A=false,N,g,M=true,m=function(){var AA=typeof J.getElementById!=d&&typeof J.getElementsByTagName!=d&&typeof J.createElement!=d,AH=T.userAgent.toLowerCase(),y=T.platform.toLowerCase(),AE=y?/win/.test(y):/win/.test(AH),AC=y?/mac/.test(y):/mac/.test(AH),AF=/webkit/.test(AH)?parseFloat(AH.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,x=!+"\v1",AG=[0,0,0],AB=null;if(typeof T.plugins!=d&&typeof T.plugins[s]==R){AB=T.plugins[s].description;if(AB&&!(typeof T.mimeTypes!=d&&T.mimeTypes[Q]&&!T.mimeTypes[Q].enabledPlugin)){t=true;x=false;AB=AB.replace(/^.*\s+(\S+\s+\S+$)/,"$1");AG[0]=parseInt(AB.replace(/^(.*)\..*$/,"$1"),10);AG[1]=parseInt(AB.replace(/^.*\.(.*)\s.*$/,"$1"),10);AG[2]=/[a-zA-Z]/.test(AB)?parseInt(AB.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof o.ActiveXObject!=d){try{var AD=new ActiveXObject(w);if(AD){AB=AD.GetVariable("$version");if(AB){x=true;AB=AB.split(" ")[1].split(",");AG=[parseInt(AB[0],10),parseInt(AB[1],10),parseInt(AB[2],10)]}}}catch(z){}}}return{w3:AA,pv:AG,wk:AF,ie:x,win:AE,mac:AC}}(),K=function(){if(!m.w3){return }if((typeof J.readyState!=d&&J.readyState=="complete")||(typeof J.readyState==d&&(J.getElementsByTagName("body")[0]||J.body))){F()}if(!j){if(typeof J.addEventListener!=d){J.addEventListener("DOMContentLoaded",F,false)}if(m.ie&&m.win){J.attachEvent(X,function(){if(J.readyState=="complete"){J.detachEvent(X,arguments.callee);F()}});if(o==top){(function(){if(j){return }try{J.documentElement.doScroll("left")}catch(x){setTimeout(arguments.callee,0);return }F()})()}}if(m.wk){(function(){if(j){return }if(!/loaded|complete/.test(J.readyState)){setTimeout(arguments.callee,0);return }F()})()}S(F)}}();function F(){if(j){return }try{var z=J.getElementsByTagName("body")[0].appendChild(c("span"));z.parentNode.removeChild(z)}catch(AA){return }j=true;var x=u.length;for(var y=0;y<x;y++){u[y]()}}function k(x){if(j){x()}else{u[u.length]=x}}function S(y){if(typeof o.addEventListener!=d){o.addEventListener("load",y,false)}else{if(typeof J.addEventListener!=d){J.addEventListener("load",y,false)}else{if(typeof o.attachEvent!=d){I(o,"onload",y)}else{if(typeof o.onload=="function"){var x=o.onload;o.onload=function(){x();y()}}else{o.onload=y}}}}}function H(){if(t){v()}else{h()}}function v(){var x=J.getElementsByTagName("body")[0];var AA=c(R);AA.setAttribute("type",Q);var z=x.appendChild(AA);if(z){var y=0;(function(){if(typeof z.GetVariable!=d){var AB=z.GetVariable("$version");if(AB){AB=AB.split(" ")[1].split(",");m.pv=[parseInt(AB[0],10),parseInt(AB[1],10),parseInt(AB[2],10)]}}else{if(y<10){y++;setTimeout(arguments.callee,10);return }}x.removeChild(AA);z=null;h()})()}else{h()}}function h(){var AG=O.length;if(AG>0){for(var AF=0;AF<AG;AF++){var y=O[AF].id;var AB=O[AF].callbackFn;var AA={success:false,id:y};if(m.pv[0]>0){var AE=C(y);if(AE){if(f(O[AF].swfVersion)&&!(m.wk&&m.wk<312)){W(y,true);if(AB){AA.success=true;AA.ref=Z(y);AB(AA)}}else{if(O[AF].expressInstall&&a()){var AI={};AI.data=O[AF].expressInstall;AI.width=AE.getAttribute("width")||"0";AI.height=AE.getAttribute("height")||"0";if(AE.getAttribute("class")){AI.styleclass=AE.getAttribute("class")}if(AE.getAttribute("align")){AI.align=AE.getAttribute("align")}var AH={};var x=AE.getElementsByTagName("param");var AC=x.length;for(var AD=0;AD<AC;AD++){if(x[AD].getAttribute("name").toLowerCase()!="movie"){AH[x[AD].getAttribute("name")]=x[AD].getAttribute("value")}}p(AI,AH,y,AB)}else{P(AE);if(AB){AB(AA)}}}}}else{W(y,true);if(AB){var z=Z(y);if(z&&typeof z.SetVariable!=d){AA.success=true;AA.ref=z}AB(AA)}}}}}function Z(AA){var x=null;var y=C(AA);if(y&&y.nodeName=="OBJECT"){if(typeof y.SetVariable!=d){x=y}else{var z=y.getElementsByTagName(R)[0];if(z){x=z}}}return x}function a(){return !A&&f("6.0.65")&&(m.win||m.mac)&&!(m.wk&&m.wk<312)}function p(AA,AB,x,z){A=true;e=z||null;b={success:false,id:x};var AE=C(x);if(AE){if(AE.nodeName=="OBJECT"){L=G(AE);q=null}else{L=AE;q=x}AA.id=r;if(typeof AA.width==d||(!/%$/.test(AA.width)&&parseInt(AA.width,10)<310)){AA.width="310"}if(typeof AA.height==d||(!/%$/.test(AA.height)&&parseInt(AA.height,10)<137)){AA.height="137"}J.title=J.title.slice(0,47)+" - Flash Player Installation";var AD=m.ie&&m.win?"ActiveX":"PlugIn",AC="MMredirectURL="+o.location.toString().replace(/&/g,"%26")+"&MMplayerType="+AD+"&MMdoctitle="+J.title;if(typeof AB.flashvars!=d){AB.flashvars+="&"+AC}else{AB.flashvars=AC}if(m.ie&&m.win&&AE.readyState!=4){var y=c("div");x+="SWFObjectNew";y.setAttribute("id",x);AE.parentNode.insertBefore(y,AE);AE.style.display="none";(function(){if(AE.readyState==4){AE.parentNode.removeChild(AE)}else{setTimeout(arguments.callee,10)}})()}U(AA,AB,x)}}function P(y){if(m.ie&&m.win&&y.readyState!=4){var x=c("div");y.parentNode.insertBefore(x,y);x.parentNode.replaceChild(G(y),x);y.style.display="none";(function(){if(y.readyState==4){y.parentNode.removeChild(y)}else{setTimeout(arguments.callee,10)}})()}else{y.parentNode.replaceChild(G(y),y)}}function G(AB){var AA=c("div");if(m.win&&m.ie){AA.innerHTML=AB.innerHTML}else{var y=AB.getElementsByTagName(R)[0];if(y){var AC=y.childNodes;if(AC){var x=AC.length;for(var z=0;z<x;z++){if(!(AC[z].nodeType==1&&AC[z].nodeName=="PARAM")&&!(AC[z].nodeType==8)){AA.appendChild(AC[z].cloneNode(true))}}}}}return AA}function U(AI,AG,y){var x,AA=C(y);if(m.wk&&m.wk<312){return x}if(AA){if(typeof AI.id==d){AI.id=y}if(m.ie&&m.win){var AH="";for(var AE in AI){if(AI[AE]!=Object.prototype[AE]){if(AE.toLowerCase()=="data"){AG.movie=AI[AE]}else{if(AE.toLowerCase()=="styleclass"){AH+=' class="'+AI[AE]+'"'}else{if(AE.toLowerCase()!="classid"){AH+=" "+AE+'="'+AI[AE]+'"'}}}}}var AF="";for(var AD in AG){if(AG[AD]!=Object.prototype[AD]){AF+='<param name="'+AD+'" value="'+AG[AD]+'" />'}}AA.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+AH+">"+AF+"</object>";n[n.length]=AI.id;x=C(AI.id)}else{var z=c(R);z.setAttribute("type",Q);for(var AC in AI){if(AI[AC]!=Object.prototype[AC]){if(AC.toLowerCase()=="styleclass"){z.setAttribute("class",AI[AC])}else{if(AC.toLowerCase()!="classid"){z.setAttribute(AC,AI[AC])}}}}for(var AB in AG){if(AG[AB]!=Object.prototype[AB]&&AB.toLowerCase()!="movie"){E(z,AB,AG[AB])}}AA.parentNode.replaceChild(z,AA);x=z}}return x}function E(z,x,y){var AA=c("param");AA.setAttribute("name",x);AA.setAttribute("value",y);z.appendChild(AA)}function Y(y){var x=C(y);if(x&&x.nodeName=="OBJECT"){if(m.ie&&m.win){x.style.display="none";(function(){if(x.readyState==4){B(y)}else{setTimeout(arguments.callee,10)}})()}else{x.parentNode.removeChild(x)}}}function B(z){var y=C(z);if(y){for(var x in y){if(typeof y[x]=="function"){y[x]=null}}y.parentNode.removeChild(y)}}function C(z){var x=null;try{x=J.getElementById(z)}catch(y){}return x}function c(x){return J.createElement(x)}function I(z,x,y){z.attachEvent(x,y);i[i.length]=[z,x,y]}function f(z){var y=m.pv,x=z.split(".");x[0]=parseInt(x[0],10);x[1]=parseInt(x[1],10)||0;x[2]=parseInt(x[2],10)||0;return(y[0]>x[0]||(y[0]==x[0]&&y[1]>x[1])||(y[0]==x[0]&&y[1]==x[1]&&y[2]>=x[2]))?true:false}function V(AC,y,AD,AB){if(m.ie&&m.mac){return }var AA=J.getElementsByTagName("head")[0];if(!AA){return }var x=(AD&&typeof AD=="string")?AD:"screen";if(AB){N=null;g=null}if(!N||g!=x){var z=c("style");z.setAttribute("type","text/css");z.setAttribute("media",x);N=AA.appendChild(z);if(m.ie&&m.win&&typeof J.styleSheets!=d&&J.styleSheets.length>0){N=J.styleSheets[J.styleSheets.length-1]}g=x}if(m.ie&&m.win){if(N&&typeof N.addRule==R){N.addRule(AC,y)}}else{if(N&&typeof J.createTextNode!=d){N.appendChild(J.createTextNode(AC+" {"+y+"}"))}}}function W(z,x){if(!M){return }var y=x?"visible":"hidden";if(j&&C(z)){C(z).style.visibility=y}else{V("#"+z,"visibility:"+y)}}function l(y){var z=/[\\\"<>\.;]/;var x=z.exec(y)!=null;return x&&typeof encodeURIComponent!=d?encodeURIComponent(y):y}var D=function(){if(m.ie&&m.win){window.attachEvent("onunload",function(){var AC=i.length;for(var AB=0;AB<AC;AB++){i[AB][0].detachEvent(i[AB][1],i[AB][2])}var z=n.length;for(var AA=0;AA<z;AA++){Y(n[AA])}for(var y in m){m[y]=null}m=null;for(var x in audioplayer_swfobject){audioplayer_swfobject[x]=null}audioplayer_swfobject=null})}}();return{registerObject:function(AB,x,AA,z){if(m.w3&&AB&&x){var y={};y.id=AB;y.swfVersion=x;y.expressInstall=AA;y.callbackFn=z;O[O.length]=y;W(AB,false)}else{if(z){z({success:false,id:AB})}}},getObjectById:function(x){if(m.w3){return Z(x)}},embedSWF:function(AB,AH,AE,AG,y,AA,z,AD,AF,AC){var x={success:false,id:AH};if(m.w3&&!(m.wk&&m.wk<312)&&AB&&AH&&AE&&AG&&y){W(AH,false);k(function(){AE+="";AG+="";var AJ={};if(AF&&typeof AF===R){for(var AL in AF){AJ[AL]=AF[AL]}}AJ.data=AB;AJ.width=AE;AJ.height=AG;var AM={};if(AD&&typeof AD===R){for(var AK in AD){AM[AK]=AD[AK]}}if(z&&typeof z===R){for(var AI in z){if(typeof AM.flashvars!=d){AM.flashvars+="&"+AI+"="+z[AI]}else{AM.flashvars=AI+"="+z[AI]}}}if(f(y)){var AN=U(AJ,AM,AH);if(AJ.id==AH){W(AH,true)}x.success=true;x.ref=AN}else{if(AA&&a()){AJ.data=AA;p(AJ,AM,AH,AC);return }else{W(AH,true)}}if(AC){AC(x)}})}else{if(AC){AC(x)}}},switchOffAutoHideShow:function(){M=false},ua:m,getFlashPlayerVersion:function(){return{major:m.pv[0],minor:m.pv[1],release:m.pv[2]}},hasFlashPlayerVersion:f,createSWF:function(z,y,x){if(m.w3){return U(z,y,x)}else{return undefined}},showExpressInstall:function(z,AA,x,y){if(m.w3&&a()){p(z,AA,x,y)}},removeSWF:function(x){if(m.w3){Y(x)}},createCSS:function(AA,z,y,x){if(m.w3){V(AA,z,y,x)}},addDomLoadEvent:k,addLoadEvent:S,getQueryParamValue:function(AA){var z=J.location.search||J.location.hash;if(z){if(/\?/.test(z)){z=z.split("?")[1]}if(AA==null){return l(z)}var y=z.split("&");for(var x=0;x<y.length;x++){if(y[x].substring(0,y[x].indexOf("="))==AA){return l(y[x].substring((y[x].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(A){var x=C(r);if(x&&L){x.parentNode.replaceChild(L,x);if(q){W(q,true);if(m.ie&&m.win){L.style.display="block"}}if(e){e(b)}}A=false}}}}();var AudioPlayer=function(){var H=[];var D;var F="";var A={};var E=-1;var G="9";function B(I){if(document.all&&!window[I]){for(var J=0;J<document.forms.length;J++){if(document.forms[J][I]){return document.forms[J][I];break}}}return document.all?window[I]:document[I]}function C(I,J,K){B(I).addListener(J,K)}return{setup:function(J,I){F=J;A=I;if(audioplayer_swfobject.hasFlashPlayerVersion(G)){audioplayer_swfobject.switchOffAutoHideShow();audioplayer_swfobject.createCSS("p.audioplayer_container span","visibility:hidden;height:24px;overflow:hidden;padding:0;border:none;")}},getPlayer:function(I){return B(I)},addListener:function(I,J,K){C(I,J,K)},embed:function(I,K){var N={};var L;var J={};var O={};var M={};for(L in A){N[L]=A[L]}for(L in K){N[L]=K[L]}if(N.transparentpagebg=="yes"){J.bgcolor="#FFFFFF";J.wmode="transparent"}else{if(N.pagebg){J.bgcolor="#"+N.pagebg}J.wmode="opaque"}J.menu="false";for(L in N){if(L=="pagebg"||L=="width"||L=="transparentpagebg"){continue}O[L]=N[L]}M.name=I;M.style="outline: none";O.playerID=I;audioplayer_swfobject.embedSWF(F,I,N.width.toString(),"24",G,false,O,J,M);H.push(I)},syncVolumes:function(I,K){E=K;for(var J=0;J<H.length;J++){if(H[J]!=I){B(H[J]).setVolume(E)}}},activate:function(I,J){if(D&&D!=I){B(D).close()}D=I},load:function(K,I,L,J){B(K).load(I,L,J)},close:function(I){B(I).close();if(I==D){D=null}},open:function(I,J){if(J==undefined){J=1}B(I).open(J==undefined?0:J-1)},getVolume:function(I){return E}}}();
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010 Martin Laine
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,49 @@
1
+ body {
2
+ color: black;
3
+ background: white;
4
+ font-family: Georgia, Times, "Times New Roman", serif;
5
+ }
6
+
7
+ h1, h2, h3, h4, h5 {
8
+ font-family: Helvetica, Arial, sans-serif;
9
+ font-weight: normal;
10
+ }
11
+
12
+ h1{
13
+ margin-bottom: 0.33em
14
+ }
15
+
16
+ h2{
17
+ font-size: 1.125em;
18
+ margin-top: 0.125em;
19
+ }
20
+
21
+ h3, h4 {
22
+ color:gray;
23
+ font-size: 0.875em;
24
+ }
25
+
26
+ h3{
27
+ margin-top: 0.5em;
28
+ margin-bottom: 0.125em;
29
+ line-height: auto;
30
+ }
31
+
32
+ h4{
33
+ margin-top: 0.125em;
34
+ margin-bottom: 0.125em;
35
+ }
36
+
37
+ p{
38
+ margin-top: 0.125em;
39
+ margin-bottom: 2em;
40
+ line-height: 140%;
41
+ }
42
+
43
+ a[href], a[href]:link {
44
+ color: gray;
45
+ }
46
+
47
+ a[href]:visited {
48
+ color: silver;
49
+ }
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title><%= transcript.title %> transcript</title>
6
+ <link rel="stylesheet" href="etc/transcript.css">
7
+ <script src="etc/player/audio-player.js" type="text/javascript"></script>
8
+ <script src="etc/audio-compat.js" type="text/javascript"></script>
9
+ </head>
10
+ <body>
11
+ <h1><%= transcript.title %> transcript</h1>
12
+ <h2><% if transcript.subtitle %><%= transcript.subtitle %><% end %></h2>
13
+
14
+ <% transcript.sort.each do |chunk| %>
15
+
16
+ <h3><%= chunk.offset %></h3>
17
+ <h3><audio src="audio/chunks/<%= chunk.filename_local %>" preload="none" controls></audio></h3>
18
+ <h4><a href="https://requester.mturk.com/bulk/workers/<%= chunk.worker %>">Worker</a> | <a href="https://requester.mturk.com/mturk/manageHIT?HITId=<%= chunk.hit %>&amp;viewableEditPane=manageHIT_downloadResults">HIT</a></h4>
19
+
20
+ <p><%= chunk.body_as_html %>
21
+ <% end %>
22
+ </body>
23
+ </html>