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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.prettierignore +2 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +13 -0
  6. data/CHANGELOG.md +5 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +187 -0
  10. data/Rakefile +12 -0
  11. data/bagel.gemspec +32 -0
  12. data/bin/console +15 -0
  13. data/bin/setup +8 -0
  14. data/lib/bagel/colors.rb +8 -0
  15. data/lib/bagel/fonts.rb +3 -0
  16. data/lib/bagel/graphic/comment.rb +49 -0
  17. data/lib/bagel/graphic/intro.rb +49 -0
  18. data/lib/bagel/graphic/scoreboard/context.rb +28 -0
  19. data/lib/bagel/graphic/scoreboard/games.rb +13 -0
  20. data/lib/bagel/graphic/scoreboard/name.rb +32 -0
  21. data/lib/bagel/graphic/scoreboard/points.rb +13 -0
  22. data/lib/bagel/graphic/scoreboard/score.rb +38 -0
  23. data/lib/bagel/graphic/scoreboard/server.rb +16 -0
  24. data/lib/bagel/graphic/scoreboard/sets.rb +13 -0
  25. data/lib/bagel/graphic/scoreboard.rb +102 -0
  26. data/lib/bagel/graphic/stats/duration.rb +33 -0
  27. data/lib/bagel/graphic/stats/header.rb +32 -0
  28. data/lib/bagel/graphic/stats/result.rb +41 -0
  29. data/lib/bagel/graphic/stats/stat.rb +48 -0
  30. data/lib/bagel/graphic/stats.rb +80 -0
  31. data/lib/bagel/graphic.rb +23 -0
  32. data/lib/bagel/tennis/checker.rb +100 -0
  33. data/lib/bagel/tennis/outcome.rb +48 -0
  34. data/lib/bagel/tennis/player.rb +22 -0
  35. data/lib/bagel/tennis/point.rb +17 -0
  36. data/lib/bagel/tennis/score.rb +68 -0
  37. data/lib/bagel/tennis/scoring.rb +26 -0
  38. data/lib/bagel/tennis/serve.rb +36 -0
  39. data/lib/bagel/tennis/stats/aces.rb +28 -0
  40. data/lib/bagel/tennis/stats/break_points_won.rb +33 -0
  41. data/lib/bagel/tennis/stats/double_faults.rb +28 -0
  42. data/lib/bagel/tennis/stats/first_serve_in.rb +33 -0
  43. data/lib/bagel/tennis/stats/first_serve_points_won.rb +33 -0
  44. data/lib/bagel/tennis/stats/net_points_won.rb +33 -0
  45. data/lib/bagel/tennis/stats/points_won.rb +24 -0
  46. data/lib/bagel/tennis/stats/second_serve_points_won.rb +33 -0
  47. data/lib/bagel/tennis/stats/stat.rb +40 -0
  48. data/lib/bagel/tennis/stats/unforced_errors.rb +24 -0
  49. data/lib/bagel/tennis/stats/winners.rb +24 -0
  50. data/lib/bagel/tennis/stats.rb +16 -0
  51. data/lib/bagel/tennis.rb +13 -0
  52. data/lib/bagel/timeframe.rb +31 -0
  53. data/lib/bagel/version.rb +5 -0
  54. data/lib/bagel/video/clip.rb +41 -0
  55. data/lib/bagel/video/clip_builder_factory.rb +13 -0
  56. data/lib/bagel/video/clip_builders/clip_builder.rb +32 -0
  57. data/lib/bagel/video/clip_builders/intro_clip_builder.rb +37 -0
  58. data/lib/bagel/video/clip_builders/point_clip_builder.rb +67 -0
  59. data/lib/bagel/video/clip_builders/stats_clip_builder.rb +56 -0
  60. data/lib/bagel/video/clip_data_parser.rb +39 -0
  61. data/lib/bagel/video/clip_director.rb +16 -0
  62. data/lib/bagel/video/clip_saver.rb +59 -0
  63. data/lib/bagel/video/fade.rb +24 -0
  64. data/lib/bagel/video/ffmpeg/concat_file.rb +45 -0
  65. data/lib/bagel/video/ffmpeg/fade_filter.rb +34 -0
  66. data/lib/bagel/video/ffmpeg/overlay_filter.rb +52 -0
  67. data/lib/bagel/video/ffmpeg.rb +41 -0
  68. data/lib/bagel/video/orientation.rb +8 -0
  69. data/lib/bagel/video/overlays/comment.rb +15 -0
  70. data/lib/bagel/video/overlays/intro.rb +15 -0
  71. data/lib/bagel/video/overlays/overlay.rb +28 -0
  72. data/lib/bagel/video/overlays/scoreboard.rb +15 -0
  73. data/lib/bagel/video/overlays/second_serve.rb +15 -0
  74. data/lib/bagel/video/overlays/stats.rb +15 -0
  75. data/lib/bagel/video/padding.rb +9 -0
  76. data/lib/bagel/video/video.rb +18 -0
  77. data/lib/bagel/video.rb +32 -0
  78. data/lib/bagel.rb +76 -0
  79. metadata +196 -0
