podcast-buddy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 55e233781e7481f2c6de2040e8ca2960b4f08738c8e02cdd0a6e498dd8bcf716
4
+ data.tar.gz: bdfdc5291f075099ec16e9eaace611c48187de9d31932fe067feb39f88ac596f
5
+ SHA512:
6
+ metadata.gz: 75beed9ca8c328e426ca85b8d5f894a0497e4a663c072a4d5d640dde623392e4796b381616a8f03978a687835b455fabe238ed87fed0f3c5a1e5d6c1ba37e8b4
7
+ data.tar.gz: 5a727fb531e7b5c21285bd2c29957181a52daac060ce70e2d547e1b734743941dbb1d0608a80b3fe5f06b800267b9aac3d273897726b7b13393d1146167f9215
data/.buddylint.yml ADDED
@@ -0,0 +1,3 @@
1
+ Style/StringLiterals:
2
+ Enabled: true
3
+ EnforcedStyle: double_quotes
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.standard.yml ADDED
@@ -0,0 +1,10 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/standardrb/standard
3
+ ruby_version: 3.0
4
+ #fix: true # default: false
5
+ parallel: true # default: false
6
+ format: progress # default: Standard::Formatter
7
+ extend_config:
8
+ - .buddylint.yml
9
+ ignore:
10
+ - 'whisper.cpp/**/*'
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-08-09
4
+
5
+ - Initial release
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Valentino Stoll
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # Podcast Buddy
2
+
3
+ This is a simple Ruby command-line tool that allows dropping in an AI buddy to
4
+ your podcast.
5
+
6
+ ## Installation
7
+
8
+ Install the gem and add to the application's Gemfile by executing:
9
+
10
+ $ bundle add podcast-buddy
11
+
12
+ If bundler is not being used to manage dependencies, install the gem by executing:
13
+
14
+ $ gem install podcast-buddy
15
+
16
+ ## Usage
17
+
18
+ Run your buddy from the command-line:
19
+
20
+ ```bash
21
+ podcast-buddy
22
+ ```
23
+
24
+ This will install a couple dependencies, if they don't exist:
25
+
26
+ 1. `git` (for cloning whisper.cpp locally)
27
+ 2. `sdl2` - Simple DirectMedia Layer; for cross-platform audio input access
28
+ 3. [whipser.cpp with streaming](https://github.com/ggerganov/whisper.cpp/tree/master/examples/stream) – For transcribing audio in near-real-time
29
+
30
+ Other requirements:
31
+
32
+ 1. OpenAI token stored in your environment as `OPENAI_ACCESS_TOKEN`
33
+ 2. MacOS
34
+
35
+ ### Asking your buddy questions during recordings
36
+
37
+ At any time, you can press the `return` key and ask your buddy a question.
38
+ Once you see the question show up in the output, press `return` key again and
39
+ your Buddy will answer using the system output (via `afplay`).
40
+
41
+ ### Completing your pod
42
+
43
+ Once you're done, simply `ctrl-c` to wrap things up.
44
+
45
+ ### Helpful files logged during your session with Buddy
46
+
47
+ 1. Your full transcript is stored in `tmp/transcript.log`
48
+ 2. A summarization of the discussion and topics are stored in `tmp/discussion.log`
49
+ 3. The raw whisper logs are stored in `tmp/whisper.log`
50
+
51
+ ## Development
52
+
53
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
54
+
55
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
56
+
57
+ ## Contributing
58
+
59
+ Bug reports and pull requests are welcome on GitHub at https://github.com/codenamev/podcast-buddy. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/codenamev/podcast-buddy/blob/main/CODE_OF_CONDUCT.md).
60
+
61
+ ## License
62
+
63
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
64
+
65
+ ## Code of Conduct
66
+
67
+ Everyone interacting in the PodcastBuddy project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/codenamev/podcast-buddy/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[spec standard]
data/exe/podcast_buddy ADDED
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/podcast_buddy"
4
+ require "optparse"
5
+
6
+ options = {}
7
+ OptionParser.new do |opts|
8
+ opts.banner = "Usage: podcast-buddy [options]"
9
+
10
+ opts.on("--debug", "Run in debug mode") do |v|
11
+ options[:debug] = v
12
+ end
13
+ end.parse!
14
+
15
+ PodcastBuddy.logger.formatter = proc do |severity, datetime, progname, msg|
16
+ if severity.to_s == "INFO"
17
+ "#{msg}\n"
18
+ else
19
+ "[#{severity}] #{msg}\n"
20
+ end
21
+ end
22
+
23
+ if options[:debug]
24
+ PodcastBuddy.logger.info "Turning on debug mode..."
25
+ PodcastBuddy.logger.level = Logger::DEBUG
26
+ else
27
+ PodcastBuddy.logger.level = Logger::INFO
28
+ end
29
+
30
+ PodcastBuddy.logger.info "Setting up dependencies..."
31
+ PodcastBuddy.setup
32
+ PodcastBuddy.logger.info "Setup complete."
33
+
34
+ # OpenAI API client setup
35
+ raise "Please set an OPENAI_ACCESS_TOKEN environment variable." if ENV["OPENAI_ACCESS_TOKEN"].to_s.strip.squeeze.empty?
36
+
37
+ client = OpenAI::Client.new(access_token: ENV["OPENAI_ACCESS_TOKEN"], log_errors: true)
38
+
39
+ # Signals for question detection
40
+ question_signal = PodcastBuddy::PodSignal.new
41
+ end_signal = PodcastBuddy::PodSignal.new
42
+
43
+ # Initialize variables
44
+ @latest_transcription = ""
45
+ @question_transcription = ""
46
+ @full_transcription = ""
47
+ @listening_for_question = false
48
+ @shutdown_flag = false
49
+ @threads = []
50
+ @transcription_queue = Queue.new
51
+
52
+ # Method to extract topics and summarize
53
+ def extract_topics_and_summarize(client, text)
54
+ current_discussion = File.exist?(PodcastBuddy.discussion_log_file) ? File.read(PodcastBuddy.discussion_log_file) : ""
55
+ current_discussion.rstrip.squeeze!
56
+ messages = []
57
+ if current_discussion.length > 1
58
+ messages << {role: "user", content: "Given the prior summary and topics:\n---\n#{current_discussion}\n---"}
59
+ messages << {role: "user", content: "Continue the running summary and list of topcs using the following discussion:\n---#{text}\n---"}
60
+ else
61
+ messages << {role: "user", content: "Extract topics and summarize the following discussion:\n---#{text}\n---"}
62
+ end
63
+ response = client.chat(parameters: {
64
+ model: "gpt-4o",
65
+ messages: messages,
66
+ max_tokens: 150
67
+ })
68
+ response.dig("choices", 0, "message", "content").strip
69
+ end
70
+
71
+ def answer_question(client, question)
72
+ latest_context = `tail -n 25 #{PodcastBuddy.discussion_log_file}`
73
+ previous_discussion = @full_transcription.split("\n").last(25)
74
+ PodcastBuddy.logger.info "Answering question:\n#{question}"
75
+ PodcastBuddy.logger.debug "Context:\n---#{latest_context}\n---\nPrevious discussion:\n---#{previous_discussion}\n---\nAnswering question:\n---#{question}\n---"
76
+ response = client.chat(parameters: {
77
+ model: "gpt-4o",
78
+ messages: [
79
+ {role: "system", content: "You are a kind and helpful podcast assistant helping to answer questions as them come up during a recording. Keep the answer succinct and without speculation."},
80
+ {role: "user", content: "Context:\n#{latest_context}\n\nPrevious Discussion:\n#{previous_discussion}\n\nQuestion:\n#{question}\n\nAnswer:"}
81
+ ],
82
+ max_tokens: 150
83
+ })
84
+ response.dig("choices", 0, "message", "content").strip
85
+ end
86
+
87
+ # Method to convert text to speech
88
+ def text_to_speech(client, text)
89
+ response = client.audio.speech(parameters: {
90
+ model: "tts-1",
91
+ input: text,
92
+ voice: "onyx",
93
+ response_format: "mp3",
94
+ speed: 1.0
95
+ })
96
+ File.binwrite("response.mp3", response)
97
+ end
98
+
99
+ # Method to handle audio stream processing
100
+ def process_audio_stream(client)
101
+ Thread.new do
102
+ whisper_command = "./whisper.cpp/stream -m ./whisper.cpp/models/ggml-#{PodcastBuddy.whisper_model}.bin -t 4 --step 0 --length 5000 --keep 500 --vad-thold 0.60 --audio-ctx 0 --keep-context -c 1"
103
+ PodcastBuddy.logger.info "Buffering audio..."
104
+ Open3.popen3(whisper_command) do |stdin, stdout, stderr, thread|
105
+ stderr_thread = Thread.new do
106
+ stderr.each { |line| PodcastBuddy.whisper_logger.error line }
107
+ end
108
+ stdout_thread = Thread.new do
109
+ stdout.each { |line| PodcastBuddy.whisper_logger.debug line }
110
+ end
111
+
112
+ while (transcription = stdout.gets)
113
+ # Cleanup captured text from whisper
114
+ transcription_text = (transcription.scan(/\[[0-9]+.*[0-9]+\]\s?(.*)\n/) || [p])[0].to_s
115
+ PodcastBuddy.logger.debug "Received transcription: #{transcription_text}"
116
+ transcription_text = transcription_text.gsub("[BLANK_AUDIO]", "")
117
+ transcription_text = transcription_text.gsub(/\A\["\s?/, "")
118
+ transcription_text = transcription_text.gsub(/"\]\Z/, "")
119
+ break if @shutdown_flag
120
+
121
+ PodcastBuddy.logger.debug "Filtered: #{transcription_text}"
122
+ next if transcription_text.to_s.strip.squeeze.empty?
123
+
124
+ @full_transcription += transcription_text
125
+
126
+ if @listening_for_question
127
+ @question_transcription += transcription_text
128
+ PodcastBuddy.logger.info "Heard Question: #{transcription_text}"
129
+ else
130
+ @transcription_queue << transcription_text
131
+ PodcastBuddy.logger.info "Heard: #{transcription_text}"
132
+ PodcastBuddy.update_transcript(transcription_text)
133
+ end
134
+ end
135
+
136
+ # Close streams and join stderr thread on shutdown
137
+ stderr_thread.join
138
+ stdout_thread.join
139
+ stdin.close
140
+ stdout.close
141
+ stderr.close
142
+ end
143
+ end
144
+ end
145
+
146
+ # Method to generate show notes
147
+ def generate_show_notes(client, transcription)
148
+ summary = extract_topics_and_summarize(client, transcription)
149
+ File.open("show_notes.md", "w") do |file|
150
+ file.puts "# Show Notes"
151
+ file.puts summary
152
+ end
153
+ end
154
+
155
+ # Periodically summarize latest transcription
156
+ def periodic_summarization(client, interval = 15)
157
+ Thread.new do
158
+ loop do
159
+ break if @shutdown_flag
160
+
161
+ sleep interval
162
+ summarize_latest(client)
163
+ rescue => e
164
+ PodcastBuddy.logger.warn "[summarization] periodic summarization failed: #{e.message}"
165
+ end
166
+ end
167
+ end
168
+
169
+ def summarize_latest(client = OpenAI::Client.new(access_token: ENV["OPENAI_ACCESS_TOKEN"], log_errors: true))
170
+ latest_transcriptions = []
171
+ latest_transcriptions << @transcription_queue.pop until @transcription_queue.empty?
172
+ return if latest_transcriptions.join.strip.squeeze.empty?
173
+
174
+ PodcastBuddy.logger.debug "[periodic summarization] Latest transcript: #{latest_transcriptions.join}"
175
+ summary = extract_topics_and_summarize(client, latest_transcriptions.join)
176
+ File.write(PodcastBuddy.discussion_log_file, summary)
177
+ end
178
+
179
+ # Handle Ctrl-C to generate show notes
180
+ Signal.trap("INT") do
181
+ puts "\nShutting down streams..."
182
+ @shutdown_flag = true
183
+ # Wait for all threads to finish
184
+ @threads.compact.each(&:join)
185
+
186
+ # Will have to re-think this. Can't syncronize from a trap (Faraday)
187
+ # Also cannot log from trap
188
+ #puts "\nGenerating show notes..."
189
+ #generate_show_notes(client, @full_transcription)
190
+ exit
191
+ end
192
+
193
+ # Setup signal subscriptions
194
+ question_signal.subscribe do
195
+ PodcastBuddy.logger.debug "Question signal received"
196
+ @listening_for_question = true
197
+ @question_transcription.clear
198
+ summarize_latest
199
+ end
200
+
201
+ end_signal.subscribe do
202
+ PodcastBuddy.logger.info "End of question signal received"
203
+ @listening_for_question = false
204
+
205
+ PodcastBuddy.logger.info "Waiting for question to complete (enter return again)..."
206
+ loop do
207
+ PodcastBuddy.logger.debug "Checking for question..."
208
+ sleep 0.3
209
+ break if @question_transcription.to_s.length > 0 || @listening_for_question == true
210
+ end
211
+ # Guard against interruptions
212
+ if !@question_transcription.empty?
213
+ response_text = answer_question(client, @question_transcription)
214
+ text_to_speech(client, response_text)
215
+ system("afplay response.mp3")
216
+ end
217
+ end
218
+
219
+ # Start audio stream processing
220
+ @threads << process_audio_stream(client)
221
+ # Start periodic summarization
222
+ @threads << periodic_summarization(client, 60)
223
+
224
+ # Main loop to wait for question and end signals
225
+ loop do
226
+ PodcastBuddy.logger.info "Press Enter to signal a question start..."
227
+ gets
228
+ question_signal.trigger
229
+ PodcastBuddy.logger.info "Press Enter to signal the end of the question..."
230
+ gets
231
+ end_signal.trigger
232
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PodcastBuddy
4
+ class PodSignal
5
+ def initialize
6
+ @listeners = []
7
+ @queue = Queue.new
8
+ start_listener_thread
9
+ end
10
+
11
+ def subscribe(&block)
12
+ @listeners << block
13
+ end
14
+
15
+ def trigger(data = nil)
16
+ @queue << data
17
+ end
18
+
19
+ private
20
+
21
+ def start_listener_thread
22
+ Thread.new do
23
+ loop do
24
+ data = @queue.pop
25
+ @listeners.each { |listener| listener.call(data) }
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PodcastBuddy
4
+ class SystemDependency < Struct.new(:type, :name, keyword_init: true)
5
+ attr_accessor :name
6
+ attr_reader :type
7
+
8
+ def initialize(name:, type: :system)
9
+ @name = name
10
+ @type = type || :system
11
+ end
12
+
13
+ def self.auto_install!(name)
14
+ system_dependency = new(name: name, type: :system)
15
+ system_dependency.install unless system_dependency.installed?
16
+ end
17
+
18
+ def installed?
19
+ PodcastBuddy.logger.info "Checking for system dependency: #{name}..."
20
+ if name.to_s == "whisper"
21
+ Dir.exist?("whisper.cpp")
22
+ else
23
+ system("brew list #{name}") || system("type -a #{name}")
24
+ end
25
+ end
26
+
27
+ def install
28
+ PodcastBuddy.logger.info "Installing #{name}..."
29
+ originating_directory = Dir.pwd
30
+ if name.to_s == "whisper"
31
+ Dir.chdir(PodcastBuddy.root)
32
+ PodcastBuddy.logger.info `git clone https://github.com/ggerganov/whisper.cpp`
33
+ Dir.chdir("whisper.cpp")
34
+ PodcastBuddy.logger.info "Downloading GGML model: #{PodcastBuddy.whisper_model}"
35
+ PodcastBuddy.logger.info `bash ./models/download-ggml-model.sh #{PodcastBuddy.whisper_model}`
36
+ PodcastBuddy.logger.info `make && make stream`
37
+ else
38
+ `brew list #{name} || brew install #{name}`
39
+ end
40
+ ensure
41
+ Dir.chdir(originating_directory)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PodcastBuddy
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+ require "open3"
7
+ require "logger"
8
+
9
+ require "bundler/inline"
10
+
11
+ require_relative "podcast_buddy/version"
12
+ require_relative "podcast_buddy/system_dependency"
13
+ require_relative "podcast_buddy/pod_signal"
14
+
15
+ module PodcastBuddy
16
+ class Error < StandardError; end
17
+
18
+ def self.root
19
+ Dir.pwd
20
+ end
21
+
22
+ def self.logger
23
+ @logger ||= Logger.new($stdout, level: Logger::DEBUG)
24
+ end
25
+
26
+ def self.logger=(logger)
27
+ @logger = logger
28
+ end
29
+
30
+ def self.whisper_model
31
+ "small.en"
32
+ end
33
+
34
+ def self.whisper_logger
35
+ @whisper_logger ||= Logger.new("tmp/whisper.log", "daily")
36
+ end
37
+
38
+ def self.discussion_log_file
39
+ @discussion_log_file ||= "#{root}/tmp/discussion-#{Time.new.strftime("%Y-%m-%d")}.log"
40
+ end
41
+
42
+ def self.transcript_file
43
+ @transcript_file ||= "#{root}/tmp/transcript-#{Time.new.strftime("%Y-%m-%d")}.log"
44
+ end
45
+
46
+ def self.update_transcript(text)
47
+ File.open(transcript_file, "a") { |f| f.puts text }
48
+ end
49
+
50
+ def self.setup
51
+ gemfile do
52
+ source "https://rubygems.org"
53
+ gem "ruby-openai"
54
+ end
55
+
56
+ require "openai"
57
+ logger.info "Gems installed and loaded!"
58
+ SystemDependency.auto_install!(:git)
59
+ SystemDependency.auto_install!(:sdl2)
60
+ SystemDependency.auto_install!(:whisper)
61
+ end
62
+ end
data/podcast_buddy.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "json"
4
+ require "open3"
5
+ require "logger"
6
+
7
+ require "bundler/inline"
8
+ require_relative "podcast_buddy/system_dependency"
@@ -0,0 +1,4 @@
1
+ module PodcastBuddy
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
data/tmp/.gitkeep ADDED
File without changes
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: podcast-buddy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Valentino Stoll
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-08-09 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A simple Ruby command-line tool for dropping in an AI buddy to your podcast.
14
+ email:
15
+ - v@codenamev.com
16
+ executables:
17
+ - podcast_buddy
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ".buddylint.yml"
22
+ - ".rspec"
23
+ - ".standard.yml"
24
+ - CHANGELOG.md
25
+ - CODE_OF_CONDUCT.md
26
+ - LICENSE.txt
27
+ - README.md
28
+ - Rakefile
29
+ - exe/podcast_buddy
30
+ - lib/podcast_buddy.rb
31
+ - lib/podcast_buddy/pod_signal.rb
32
+ - lib/podcast_buddy/system_dependency.rb
33
+ - lib/podcast_buddy/version.rb
34
+ - podcast_buddy.rb
35
+ - sig/podcast_buddy.rbs
36
+ - tmp/.gitkeep
37
+ homepage: https://github.com/codenamev/podcast-buddy
38
+ licenses:
39
+ - MIT
40
+ metadata:
41
+ allowed_push_host: https://rubygems.org
42
+ homepage_uri: https://github.com/codenamev/podcast-buddy
43
+ source_code_uri: https://github.com/codenamev/podcast-buddy
44
+ changelog_uri: https://github.com/codenamev/podcast-buddy/tree/main/CHANGELOG.md
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 3.0.0
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.3.26
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: A simple Ruby command-line tool for dropping in an AI buddy to your podcast.
64
+ test_files: []