diff --git a/Gemfile b/Gemfile index 47aadd3..a564e48 100644 --- a/Gemfile +++ b/Gemfile @@ -91,12 +91,12 @@ gem "omniauth-discord" gem "omniauth-twitch" gem "dotenv-rails", groups: [:development, :test] -gem "ruby-openai", "~> 4.2" +gem "ruby-openai", "~> 7.1" gem "qdrant-ruby", "~> 0.9.2" # gem "pgvector", "~> 0.2" # gem "plain-rails", path: "/Users/michelson/Documents/rubyonrails/plain" -gem "plain-rails", github: "chaskiq/plain", branch: "documents" # path: "/Users/michelson/Documents/rubyonrails/plain" +# gem "plain-rails", github: "chaskiq/plain", branch: "documents" # path: "/Users/michelson/Documents/rubyonrails/plain" # gem "plain-rails", "0.1.2" #, path: "/Users/michelson/Documents/rubyonrails/plain" # sentry diff --git a/Gemfile.lock b/Gemfile.lock index b01e4c1..fcef402 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,19 +5,6 @@ GIT specs: ruby-oembed (0.16.1) -GIT - remote: https://github.com/chaskiq/plain.git - revision: c775ad400d207f0a8d972bbae4da1360919fa9a5 - branch: documents - specs: - plain-rails (0.1.2) - front_matter_parser (~> 1.0.1) - langchainrb (~> 0.6.12) - qdrant-ruby (~> 0.9.2) - rails (>= 7.0.6) - redcarpet (~> 2.3.0) - ruby-openai (~> 4.2) - GIT remote: https://github.com/hotwired/turbo-rails.git revision: e376852bfb273f69f4ebb54cf516b99fcbaa7acb @@ -228,7 +215,6 @@ GEM rack-session (>= 1, < 3) aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) - baran (0.1.7) base64 (0.2.0) bcrypt (3.1.19) bcrypt_pbkdf (1.1.0) @@ -249,7 +235,6 @@ GEM xpath (~> 3.2) chunky_png (1.4.0) coderay (1.1.3) - colorize (0.8.1) concurrent-ruby (1.2.3) connection_pool (2.4.1) crass (1.0.6) @@ -284,6 +269,7 @@ GEM drb (2.2.1) ed25519 (1.3.0) erubi (1.12.0) + event_stream_parser (1.0.0) factory_bot (6.2.1) activesupport (>= 5.0.0) factory_bot_rails (6.2.0) @@ -291,19 +277,18 @@ GEM railties (>= 5.0.0) faker (3.2.0) i18n (>= 1.8.11, < 2) - faraday (2.7.10) - faraday-net_http (>= 2.0, < 3.1) - ruby2_keywords (>= 0.0.4) + faraday (2.9.2) + faraday-net_http (>= 2.0, < 3.2) faraday-multipart (1.0.4) multipart-post (~> 2) - faraday-net_http (3.0.2) + faraday-net_http (3.1.0) + net-http ffi (1.15.5) ffi-compiler (1.0.1) ffi (>= 1.0.0) rake friendly_id (5.5.0) activerecord (>= 4.0.0) - front_matter_parser (1.0.1) geocoder (1.8.2) globalid (1.2.1) activesupport (>= 6.1) @@ -333,8 +318,6 @@ GEM jsbundling-rails (1.1.2) railties (>= 6.0.0) json (2.6.3) - json-schema (4.0.0) - addressable (>= 2.8) jwt (2.7.1) kaminari (1.2.2) activesupport (>= 4.1.0) @@ -352,12 +335,6 @@ GEM activemodel (>= 6.0.0) activesupport (>= 6.0.0) redis (>= 4.2, < 6) - langchainrb (0.6.12) - baran (~> 0.1.6) - colorize (~> 0.8.1) - json-schema (~> 4.0.0) - tiktoken_ruby (~> 0.0.5) - zeitwerk (= 2.6.11) language_server-protocol (3.17.0.3) lint_roller (1.1.0) llhttp-ffi (0.4.0) @@ -391,8 +368,10 @@ GEM zeitwerk (~> 2.5) msgpack (1.7.2) multi_xml (0.6.0) - multipart-post (2.3.0) + multipart-post (2.4.1) mutex_m (0.2.0) + net-http (0.4.1) + uri net-imap (0.4.11) date net-protocol @@ -509,7 +488,6 @@ GEM zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.2.1) - redcarpet (2.3.0) redis (5.0.6) redis-client (>= 0.9.0) redis-client (0.22.1) @@ -540,13 +518,13 @@ GEM rubocop-performance (1.18.0) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) - ruby-openai (4.3.2) + ruby-openai (7.1.0) + event_stream_parser (>= 0.3.0, < 2.0.0) faraday (>= 1) faraday-multipart (>= 1) ruby-progressbar (1.13.0) ruby-vips (2.1.4) ffi (~> 1.12) - ruby2_keywords (0.0.5) rubyzip (2.3.2) selenium-webdriver (4.10.0) rexml (~> 3.2, >= 3.2.5) @@ -609,9 +587,6 @@ GEM activerecord (>= 6.0) stripe (8.6.0) thor (1.3.1) - tiktoken_ruby (0.0.5-arm64-darwin) - tiktoken_ruby (0.0.5-x86_64-darwin) - tiktoken_ruby (0.0.5-x86_64-linux) timeout (0.4.1) transbank-sdk (3.0.2) json (~> 2.0) @@ -621,6 +596,7 @@ GEM unf_ext unf_ext (0.0.8.2) unicode-display_width (2.4.2) + uri (0.13.0) version_gem (1.1.3) warden (1.2.9) rack (>= 2.0.9) @@ -686,7 +662,6 @@ DEPENDENCIES omniauth-twitch omniauth-twitter pg (~> 1.1) - plain-rails! pry puma qdrant-ruby (~> 0.9.2) @@ -700,7 +675,7 @@ DEPENDENCIES rspec-rails! rspec-support! ruby-oembed! - ruby-openai (~> 4.2) + ruby-openai (~> 7.1) rubyzip (~> 2.3) selenium-webdriver sentry-rails diff --git a/app/controllers/podcasts_controller.rb b/app/controllers/podcasts_controller.rb index aea8e7f..0a56b3d 100644 --- a/app/controllers/podcasts_controller.rb +++ b/app/controllers/podcasts_controller.rb @@ -1,2 +1,34 @@ class PodcastsController < ApplicationController + + + def show + @user = User.find_by(username: params[:user_id]) + end + + def edit + @user = User.find_by(username: params[:user_id]) + @info = @user.podcaster_info || @user.build_podcaster_info + end + + def update + @user = User.find_by(username: params[:user_id]) + @info = @user.podcaster_info || @user.build_podcaster_info + @info.update(podcaster_params) + redirect_to user_podcast_path(@user.username) + end + + + def create + @user = User.find_by(username: params[:user_id]) + @info = @user.podcaster_info || @user.build_podcaster_info + @info.update(podcaster_params) + redirect_to user_podcast_path(@user.username) + end + + + private + + def podcaster_params + params.require(:podcaster_info).permit(:about, :title, :description) + end end diff --git a/app/models/track.rb b/app/models/track.rb index c173cb1..c69e561 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -335,4 +335,11 @@ def self.get_tracks_by_tag(tag) tag = tag.downcase includes(:user).where("? = ANY (tags)", tag) end + + def podcast_summarizer + file_path = ActiveStorage::Blob.service.path_for(mp3_audio.key) + summarizer = AudioSummarizer.new(file_path) + transcription = summarizer.summarize + self.update(description: transcription) + end end diff --git a/app/services/audio_summarizer.rb b/app/services/audio_summarizer.rb new file mode 100644 index 0000000..21fe443 --- /dev/null +++ b/app/services/audio_summarizer.rb @@ -0,0 +1,88 @@ +# app/services/audio_summarizer.rb +require 'openai' +require 'securerandom' +require 'tmpdir' + +class AudioSummarizer + MAX_FILE_SIZE_MB = 20 + MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024 + + def initialize(file_path) + @file_path = file_path + @client = OpenAI::Client.new(access_token: ENV["OPENAI_API_KEY"], log_errors: true) + end + + def summarize + chunk_paths = split_audio_by_silence + transcriptions = chunk_paths.map { |chunk_path| + Rails.logger.info("CHUNK PATH #{chunk_path}") + transcribe_chunk(chunk_path) + } + text = transcriptions.join("\n") + sumarize_transcription(text) + ensure + cleanup_temp_files(chunk_paths) + end + + def sumarize_transcription(text) + response = @client.chat( + parameters: { + model: "gpt-4o", + messages: [ + { role: "assistant", content: "summarize the podcast transcription for the Rauversion platform"}, + { role: "user", content: text} + ], + temperature: 0.7, + }) + response.dig("choices", 0, "message", "content") + end + + private + + def split_audio_by_silence + output_dir = Dir.mktmpdir + chunk_pattern = File.join(output_dir, 'chunk_%03d.wav') + + # Split the file by silence detection + `ffmpeg -i "#{@file_path}" -af silencedetect=noise=-30dB:d=0.5 -f segment -segment_time 30 -c:a pcm_s16le "#{chunk_pattern}"` + + chunk_paths = Dir.glob("#{output_dir}/chunk_*.wav") + valid_chunks = [] + + chunk_paths.each do |chunk_path| + if File.size(chunk_path) > MAX_FILE_SIZE_BYTES + # Re-split large chunks further + valid_chunks += split_large_chunk(chunk_path, output_dir) + File.delete(chunk_path) # Delete the original large chunk + else + valid_chunks << chunk_path + end + end + + valid_chunks + end + + def split_large_chunk(chunk_path, output_dir) + temp_pattern = File.join(output_dir, 'temp_chunk_%03d.wav') + + `ffmpeg -i "#{chunk_path}" -f segment -segment_time 15 -c:a pcm_s16le "#{temp_pattern}"` + + Dir.glob("#{output_dir}/temp_chunk_*.wav").select do |path| + File.size(path) <= MAX_FILE_SIZE_BYTES + end + end + + def transcribe_chunk(chunk_path) + response = @client.audio.transcribe( + parameters: { + file: File.open(chunk_path), + model: "whisper-1" + } + ) + response['text'] + end + + def cleanup_temp_files(files) + files.each { |file| File.delete(file) if File.exist?(file) } + end +end diff --git a/app/services/dalle.rb b/app/services/dalle.rb index dea4e8d..72f6543 100644 --- a/app/services/dalle.rb +++ b/app/services/dalle.rb @@ -5,45 +5,16 @@ class Dalle BASE_URL = "https://api.openai.com/v1/" def initialize(token = nil) - token ||= ENV["OPENAI_API_KEY"] - - @conn = Faraday.new(url: BASE_URL) do |conn| - conn.request :json - conn.response :json - conn.authorization :Bearer, token - conn.adapter Faraday.default_adapter - end + @client = OpenAI::Client.new(access_token: token || ENV["OPENAI_API_KEY"], log_errors: true) end - # prompt: A text description of the desired image(s). - # Options should include: - # - :n, an integer to denote the number of images to generate. - # - :size, the size of the generated images. - # - :response_format, the format in which the generated images are returned. - # - :user, a unique identifier representing your end-user - def images(prompt, options = {}) - options[:prompt] = prompt - response = @conn.post do |req| - req.url "images/generations" - req.headers["Content-Type"] = "application/json" - req.body = options.to_json - end - - handle_response(response) - end - - private - - def handle_response(response) - if response.success? - body = response.body - if body["data"] && body["data"].first["url"] - {ok: body["data"].first["url"]} - else - {error: nil} - end - else - {error: response.body["error"]} - end + def generate(prompt: nil) + response = @client.images.generate(parameters: { + prompt: prompt, + model: "dall-e-3", + size: "1024x1024", + quality: "hd" + }) + puts response.dig("data", 0, "url") end end diff --git a/app/views/podcasts/_footer.html.erb b/app/views/podcasts/_footer.html.erb new file mode 100644 index 0000000..fe0a7f5 --- /dev/null +++ b/app/views/podcasts/_footer.html.erb @@ -0,0 +1,22 @@ +<> \ No newline at end of file diff --git a/app/views/podcasts/_player.erb b/app/views/podcasts/_player.erb new file mode 100644 index 0000000..45f08e4 --- /dev/null +++ b/app/views/podcasts/_player.erb @@ -0,0 +1,96 @@ +
+
+ +
+ 5: Bill Lumbergh +
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+ +
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/app/views/podcasts/_podcast.erb b/app/views/podcasts/_podcast.erb new file mode 100644 index 0000000..ac1bc6e --- /dev/null +++ b/app/views/podcasts/_podcast.erb @@ -0,0 +1,32 @@ +
+
+
+
+
+

