bagel 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.prettierignore +2 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +187 -0
- data/Rakefile +12 -0
- data/bagel.gemspec +32 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/bagel/colors.rb +8 -0
- data/lib/bagel/fonts.rb +3 -0
- data/lib/bagel/graphic/comment.rb +49 -0
- data/lib/bagel/graphic/intro.rb +49 -0
- data/lib/bagel/graphic/scoreboard/context.rb +28 -0
- data/lib/bagel/graphic/scoreboard/games.rb +13 -0
- data/lib/bagel/graphic/scoreboard/name.rb +32 -0
- data/lib/bagel/graphic/scoreboard/points.rb +13 -0
- data/lib/bagel/graphic/scoreboard/score.rb +38 -0
- data/lib/bagel/graphic/scoreboard/server.rb +16 -0
- data/lib/bagel/graphic/scoreboard/sets.rb +13 -0
- data/lib/bagel/graphic/scoreboard.rb +102 -0
- data/lib/bagel/graphic/stats/duration.rb +33 -0
- data/lib/bagel/graphic/stats/header.rb +32 -0
- data/lib/bagel/graphic/stats/result.rb +41 -0
- data/lib/bagel/graphic/stats/stat.rb +48 -0
- data/lib/bagel/graphic/stats.rb +80 -0
- data/lib/bagel/graphic.rb +23 -0
- data/lib/bagel/tennis/checker.rb +100 -0
- data/lib/bagel/tennis/outcome.rb +48 -0
- data/lib/bagel/tennis/player.rb +22 -0
- data/lib/bagel/tennis/point.rb +17 -0
- data/lib/bagel/tennis/score.rb +68 -0
- data/lib/bagel/tennis/scoring.rb +26 -0
- data/lib/bagel/tennis/serve.rb +36 -0
- data/lib/bagel/tennis/stats/aces.rb +28 -0
- data/lib/bagel/tennis/stats/break_points_won.rb +33 -0
- data/lib/bagel/tennis/stats/double_faults.rb +28 -0
- data/lib/bagel/tennis/stats/first_serve_in.rb +33 -0
- data/lib/bagel/tennis/stats/first_serve_points_won.rb +33 -0
- data/lib/bagel/tennis/stats/net_points_won.rb +33 -0
- data/lib/bagel/tennis/stats/points_won.rb +24 -0
- data/lib/bagel/tennis/stats/second_serve_points_won.rb +33 -0
- data/lib/bagel/tennis/stats/stat.rb +40 -0
- data/lib/bagel/tennis/stats/unforced_errors.rb +24 -0
- data/lib/bagel/tennis/stats/winners.rb +24 -0
- data/lib/bagel/tennis/stats.rb +16 -0
- data/lib/bagel/tennis.rb +13 -0
- data/lib/bagel/timeframe.rb +31 -0
- data/lib/bagel/version.rb +5 -0
- data/lib/bagel/video/clip.rb +41 -0
- data/lib/bagel/video/clip_builder_factory.rb +13 -0
- data/lib/bagel/video/clip_builders/clip_builder.rb +32 -0
- data/lib/bagel/video/clip_builders/intro_clip_builder.rb +37 -0
- data/lib/bagel/video/clip_builders/point_clip_builder.rb +67 -0
- data/lib/bagel/video/clip_builders/stats_clip_builder.rb +56 -0
- data/lib/bagel/video/clip_data_parser.rb +39 -0
- data/lib/bagel/video/clip_director.rb +16 -0
- data/lib/bagel/video/clip_saver.rb +59 -0
- data/lib/bagel/video/fade.rb +24 -0
- data/lib/bagel/video/ffmpeg/concat_file.rb +45 -0
- data/lib/bagel/video/ffmpeg/fade_filter.rb +34 -0
- data/lib/bagel/video/ffmpeg/overlay_filter.rb +52 -0
- data/lib/bagel/video/ffmpeg.rb +41 -0
- data/lib/bagel/video/orientation.rb +8 -0
- data/lib/bagel/video/overlays/comment.rb +15 -0
- data/lib/bagel/video/overlays/intro.rb +15 -0
- data/lib/bagel/video/overlays/overlay.rb +28 -0
- data/lib/bagel/video/overlays/scoreboard.rb +15 -0
- data/lib/bagel/video/overlays/second_serve.rb +15 -0
- data/lib/bagel/video/overlays/stats.rb +15 -0
- data/lib/bagel/video/padding.rb +9 -0
- data/lib/bagel/video/video.rb +18 -0
- data/lib/bagel/video.rb +32 -0
- data/lib/bagel.rb +76 -0
- metadata +196 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module Bagel::Tennis::Stats
|
2
|
+
class FirstServeIn < Stat
|
3
|
+
private attr_reader :p1_label, :p2_label, :p1_percentage, :p2_percentage
|
4
|
+
|
5
|
+
def name
|
6
|
+
'1st SERVE IN'
|
7
|
+
end
|
8
|
+
|
9
|
+
def values
|
10
|
+
[ p1_label, p2_label ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def superior
|
14
|
+
[ p1_percentage > p2_percentage, p2_percentage > p1_percentage ]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def calculate
|
20
|
+
p1_served = points.select(&:p1_serve?)
|
21
|
+
p2_served = points.select(&:p2_serve?)
|
22
|
+
|
23
|
+
p1_served_no_faults = p1_served.select(&:no_faults?)
|
24
|
+
p2_served_no_faults = p2_served.select(&:no_faults?)
|
25
|
+
|
26
|
+
@p1_percentage = percentage(p1_served.count, p1_served_no_faults.count)
|
27
|
+
@p2_percentage = percentage(p2_served.count, p2_served_no_faults.count)
|
28
|
+
|
29
|
+
@p1_label = label(p1_served.count, p1_served_no_faults.count, p1_percentage)
|
30
|
+
@p2_label = label(p2_served.count, p2_served_no_faults.count, p2_percentage)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Bagel::Tennis::Stats
|
2
|
+
class FirstServePointsWon < Stat
|
3
|
+
private attr_reader :p1_label, :p2_label, :p1_percentage, :p2_percentage
|
4
|
+
|
5
|
+
def name
|
6
|
+
'1st SERVE POINTS WON'
|
7
|
+
end
|
8
|
+
|
9
|
+
def values
|
10
|
+
[ p1_label, p2_label ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def superior
|
14
|
+
[ p1_percentage > p2_percentage, p2_percentage > p1_percentage ]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def calculate
|
20
|
+
p1_first_serves = points.select(&:p1_serve?).select(&:no_faults?)
|
21
|
+
p2_first_serves = points.select(&:p2_serve?).select(&:no_faults?)
|
22
|
+
|
23
|
+
p1_first_serves_won = p1_first_serves.select(&:p1_won?)
|
24
|
+
p2_first_serves_won = p2_first_serves.select(&:p2_won?)
|
25
|
+
|
26
|
+
@p1_percentage = percentage(p1_first_serves.count, p1_first_serves_won.count)
|
27
|
+
@p2_percentage = percentage(p2_first_serves.count, p2_first_serves_won.count)
|
28
|
+
|
29
|
+
@p1_label = label(p1_first_serves.count, p1_first_serves_won.count, p1_percentage)
|
30
|
+
@p2_label = label(p2_first_serves.count, p2_first_serves_won.count, p2_percentage)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Bagel::Tennis::Stats
|
2
|
+
class NetPointsWon < Stat
|
3
|
+
private attr_reader :p1_label, :p2_label, :p1_percentage, :p2_percentage
|
4
|
+
|
5
|
+
def name
|
6
|
+
'NET POINTS WON'
|
7
|
+
end
|
8
|
+
|
9
|
+
def values
|
10
|
+
[ p1_label, p2_label ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def superior
|
14
|
+
[ p1_percentage > p2_percentage, p2_percentage > p1_percentage ]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def calculate
|
20
|
+
p1_net_points = points.select(&:p1_net_point?)
|
21
|
+
p2_net_points = points.select(&:p2_net_point?)
|
22
|
+
|
23
|
+
p1_net_points_won = p1_net_points.select(&:p1_won?)
|
24
|
+
p2_net_points_won = p2_net_points.select(&:p2_won?)
|
25
|
+
|
26
|
+
@p1_percentage = percentage(p1_net_points.count, p1_net_points_won.count)
|
27
|
+
@p2_percentage = percentage(p2_net_points.count, p2_net_points_won.count)
|
28
|
+
|
29
|
+
@p1_label = label(p1_net_points.count, p1_net_points_won.count, p1_percentage)
|
30
|
+
@p2_label = label(p2_net_points.count, p2_net_points_won.count, p2_percentage)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Bagel::Tennis::Stats
|
2
|
+
class PointsWon < Stat
|
3
|
+
private attr_reader :p1, :p2
|
4
|
+
|
5
|
+
def name
|
6
|
+
'POINTS WON'
|
7
|
+
end
|
8
|
+
|
9
|
+
def values
|
10
|
+
[ p1, p2 ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def superior
|
14
|
+
[ p1 > p2, p2 > p1 ]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def calculate
|
20
|
+
@p1 = points.select(&:p1_won?).count
|
21
|
+
@p2 = points.select(&:p2_won?).count
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Bagel::Tennis::Stats
|
2
|
+
class SecondServePointsWon < Stat
|
3
|
+
private attr_reader :p1_label, :p2_label, :p1_percentage, :p2_percentage
|
4
|
+
|
5
|
+
def name
|
6
|
+
'2nd SERVE POINTS WON'
|
7
|
+
end
|
8
|
+
|
9
|
+
def values
|
10
|
+
[ p1_label, p2_label ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def superior
|
14
|
+
[ p1_percentage > p2_percentage, p2_percentage > p1_percentage ]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def calculate
|
20
|
+
p1_second_serves = points.select(&:p1_serve?).select(&:second_serve?)
|
21
|
+
p2_second_serves = points.select(&:p2_serve?).select(&:second_serve?)
|
22
|
+
|
23
|
+
p1_second_serves_won = p1_second_serves.select(&:p1_won?)
|
24
|
+
p2_second_serves_won = p2_second_serves.select(&:p2_won?)
|
25
|
+
|
26
|
+
@p1_percentage = percentage(p1_second_serves.count, p1_second_serves_won.count)
|
27
|
+
@p2_percentage = percentage(p2_second_serves.count, p2_second_serves_won.count)
|
28
|
+
|
29
|
+
@p1_label = label(p1_second_serves.count, p1_second_serves_won.count, p1_percentage)
|
30
|
+
@p2_label = label(p2_second_serves.count, p2_second_serves_won.count, p2_percentage)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Bagel::Tennis::Stats
|
2
|
+
class Stat
|
3
|
+
private attr_reader :points
|
4
|
+
|
5
|
+
def initialize(points)
|
6
|
+
@points = points
|
7
|
+
calculate
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
raise_not_implemented_error(self.class, __method__)
|
12
|
+
end
|
13
|
+
|
14
|
+
def values
|
15
|
+
raise_not_implemented_error(self.class, __method__)
|
16
|
+
end
|
17
|
+
|
18
|
+
def superior
|
19
|
+
raise_not_implemented_error(self.class, __method__)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def raise_not_implemented_error(klass, method)
|
25
|
+
raise NotImplementedError, "#{klass} has not implemented method '#{method}'"
|
26
|
+
end
|
27
|
+
|
28
|
+
def calculate
|
29
|
+
raise_not_implemented_error(self.class, __method__)
|
30
|
+
end
|
31
|
+
|
32
|
+
def percentage(total, won)
|
33
|
+
(won / total.to_f * 100).round rescue 0
|
34
|
+
end
|
35
|
+
|
36
|
+
def label(total, won, percentage)
|
37
|
+
"#{won}/#{total} (#{percentage}%)"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Bagel::Tennis::Stats
|
2
|
+
class UnforcedErrors < Stat
|
3
|
+
private attr_reader :p1, :p2
|
4
|
+
|
5
|
+
def name
|
6
|
+
'UNFORCED ERRORS'
|
7
|
+
end
|
8
|
+
|
9
|
+
def values
|
10
|
+
[ p1, p2 ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def superior
|
14
|
+
[ p1 < p2, p2 < p1 ]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def calculate
|
20
|
+
@p1 = points.select(&:p2_won?).select(&:unforced_error?).count
|
21
|
+
@p2 = points.select(&:p1_won?).select(&:unforced_error?).count
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Bagel::Tennis::Stats
|
2
|
+
class Winners < Stat
|
3
|
+
private attr_reader :p1, :p2
|
4
|
+
|
5
|
+
def name
|
6
|
+
'WINNERS'
|
7
|
+
end
|
8
|
+
|
9
|
+
def values
|
10
|
+
[ p1, p2 ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def superior
|
14
|
+
[ p1 > p2, p2 > p1 ]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def calculate
|
20
|
+
@p1 = points.select(&:p1_won?).select(&:winner?).count
|
21
|
+
@p2 = points.select(&:p2_won?).select(&:winner?).count
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'bagel/tennis/stats/stat'
|
2
|
+
require 'bagel/tennis/stats/aces'
|
3
|
+
require 'bagel/tennis/stats/double_faults'
|
4
|
+
require 'bagel/tennis/stats/first_serve_in'
|
5
|
+
require 'bagel/tennis/stats/first_serve_points_won'
|
6
|
+
require 'bagel/tennis/stats/second_serve_points_won'
|
7
|
+
require 'bagel/tennis/stats/winners'
|
8
|
+
require 'bagel/tennis/stats/unforced_errors'
|
9
|
+
require 'bagel/tennis/stats/net_points_won'
|
10
|
+
require 'bagel/tennis/stats/break_points_won'
|
11
|
+
require 'bagel/tennis/stats/points_won'
|
12
|
+
|
13
|
+
module Bagel::Tennis
|
14
|
+
module Stats
|
15
|
+
end
|
16
|
+
end
|
data/lib/bagel/tennis.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bagel/tennis/point'
|
2
|
+
require 'bagel/tennis/score'
|
3
|
+
require 'bagel/tennis/serve'
|
4
|
+
require 'bagel/tennis/outcome'
|
5
|
+
require 'bagel/tennis/scoring'
|
6
|
+
require 'bagel/tennis/checker'
|
7
|
+
require 'bagel/tennis/player'
|
8
|
+
require 'bagel/tennis/stats'
|
9
|
+
|
10
|
+
module Bagel
|
11
|
+
module Tennis
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Bagel
|
2
|
+
class Timeframe
|
3
|
+
attr_reader :start, :finish
|
4
|
+
|
5
|
+
SECONDS_IN_MINUTE = 60
|
6
|
+
SECONDS_IN_HOUR = 3600
|
7
|
+
|
8
|
+
def initialize(start, finish)
|
9
|
+
@start = start
|
10
|
+
@finish = finish
|
11
|
+
end
|
12
|
+
|
13
|
+
def duration
|
14
|
+
seconds(finish) - seconds(start)
|
15
|
+
end
|
16
|
+
|
17
|
+
def duration_minutes
|
18
|
+
duration / SECONDS_IN_MINUTE
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def seconds(timestamp)
|
24
|
+
parts = timestamp.split(':')
|
25
|
+
s = parts.pop
|
26
|
+
m = parts.pop
|
27
|
+
h = parts.pop
|
28
|
+
(h.to_i * SECONDS_IN_HOUR) + (m.to_i * SECONDS_IN_MINUTE) + s.to_i
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Bagel::Video
|
2
|
+
class Clip
|
3
|
+
attr_accessor :id, :fade, :overlays
|
4
|
+
private attr_reader :timeframe
|
5
|
+
|
6
|
+
EXTENSION = '.mp4'
|
7
|
+
|
8
|
+
def initialize(timeframe)
|
9
|
+
@id = nil
|
10
|
+
@fade = nil
|
11
|
+
@timeframe = timeframe
|
12
|
+
@overlays = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def save
|
16
|
+
ClipSaver.new(self).save
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
timeframe.start
|
21
|
+
end
|
22
|
+
|
23
|
+
def duration
|
24
|
+
timeframe.duration
|
25
|
+
end
|
26
|
+
|
27
|
+
def trim_path
|
28
|
+
File.join(Bagel.config.trim_dir, filename)
|
29
|
+
end
|
30
|
+
|
31
|
+
def clip_path
|
32
|
+
File.join(Bagel.config.clip_dir, filename)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def filename
|
38
|
+
"#{id}#{EXTENSION}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Bagel::Video
|
2
|
+
class ClipBuilderFactory
|
3
|
+
BUILDERS = {
|
4
|
+
intro: Bagel::Video::ClipBuilders::IntroClipBuilder,
|
5
|
+
point: Bagel::Video::ClipBuilders::PointClipBuilder,
|
6
|
+
stats: Bagel::Video::ClipBuilders::StatsClipBuilder
|
7
|
+
}
|
8
|
+
|
9
|
+
def self.for(type, data)
|
10
|
+
BUILDERS[type].new(data)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Bagel::Video::ClipBuilders
|
2
|
+
class ClipBuilder
|
3
|
+
attr_reader :clip
|
4
|
+
|
5
|
+
def initialize(data)
|
6
|
+
@clip = Video::Clip.new(Timeframe.new(data[:start], data[:finish]))
|
7
|
+
post_initialize(data)
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_id
|
11
|
+
raise_not_implemented_error(self.class, __method__)
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_clip_fade
|
15
|
+
raise_not_implemented_error(self.class, __method__)
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_overlays
|
19
|
+
raise_not_implemented_error(self.class, __method__)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def post_initialize(data)
|
25
|
+
raise_not_implemented_error(self.class, __method__)
|
26
|
+
end
|
27
|
+
|
28
|
+
def raise_not_implemented_error(klass, method)
|
29
|
+
raise NotImplementedError, "#{klass} has not implemented method '#{method}'"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Bagel::Video::ClipBuilders
|
2
|
+
class IntroClipBuilder < ClipBuilder
|
3
|
+
private attr_reader :title, :description
|
4
|
+
|
5
|
+
ID = '00-intro'
|
6
|
+
|
7
|
+
def set_id
|
8
|
+
clip.id = ID
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_clip_fade
|
12
|
+
clip.fade = Video::Fade.new(in_start: 0, in_duration: 1.5, out_start: 6.5, out_duration: 1.5)
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_overlays
|
16
|
+
add_intro_overlay
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def post_initialize(data)
|
22
|
+
@title, @description = data[:title], data[:description]
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_intro_overlay
|
26
|
+
clip.overlays << intro_overlay
|
27
|
+
end
|
28
|
+
|
29
|
+
def intro_overlay
|
30
|
+
Overlays::Intro.new(intro_graphic)
|
31
|
+
end
|
32
|
+
|
33
|
+
def intro_graphic
|
34
|
+
Graphic::Intro.new(title, description).save
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|