<commit_before>class Transcript < ActiveRecord::Base
  attr_accessible :language, :audio_file_id, :identifier, :start_time, :end_time, :confidence, :transcriber_id, :cost_per_min, :cost_type, :retail_cost_per_min, :is_billable, :subscription_plan_id

  belongs_to :audio_file
  belongs_to :transcriber
  belongs_to :subscription_plan
  has_one :item, through: :audio_file
  has_many :timed_texts, -> { order 'start_time ASC' }
  has_many :speakers

  default_scope -> { includes(:timed_texts) }

  after_save :update_item

  RETAIL    = 2 
  WHOLESALE = 1 
  COST_TYPES = {RETAIL => "Retail", WHOLESALE => "Wholesale"}

  def plan
    if subscription_plan
      subscription_plan.as_cached
    else
      audio_file_lazarus.user.plan
    end
  end

  def billed_as
    COST_TYPES[self.cost_type]
  end

  def update_item
    IndexerWorker.perform_async(:update, item.class.to_s, item.id) if item
  end

  def timed_texts
    super.each do |tt|
      tt.transcript = self
    end
    super
  end

  def set_confidence
    sum = 0.0
    count = 0.0
    self.timed_texts.each{|tt| sum = sum + tt.confidence.to_f; count = count + 1.0}
    if count > 0.0
      average = sum / count
      self.update_attribute(:confidence, average)
    end
    average
  end

  def as_json(options={})
    { sections: timed_texts }
  end

  # def to_doc(format=:srt)
  #   action_view = ActionView::Base.new(Rails.configuration.paths["app/views"])
  #   action_view.class_eval do 
  #     include Rails.application.routes.url_helpers
  #     include Api::BaseHelper
  #     def protect_against_forgery?; false; end
  #   end

  #   action_view.render(template: 'api/v1/transcripts/show', formats: [format], locals: {transcript: self})
  # end

  def to_srt
    srt = ""
    timed_texts.each_with_index do |tt, index|

      end_time = tt.end_time
      end_mils = '000'

      if (index + 1) < timed_texts.size
        end_time_max = [(timed_texts[index + 1].start_time - 1), 0].max
        end_time = [tt.end_time, end_time_max].min
        end_mils = '999'
      end

      if (index > 0)
        srt += "\r\n" 
      end

      srt += "#{index + 1}\n"
      srt += "#{format_time(tt.start_time)},000 --> #{format_time(end_time)},#{end_mils}\n"
      srt += tt.text + "\n"
    end
    srt
  end

  def speaker_name(speaker_id)
    # memoize lookup by id
    if !@_speakers_by_id
      @_speakers_by_id = {}
      speakers.each do |sp|
        @_speakers_by_id[sp.id] = sp
      end
    end
    @_speakers_by_id[speaker_id].name
  end

  def has_speaker_ids
    self.timed_texts.where("speaker_id is not null").count > 0 ? true : false
  end

  # return array of timed texts, grouped by speaker change
  # each item in the array is a HashWithIndifferentAccess object,
  # with keys: start, speaker, text, offsets
  def chunked_by_speaker
    chunks = []
    cur_chunk = nil
    prev_speaker = nil
    timed_texts.each do |tt|
      speaker_name = self.speaker_name(tt.speaker_id)
      if !cur_chunk or !prev_speaker or speaker_name != prev_speaker
        prev_speaker = speaker_name
        if cur_chunk
          chunks.push cur_chunk
        end
        cur_chunk = HashWithIndifferentAccess.new
        cur_chunk[:speaker] = speaker_name
        cur_chunk[:start]   = tt.start_time
        cur_chunk[:ts]      = tt.offset_as_ts
        cur_chunk[:text]    = [tt.text]
        cur_chunk[:offsets] = [tt.start_time]
        next
      end
      cur_chunk[:text].push tt.text
      cur_chunk[:offsets].push tt.start_time
      prev_speaker = speaker_name
    end
    if cur_chunk and cur_chunk[:text].size > 0
      chunks.push cur_chunk  # last one
    end
    chunks
  end

  # returns array of timed texts, grouped by 'chunk_size' seconds.
  # default is '30' seconds per chunk.
  # format of response is same as chunked_by_speaker() except for 'speaker'
  def chunked_by_time(chunk_size=30)
    chunks = []
    cur_chunk = nil
    prev_start = nil
    timed_texts.each do |tt|
      start_time = tt.start_time.to_i
      if !cur_chunk or !prev_start or (start_time - prev_start) > chunk_size
        puts "prev_start=#{prev_start.inspect}  start_time=#{start_time.inspect}  chunk_size=#{chunk_size.inspect}"
        prev_start = start_time
        if cur_chunk
          chunks.push cur_chunk
        end 
        cur_chunk = HashWithIndifferentAccess.new
        cur_chunk[:start]   = start_time
        cur_chunk[:ts]      = tt.offset_as_ts
        cur_chunk[:text]    = [tt.text]
        cur_chunk[:offsets] = [tt.start_time]
        next
      end 
      cur_chunk[:text].push tt.text
      cur_chunk[:offsets].push tt.start_time
    end 
    if cur_chunk and cur_chunk[:text].size > 0 
      chunks.push cur_chunk  # last one
    end 
    chunks<commit_msg>disable debugging statement<commit_after>class Transcript < ActiveRecord::Base
  attr_accessible :language, :audio_file_id, :identifier, :start_time, :end_time, :confidence, :transcriber_id, :cost_per_min, :cost_type, :retail_cost_per_min, :is_billable, :subscription_plan_id

  belongs_to :audio_file
  belongs_to :transcriber
  belongs_to :subscription_plan
  has_one :item, through: :audio_file
  has_many :timed_texts, -> { order 'start_time ASC' }
  has_many :speakers

  default_scope -> { includes(:timed_texts) }

  after_save :update_item

  RETAIL    = 2 
  WHOLESALE = 1 
  COST_TYPES = {RETAIL => "Retail", WHOLESALE => "Wholesale"}

  def plan
    if subscription_plan
      subscription_plan.as_cached
    else
      audio_file_lazarus.user.plan
    end
  end

  def billed_as
    COST_TYPES[self.cost_type]
  end

  def update_item
    IndexerWorker.perform_async(:update, item.class.to_s, item.id) if item
  end

  def timed_texts
    super.each do |tt|
      tt.transcript = self
    end
    super
  end

  def set_confidence
    sum = 0.0
    count = 0.0
    self.timed_texts.each{|tt| sum = sum + tt.confidence.to_f; count = count + 1.0}
    if count > 0.0
      average = sum / count
      self.update_attribute(:confidence, average)
    end
    average
  end

  def as_json(options={})
    { sections: timed_texts }
  end

  # def to_doc(format=:srt)
  #   action_view = ActionView::Base.new(Rails.configuration.paths["app/views"])
  #   action_view.class_eval do 
  #     include Rails.application.routes.url_helpers
  #     include Api::BaseHelper
  #     def protect_against_forgery?; false; end
  #   end

  #   action_view.render(template: 'api/v1/transcripts/show', formats: [format], locals: {transcript: self})
  # end

  def to_srt
    srt = ""
    timed_texts.each_with_index do |tt, index|

      end_time = tt.end_time
      end_mils = '000'

      if (index + 1) < timed_texts.size
        end_time_max = [(timed_texts[index + 1].start_time - 1), 0].max
        end_time = [tt.end_time, end_time_max].min
        end_mils = '999'
      end

      if (index > 0)
        srt += "\r\n" 
      end

      srt += "#{index + 1}\n"
      srt += "#{format_time(tt.start_time)},000 --> #{format_time(end_time)},#{end_mils}\n"
      srt += tt.text + "\n"
    end
    srt
  end

  def speaker_name(speaker_id)
    # memoize lookup by id
    if !@_speakers_by_id
      @_speakers_by_id = {}
      speakers.each do |sp|
        @_speakers_by_id[sp.id] = sp
      end
    end
    @_speakers_by_id[speaker_id].name
  end

  def has_speaker_ids
    self.timed_texts.where("speaker_id is not null").count > 0 ? true : false
  end

  # return array of timed texts, grouped by speaker change
  # each item in the array is a HashWithIndifferentAccess object,
  # with keys: start, speaker, text, offsets
  def chunked_by_speaker
    chunks = []
    cur_chunk = nil
    prev_speaker = nil
    timed_texts.each do |tt|
      speaker_name = self.speaker_name(tt.speaker_id)
      if !cur_chunk or !prev_speaker or speaker_name != prev_speaker
        prev_speaker = speaker_name
        if cur_chunk
          chunks.push cur_chunk
        end
        cur_chunk = HashWithIndifferentAccess.new
        cur_chunk[:speaker] = speaker_name
        cur_chunk[:start]   = tt.start_time
        cur_chunk[:ts]      = tt.offset_as_ts
        cur_chunk[:text]    = [tt.text]
        cur_chunk[:offsets] = [tt.start_time]
        next
      end
      cur_chunk[:text].push tt.text
      cur_chunk[:offsets].push tt.start_time
      prev_speaker = speaker_name
    end
    if cur_chunk and cur_chunk[:text].size > 0
      chunks.push cur_chunk  # last one
    end
    chunks
  end

  # returns array of timed texts, grouped by 'chunk_size' seconds.
  # default is '30' seconds per chunk.
  # format of response is same as chunked_by_speaker() except for 'speaker'
  def chunked_by_time(chunk_size=30)
    chunks = []
    cur_chunk = nil
    prev_start = nil
    timed_texts.each do |tt|
      start_time = tt.start_time.to_i
      if !cur_chunk or !prev_start or (start_time - prev_start) > chunk_size
        #puts "prev_start=#{prev_start.inspect}  start_time=#{start_time.inspect}  chunk_size=#{chunk_size.inspect}"
        prev_start = start_time
        if cur_chunk
          chunks.push cur_chunk
        end 
        cur_chunk = HashWithIndifferentAccess.new
        cur_chunk[:start]   = start_time
        cur_chunk[:ts]      = tt.offset_as_ts
        cur_chunk[:text]    = [tt.text]
        cur_chunk[:offsets] = [tt.start_time]
        next
      end 
      cur_chunk[:text].push tt.text
      cur_chunk[:offsets].push tt.start_time
    end 
    if cur_chunk and cur_chunk[:text].size > 0 
      chunks.push cur_chunk  # last one
    end 
    chunks
{'fraction_non_alphanumeric': 0.09767637472400378, 'fraction_numerical': 0.0069393334034276105, 'mean_word_length': 2.6853932584269664, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4704834', 'n_tokens_mistral': 3444, 'n_tokens_neox': 3337, 'n_words': 737}