+ <%= link_to track.title, "#" %> +

+ + +

+ <%= truncate track.description, length: 400 %> +

+ +
+ + + Show notes +
+ +
+
+
+
+
\ No newline at end of file diff --git a/app/views/podcasts/edit.html.erb b/app/views/podcasts/edit.html.erb new file mode 100644 index 0000000..2256aee --- /dev/null +++ b/app/views/podcasts/edit.html.erb @@ -0,0 +1,13 @@ +
+ + <%= form_for @info, url: user_podcast_path(@user.username) do |f| %> + + <%= f.text_field :title %> + <%= f.text_area :about %> + <%= f.text_area :description %> + + <%= f.submit %> + + <% end %> + +
\ No newline at end of file diff --git a/app/views/podcasts/show.html.erb b/app/views/podcasts/show.html.erb new file mode 100644 index 0000000..4c80e11 --- /dev/null +++ b/app/views/podcasts/show.html.erb @@ -0,0 +1,229 @@ +
+
+ +
+ + <%= image_tag @user.avatar_url(:medium), class: "w-full", style: "color:transparent" %> + + +
+
+
+

+ <%= @user.podcaster_info.title %> +

+

+ <%= @user.podcaster_info.description %> +

+
+ +
+

+ + Listen +

+
+ +
+
+
+ +
+ + + +
+
+
+
+
+

Episodes

+
+
+
+
+ <% @user.tracks.each do |track| %> + <%= render "podcast", track: track %> + <% end %> +
+
+
+
+ + <%= render "footer" %> + + <%= render "player" %> + +
diff --git a/config/initializers/plain.rb b/config/initializers/plain.rb index ae35bc7..94dd3e8 100644 --- a/config/initializers/plain.rb +++ b/config/initializers/plain.rb @@ -1,3 +1,5 @@ +=begin + Plain.configure do |config| config.paths = [ Rails.root.join("app/models"), @@ -29,3 +31,6 @@ ) ) end + + +=end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 15f6b5c..0044d56 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -155,6 +155,8 @@ resources :follows, controller: "user_follows", only: [ :index, :create, :destroy ] + + resource :podcast, controller: "podcasts" get "followers", to: "user_follows#followers" get "followees", to: "user_follows#followees" get "/tracks", to: "users#tracks" @@ -169,5 +171,5 @@ end end - mount Plain::Engine => "/plain" + # mount Plain::Engine => "/plain" end