@@ -0,0 +1,28 @@
1
+ module Bagel::Graphic
2
+ class Scoreboard::Context
3
+ HEIGHT = 40
4
+ PADDING = 10
5
+
6
+ def initialize(context)
7
+ @context = context
8
+ end
9
+
10
+ def draw
11
+ text = Image.new(HEIGHT * 10, HEIGHT * 2) { self.background_color = COLOR_WHITE }
12
+
13
+ draw = Magick::Draw.new do |d|
14
+ d.font_family = FONT_FAMILY
15
+ d.font_weight = FONT_WEIGHT
16
+ d.pointsize = 24
17
+ d.gravity = WestGravity
18
+ d.fill = COLOR_BLACK
19
+ end
20
+
21
+ draw.annotate(text, 0, 0, 0, 0, @context)
22
+ text.trim!
23
+
24
+ canvas = Image.new(text.columns + PADDING * 2, HEIGHT) { self.background_color = COLOR_WHITE }
25
+ canvas.composite!(text, WestGravity, PADDING, 0, OverCompositeOp)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,13 @@
1
+ module Bagel
2
+ module Graphic
3
+ class Scoreboard::Games < Scoreboard::Score
4
+ def color_bg
5
+ COLOR_ORANGE
6
+ end
7
+
8
+ def color_text
9
+ COLOR_BLACK
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ module Bagel
2
+ module Graphic
3
+ class Scoreboard::Name
4
+ WIDTH = 160
5
+ HEIGHT = 50
6
+
7
+ PADDING = 10
8
+
9
+ def initialize(name)
10
+ @name = name
11
+ end
12
+
13
+ def draw
14
+ text = Image.new(WIDTH, HEIGHT) { self.background_color = COLOR_GREY }
15
+
16
+ draw = Magick::Draw.new do |d|
17
+ d.font_family = FONT_FAMILY
18
+ d.font_weight = FONT_WEIGHT
19
+ d.pointsize = FONT_SIZE
20
+ d.gravity = CenterGravity
21
+ d.fill = COLOR_WHITE
22
+ end
23
+
24
+ draw.annotate(text, 0, 0, 0, 0, @name.upcase)
25
+ text.trim!
26
+
27
+ canvas = Image.new(WIDTH, HEIGHT) { self.background_color = COLOR_GREY }
28
+ canvas.composite!(text, WestGravity, PADDING, 0, OverCompositeOp)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ module Bagel
2
+ module Graphic
3
+ class Scoreboard::Points < Scoreboard::Score
4
+ def color_bg
5
+ COLOR_WHITE
6
+ end
7
+
8
+ def color_text
9
+ COLOR_BLACK
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,38 @@
1
+ module Bagel
2
+ module Graphic
3
+ class Scoreboard::Score
4
+ WIDTH = 50
5
+ HEIGHT = 50
6
+
7
+ def initialize(value)
8
+ @value = value
9
+ end
10
+
11
+ def draw
12
+ text = Image.new(HEIGHT * 2, HEIGHT * 2) { |i| i.background_color = color_bg }
13
+
14
+ draw = Magick::Draw.new do |d|
15
+ d.font_family = FONT_FAMILY
16
+ d.font_weight = FONT_WEIGHT
17
+ d.pointsize = FONT_SIZE
18
+ d.gravity = CenterGravity
19
+ d.fill = color_text
20
+ end
21
+
22
+ draw.annotate(text, 0, 0, 0, 0, @value.to_s)
23
+ text.trim!
24
+
25
+ canvas = Image.new(HEIGHT, HEIGHT) { |i| i.background_color = color_bg }
26
+ canvas.composite!(text, CenterGravity, 0, 0, OverCompositeOp)
27
+ end
28
+
29
+ def color_bg
30
+ raise 'abstract'
31
+ end
32
+
33
+ def color_text
34
+ raise 'abstract'
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,16 @@
1
+ module Bagel
2
+ module Graphic
3
+ class Scoreboard::Server
4
+ WIDTH = 10
5
+ HEIGHT = 50
6
+
7
+ def initialize(is_server)
8
+ @is_server = is_server
9
+ end
10
+
11
+ def draw
12
+ Image.new(WIDTH, HEIGHT) { |i| i.background_color = @is_server ? COLOR_ORANGE : COLOR_GREY }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ module Bagel
2
+ module Graphic
3
+ class Scoreboard::Sets < Scoreboard::Score
4
+ def color_bg
5
+ COLOR_BLUE
6
+ end
7
+
8
+ def color_text
9
+ COLOR_WHITE
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,102 @@
1
+ module Bagel::Graphic
2
+ class Scoreboard
3
+ MARGIN = 3
4
+ PADDING = 10
5
+
6
+ FILENAME_SUFFIX = '-score'
7
+
8
+ private attr_reader :score, :names
9
+
10
+ def initialize(score, names)
11
+ @score, @names = score, names
12
+ end
13
+
14
+ def save
15
+ offset_y = Scoreboard::Context::HEIGHT + MARGIN
16
+
17
+ width = Scoreboard::Server::WIDTH + Scoreboard::Name::WIDTH + (Scoreboard::Score::HEIGHT * 3) + (MARGIN * 3)
18
+ height = (Scoreboard::Server::HEIGHT * 2) + Scoreboard::Context::HEIGHT + (MARGIN * 2)
19
+
20
+ canvas = Image.new(width, height) { |i| i.background_color = COLOR_TRANSPARENT }
21
+
22
+ margin_x = 0
23
+
24
+ if score.context
25
+ canvas.composite!(img_context, NorthWestGravity, 0, 0, OverCompositeOp)
26
+ end
27
+
28
+ canvas.composite!(img_server_a, NorthWestGravity, margin_x, offset_y, OverCompositeOp)
29
+ canvas.composite!(img_server_b, SouthWestGravity, margin_x, 0, OverCompositeOp)
30
+
31
+ margin_x += Scoreboard::Server::WIDTH
32
+
33
+ canvas.composite!(img_name_a, NorthWestGravity, margin_x, offset_y, OverCompositeOp)
34
+ canvas.composite!(img_name_b, SouthWestGravity, margin_x, 0, OverCompositeOp)
35
+
36
+ margin_x += Scoreboard::Name::WIDTH + MARGIN
37
+
38
+ score.sets.each do |set|
39
+ canvas.composite!(img_games_a(set[0]), NorthWestGravity, margin_x, offset_y, OverCompositeOp)
40
+ canvas.composite!(img_games_b(set[1]), SouthWestGravity, margin_x, 0, OverCompositeOp)
41
+ margin_x += Scoreboard::Score::HEIGHT + MARGIN
42
+ end
43
+
44
+ if score.points
45
+ canvas.composite!(img_points_a, NorthWestGravity, margin_x, offset_y, OverCompositeOp)
46
+ canvas.composite!(img_points_b, SouthWestGravity, margin_x, 0, OverCompositeOp)
47
+ end
48
+
49
+ canvas.write(path)
50
+
51
+ path
52
+ end
53
+
54
+ private
55
+
56
+ def img_context
57
+ @img_context ||= Scoreboard::Context.new(score.context).draw
58
+ end
59
+
60
+ def img_server_a
61
+ @img_server_a ||= Scoreboard::Server.new(score.server?(Player.ONE)).draw
62
+ end
63
+
64
+ def img_server_b
65
+ @img_server_b ||= Scoreboard::Server.new(score.server?(Player.TWO)).draw
66
+ end
67
+
68
+ def img_name_a
69
+ @img_name_a ||= Scoreboard::Name.new(names[0]).draw
70
+ end
71
+
72
+ def img_name_b
73
+ @img_name_b ||= Scoreboard::Name.new(names[1]).draw
74
+ end
75
+
76
+ def img_games_a(games)
77
+ Scoreboard::Games.new(games).draw
78
+ end
79
+
80
+ def img_games_b(games)
81
+ Scoreboard::Games.new(games).draw
82
+ end
83
+
84
+ def img_points_a
85
+ points = Scoring.point_call(score.points[0], score.points[1], score.tiebreak?)
86
+ Scoreboard::Points.new(points).draw
87
+ end
88
+
89
+ def img_points_b
90
+ points = Scoring.point_call(score.points[1], score.points[0], score.tiebreak?)
91
+ Scoreboard::Points.new(points).draw
92
+ end
93
+
94
+ def path
95
+ File.join(Bagel.config.graphic_dir, filename)
96
+ end
97
+
98
+ def filename
99
+ "#{score.id}#{FILENAME_SUFFIX}#{FILENAME_EXTENSION}"
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,33 @@
1
+ module Bagel
2
+ module Graphic
3
+ class Stats::Duration
4
+ WIDTH = Stats::WIDTH
5
+ HEIGHT = 90
6
+
7
+ COLOR_BG = COLOR_GREY
8
+ COLOR_TEXT = COLOR_WHITE
9
+
10
+ def initialize(minutes)
11
+ @minutes = minutes
12
+ end
13
+
14
+ def draw
15
+ canvas = Image.new(WIDTH, HEIGHT) { |i| i.background_color = COLOR_BG }
16
+
17
+ text = Image.new(WIDTH, HEIGHT) { |i| i.background_color = COLOR_BG }
18
+
19
+ draw = Magick::Draw.new do |d|
20
+ d.font_family = FONT_FAMILY
21
+ d.pointsize = 32
22
+ d.gravity = Magick::CenterGravity
23
+ d.fill = COLOR_TEXT
24
+ end
25
+
26
+ draw.annotate(text, 0, 0, 0, 0, "SET TIME: #{@minutes} MINUTES")
27
+ text.trim!
28
+
29
+ canvas.composite!(text, CenterGravity, 0, 0, AtopCompositeOp)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ module Bagel
2
+ module Graphic
3
+ class Stats::Header
4
+ WIDTH = Stats::WIDTH
5
+ HEIGHT = 90
6
+
7
+ COLOR_BG = COLOR_GREY
8
+ COLOR_TEXT = COLOR_WHITE
9
+
10
+ def initialize(set_number)
11
+ @set_number = set_number
12
+ end
13
+
14
+ def draw
15
+ text = Image.new(WIDTH, HEIGHT) { |i| i.background_color = COLOR_BG }
16
+
17
+ draw = Magick::Draw.new do |d|
18
+ d.font_family = FONT_FAMILY
19
+ d.pointsize = 36
20
+ d.gravity = Magick::CenterGravity
21
+ d.fill = COLOR_TEXT
22
+ end
23
+
24
+ draw.annotate(text, 0, 0, 0, 0, "SET #{@set_number} SUMMARY")
25
+ text.trim!
26
+
27
+ canvas = Image.new(WIDTH, HEIGHT) { |i| i.background_color = COLOR_BG }
28
+ canvas.composite!(text, CenterGravity, 0, 0, OverCompositeOp)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ module Bagel
2
+ module Graphic
3
+ class Stats::Result
4
+ WIDTH = Stats::WIDTH
5
+ HEIGHT = 70
6
+
7
+ COLOR_BG = COLOR_GREY
8
+ COLOR_TEXT = COLOR_WHITE
9
+
10
+ def initialize(names, games)
11
+ @names = names.map(&:upcase)
12
+ @games = games
13
+
14
+ @d = Magick::Draw.new do |d|
15
+ d.font_family = FONT_FAMILY
16
+ d.pointsize = 36
17
+ d.gravity = Magick::CenterGravity
18
+ d.fill = COLOR_TEXT
19
+ end
20
+ end
21
+
22
+ def draw
23
+ canvas = Image.new(WIDTH, HEIGHT) { |i| i.background_color = COLOR_BG }
24
+ canvas.composite!(cell(@names.first), WestGravity, 0, 0, OverCompositeOp)
25
+ canvas.composite!(cell(@games.join(' - '), 72), CenterGravity, 0, 0, OverCompositeOp)
26
+ canvas.composite!(cell(@names.last), EastGravity, 0, 0, OverCompositeOp)
27
+ end
28
+
29
+ private
30
+
31
+ def cell(value, font_size=36)
32
+ cell = Image.new(WIDTH/3, HEIGHT) { |i| i.background_color = COLOR_BG }
33
+ text = Image.new(WIDTH, HEIGHT) { |i| i.background_color = COLOR_BG }
34
+ @d.pointsize = font_size
35
+ @d.annotate(text, 0, 0, 0, 0, value)
36
+ text.trim!
37
+ cell.composite!(text, CenterGravity, 0, 0, OverCompositeOp)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,48 @@
1
+ module Bagel::Graphic
2
+ class Stats::Stat
3
+ WIDTH = Stats::WIDTH
4
+ HEIGHT = 65
5
+
6
+ COLOR_TEXT = COLOR_WHITE
7
+ COLOR_TEXT_HIGHLIGHT = COLOR_ORANGE
8
+
9
+ def initialize(index, stat)
10
+ @index = index
11
+ @stat = stat
12
+
13
+ @color_bg = @index.even? ? COLOR_GREY_LIGHT : COLOR_GREY_DARK
14
+
15
+ @canvas = Image.new(WIDTH, HEIGHT) { |i| i.background_color = @color_bg }
16
+ @d = Magick::Draw.new do |d|
17
+ d.font_family = FONT_FAMILY
18
+ d.pointsize = 36
19
+ d.gravity = Magick::CenterGravity
20
+ d.fill = COLOR_TEXT
21
+ end
22
+ end
23
+
24
+ def draw
25
+ left = cell(value: @stat.values[0], highlight: @stat.superior[0], is_small: false)
26
+ center = cell(value: @stat.name, is_small: true)
27
+ right = cell(value: @stat.values[1], highlight: @stat.superior[1], is_small: false)
28
+
29
+ @canvas.composite!(left, WestGravity, 0, 0, OverCompositeOp)
30
+ @canvas.composite!(center, CenterGravity, 0, 0, OverCompositeOp)
31
+ @canvas.composite!(right, EastGravity, 0, 0, OverCompositeOp)
32
+ end
33
+
34
+ private
35
+
36
+ def cell(args)
37
+ cell = Image.new(WIDTH / 3, HEIGHT) { |i| i.background_color = @color_bg }
38
+ text = Image.new(WIDTH, HEIGHT) { |i| i.background_color = @color_bg }
39
+
40
+ @d.fill = args[:highlight] ? COLOR_TEXT_HIGHLIGHT : COLOR_TEXT
41
+ @d.pointsize = 32 if args[:is_small]
42
+
43
+ @d.annotate(text, 0, 0, 0, 0, args[:value].to_s)
44
+ text.trim!
45
+ cell.composite!(text, CenterGravity, 0, 0, OverCompositeOp)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,80 @@
1
+ module Bagel::Graphic
2
+ class Stats
3
+ WIDTH = 1440
4
+ HEIGHT = 900
5
+
6
+ COLOR_BG = COLOR_GREY
7
+
8
+ FILENAME_SUFFIX = '-stats'
9
+
10
+ private attr_reader :names, :score, :points
11
+
12
+ def initialize(names:, score:, points:)
13
+ @names = names
14
+ @score = score
15
+ @points = points
16
+
17
+ @canvas = Image.new(WIDTH, HEIGHT) { |i| i.background_color = COLOR_BG }
18
+ @offset_y = 0
19
+ end
20
+
21
+ def save
22
+ add_header
23
+ add_result
24
+ add_duration
25
+
26
+ add_stat(0, Bagel::Tennis::Stats::Aces.new(points))
27
+ add_stat(1, Bagel::Tennis::Stats::DoubleFaults.new(points))
28
+ add_stat(0, Bagel::Tennis::Stats::FirstServeIn.new(points))
29
+ add_stat(1, Bagel::Tennis::Stats::FirstServePointsWon.new(points))
30
+ add_stat(0, Bagel::Tennis::Stats::SecondServePointsWon.new(points))
31
+ add_stat(1, Bagel::Tennis::Stats::Winners.new(points))
32
+ add_stat(0, Bagel::Tennis::Stats::UnforcedErrors.new(points))
33
+ add_stat(1, Bagel::Tennis::Stats::NetPointsWon.new(points))
34
+ add_stat(0, Bagel::Tennis::Stats::BreakPointsWon.new(points))
35
+ add_stat(1, Bagel::Tennis::Stats::PointsWon.new(points))
36
+
37
+ @canvas.write(path)
38
+ path
39
+ end
40
+
41
+ private
42
+
43
+ def add_header
44
+ graphic = Bagel::Graphic::Stats::Header.new(score.set_number).draw
45
+ @canvas.composite!(graphic, NorthGravity, 0, @offset_y, OverCompositeOp)
46
+ @offset_y += Stats::Header::HEIGHT
47
+ end
48
+
49
+ def add_result
50
+ graphic = Bagel::Graphic::Stats::Result.new(names, score.set_score).draw
51
+ @canvas.composite!(graphic, NorthGravity, 0, @offset_y, OverCompositeOp)
52
+ @offset_y += Stats::Result::HEIGHT
53
+ end
54
+
55
+ def add_duration
56
+ graphic = Bagel::Graphic::Stats::Duration.new(duration).draw
57
+ @canvas.composite!(graphic, NorthGravity, 0, @offset_y, OverCompositeOp)
58
+ @offset_y += Stats::Duration::HEIGHT
59
+ end
60
+
61
+ def add_stat(index, stat)
62
+ row = Bagel::Graphic::Stats::Stat.new(index, stat).draw
63
+ @canvas.composite!(row, NorthGravity, 0, @offset_y, OverCompositeOp)
64
+ @offset_y += Stats::Stat::HEIGHT
65
+ end
66
+
67
+ def duration
68
+ start, finish = points[0].start, points[-1].finish
69
+ Timeframe.new(start, finish).duration_minutes
70
+ end
71
+
72
+ def filename
73
+ "#{score.set_number.to_s.rjust(2, '0')}#{FILENAME_SUFFIX}#{FILENAME_EXTENSION}"
74
+ end
75
+
76
+ def path
77
+ File.join(Bagel.config.graphic_dir, filename)
78
+ end
79
+ end
80
+ end