Created
December 20, 2025 00:40
-
-
Save wtnabe/5195f89bcc6ece65863b20430900f4e4 to your computer and use it in GitHub Desktop.
Marpで作ったスライドの発表時間を粗々と計算する
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require "cgi" | |
| class MarpSlideCounter | |
| TOKENS_PER_SEC = 5.0 # chars | |
| INTERVAL = 4.0 # sec | |
| class Slide < Data.define(:type, :content); end | |
| class Stat < Data.define(:content_slides, :avg_length, :avg_cost, :total_cost); end | |
| SLIDE_TYPE = [:content, :ghost] | |
| # | |
| # @param [Numeric] | |
| # @param [Numeric] | |
| # | |
| def initialize(tokens_per_sec: TOKENS_PER_SEC, interval: INTERVAL) | |
| @tokens_per_sec = tokens_per_sec | |
| @interval = interval | |
| end | |
| # | |
| # @return [Array(Array<Slide>, Stat)] | |
| # | |
| def call | |
| slides = classify(cutinto_slides(File.read(src_file))) | |
| [slides, calculate(slides)] | |
| end | |
| # | |
| # @param [Array<Slide>] | |
| # @return [Stat] | |
| # | |
| def calculate(slides) | |
| contents = slides.select { |c| c.type == :content } | |
| content_slides = contents.size | |
| avg_length = contents.inject(0) { |sum, c| sum + c.content.size } / contents.size.to_f | |
| avg_cost = avg_length / @tokens_per_sec | |
| Stat.new( | |
| content_slides: content_slides, | |
| avg_length: avg_length, | |
| avg_cost: avg_cost, | |
| total_cost: (avg_cost + @interval) * content_slides | |
| ) | |
| end | |
| # | |
| # @return [String] | |
| # | |
| def src_file | |
| ARGV.shift | |
| end | |
| # | |
| # marp形式のmarkdownを一つ一つのスライドに分割する | |
| # | |
| # 空行しかないスライド、marpメタデータのスライドは除外する | |
| # | |
| # @param [String] src | |
| # @return [Array<String>] | |
| # | |
| def cutinto_slides(src) | |
| src.split(/^---+[\s]*$/).map(&:lstrip).map(&:rstrip).select { |s| | |
| s =~ /[^\s]+/ && s !~ /^marp: true/ | |
| } | |
| end | |
| # | |
| # 有意なコンテンツとタイトルや感嘆、接続のみのスライドを分類する | |
| # | |
| # 有意なコンテンツとはホワイトスペース以外の文字の間に改行が含まれていること | |
| # 例えば見出しのみのスライドは除外される | |
| # 見出しが複数ある場合は有意なコンテンツにカウントされる | |
| # | |
| # @param [Array<String>] slides | |
| # @return [Array<Slide>] | |
| # | |
| def classify(slides) | |
| slides.map { |s| | |
| if /[^\s]+\n+[^\s]+/.match?(s) | |
| Slide.new(type: :content, content: s) | |
| else | |
| Slide.new(type: :ghost, content: s) | |
| end | |
| } | |
| end | |
| end | |
| # | |
| # @param [Array<MarpSlideCounter::Slide>] slides | |
| # @param [MarpSlideCounter::Stat] stat | |
| # | |
| def display(slides, stat) | |
| puts slides[0..3].map { |e| e.content.gsub(/\n/, "\\n") } | |
| puts "#{stat.content_slides} slides" | |
| printf("avg. %4.3f chars %3.2f seconds\n", stat.avg_length, stat.avg_cost) | |
| printf("Total: %3.3f min\n", stat.total_cost / 60.0) | |
| end | |
| if $0 == __FILE__ | |
| display(*MarpSlideCounter.new.call) | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment