appydave-tools 0.7.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14a6bd2963b58f85f247282a6cf8c537495b9a18300b04c3a3794ef5df4ecaa1
4
- data.tar.gz: 5ec47924ab4268377a297e1f02f6fa6b4f62207179f21fe8ccdcd69d62cd4f8d
3
+ metadata.gz: 0ba996537dc1739ee3fb48dc8d7998d6162a5abf049a14532ca099061771f368
4
+ data.tar.gz: 725cfcaec333d10f74efcf249d4b06e1c671ba00f873ffdb86b8450db9b7ac0e
5
5
  SHA512:
6
- metadata.gz: 253546f33096bad9f9daaa5776fa9d98142a4e7cb43607bf2040ab648d49f83712c8f491b8730de4bd602bcd4080a9360cb9bdd082a1b249df4210d66083ebf6
7
- data.tar.gz: f1f638a4a299df238694c8b4e42c008e8663a08cb2b83931a953796ec6679fa83038bebe3f749b0351a7edd848220cb906c53ff076abbf953564ff0356347b29
6
+ metadata.gz: d63ebd803779eee9bb7715911492ced07dd48b373bd541a6eef0106737374025919611bc0a00de1ef003dec98a9b17417236bcf1784c50a9cc81f3cc21d5d660
7
+ data.tar.gz: b191193647bdc0196fbbc47db9dde26f6dfa0e65f1d1d709aff7befbf1aa1ac0d17438bab94643f24860a177060862d86060bea9bcdc0a2ea54c46fb7d26171c
data/.rubocop.yml CHANGED
@@ -43,8 +43,6 @@ Metrics/BlockLength:
43
43
  RSpec/ExampleLength:
44
44
  Max: 25
45
45
 
46
- Metrics/MethodLength:
47
- Max: 25
48
46
 
49
47
  Layout/LineLength:
50
48
  Max: 200
@@ -77,6 +75,8 @@ Naming/MethodParameterName:
77
75
  Style/EmptyMethod:
78
76
  Exclude:
79
77
  - "**/spec/**/*"
78
+ Style/EmptyFile:
79
+ Enabled: false
80
80
  Metrics/ParameterLists:
81
81
  Exclude:
82
82
  - "**/spec/**/*"
@@ -117,16 +117,27 @@ RSpec/DescribeClass:
117
117
  RSpec/PendingWithoutReason:
118
118
  Enabled: false
119
119
 
120
+ RSpec/MultipleMemoizedHelpers:
121
+ Enabled: false
122
+
120
123
  Metrics/AbcSize:
121
124
  Max: 25
122
125
  Exclude:
123
126
  - "bin/*"
127
+ - "**/spec/**/*"
128
+ - "lib/appydave/**/*.rb"
124
129
  Metrics/CyclomaticComplexity:
125
130
  Exclude:
126
- - "**/models/**/*"
127
- - "lib/appydave/tools/bank_reconciliation/clean/mapper.rb"
131
+ - "**/spec/**/*"
132
+ - "lib/appydave/**/*.rb"
128
133
  Metrics/PerceivedComplexity:
129
134
  Exclude:
130
- - "**/models/**/*"
131
- RSpec/MultipleMemoizedHelpers:
132
- Enabled: false
135
+ - "**/spec/**/*"
136
+ - "lib/appydave/**/*.rb"
137
+
138
+ Metrics/MethodLength:
139
+ Max: 25
140
+ Exclude:
141
+ - "**/spec/**/*"
142
+ - "bin/*.rb"
143
+ - "lib/appydave/**/*.rb"
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ # [0.8.0](https://github.com/klueless-io/appydave-tools/compare/v0.7.0...v0.8.0) (2024-06-06)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * update bank reconciliation components ([0f5dea1](https://github.com/klueless-io/appydave-tools/commit/0f5dea1f75baced1458619ead3fc5bd5bc69e0d5))
7
+
8
+
9
+ ### Features
10
+
11
+ * subtitle master for cleaning the SRT files created by whisper AI ([9f7bd37](https://github.com/klueless-io/appydave-tools/commit/9f7bd3795f4361e0615307874a88370ab49f97a7))
12
+ * subtitle master for cleaning the SRT files created by whisper AI ([0475a9e](https://github.com/klueless-io/appydave-tools/commit/0475a9ec07f2735e52ce54db6afa96e30e00c0e6))
13
+
14
+ # [0.7.0](https://github.com/klueless-io/appydave-tools/compare/v0.6.1...v0.7.0) (2024-05-29)
15
+
16
+
17
+ ### Features
18
+
19
+ * new tool for doing bank reconciliations with chart of account matching ([9b82605](https://github.com/klueless-io/appydave-tools/commit/9b8260571f6046470d5963354ee1c80e493a0f28))
20
+
1
21
  ## [0.6.1](https://github.com/klueless-io/appydave-tools/compare/v0.6.0...v0.6.1) (2024-05-26)
2
22
 
3
23
 
@@ -32,19 +32,42 @@ class BankReconciliationCLI
32
32
  private
33
33
 
34
34
  def clean_transactions(args)
35
- options = {}
35
+ options = { include: [] }
36
36
  OptionParser.new do |opts|
37
37
  opts.banner = 'Usage: bank_reconciliation.rb clean [options]'
38
- opts.on('-i', '--include PATTERN', 'GLOB pattern for source transaction files') { |v| options[:include] = v }
39
- opts.on('-f', '--transaction FOLDER', 'Transaction CSV folder where original banking CSV files are stored') { |v| options[:transaction_folder] = v }
40
- opts.on('-o', '--output FILE', 'Output CSV file name') { |v| options[:output] = v }
38
+
39
+ opts.on('-i', '--include PATTERN', 'GLOB pattern for source transaction files') do |v|
40
+ options[:include] << v
41
+ end
42
+
43
+ opts.on('-f', '--transaction FOLDER', 'Transaction CSV folder where original banking CSV files are stored') do |v|
44
+ options[:transaction_folder] = v
45
+ end
46
+
47
+ opts.on('-o', '--output FILE', 'Output CSV file name') do |v|
48
+ options[:output] = v
49
+ end
50
+
41
51
  opts.on_tail('-h', '--help', 'Show this message') do
42
52
  puts opts
43
53
  exit
44
54
  end
45
55
  end.parse!(args)
46
56
 
47
- # Implement cleaning and normalizing transactions
57
+ transaction_folder = options[:transaction_folder] || '/default/transaction/folder'
58
+ output_file = options[:output] || 'clean_transactions.csv'
59
+ include_patterns = options[:include].empty? ? ['*'] : options[:include]
60
+
61
+ puts "Cleaning transactions with options: #{options}"
62
+
63
+ # Ensure the clean directory exists
64
+ clean_dir = File.dirname(output_file)
65
+ FileUtils.mkdir_p(clean_dir)
66
+
67
+ # Initialize the CleanTransactions class and process the files
68
+ cleaner = Appydave::Tools::BankReconciliation::Clean::CleanTransactions.new(transaction_folder: transaction_folder)
69
+ cleaner.clean_transactions(include_patterns, output_file)
70
+
48
71
  puts "Cleaning transactions with options: #{options}"
49
72
  end
50
73
 
@@ -95,5 +118,7 @@ class BankReconciliationCLI
95
118
  end
96
119
  end
97
120
 
121
+ Appydave::Tools::Configuration::Config.configure
122
+
98
123
  BankReconciliationCLI.new.run
99
124
  # BankReconciliationCLI.new.run if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+
6
+ require 'appydave/tools'
7
+
8
+ # Process command line arguments for SubtitleMaster operations
9
+ class SubtitleMasterCLI
10
+ def initialize
11
+ @commands = {
12
+ 'clean' => method(:clean_subtitles),
13
+ 'correct' => method(:correct_subtitles),
14
+ 'split' => method(:split_subtitles),
15
+ 'highlight' => method(:highlight_subtitles),
16
+ 'image_prompts' => method(:generate_image_prompts)
17
+ }
18
+ end
19
+
20
+ def run
21
+ command, *args = ARGV
22
+ if @commands.key?(command)
23
+ @commands[command].call(args)
24
+ else
25
+ puts "Unknown command: #{command}"
26
+ print_help
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def clean_subtitles(args)
33
+ options = parse_options(args, 'clean')
34
+ cleaner = Appydave::Tools::SubtitleMaster::Clean.new(options[:file])
35
+ result = cleaner.clean
36
+ write_output(result, options[:output])
37
+ end
38
+
39
+ def correct_subtitles(args)
40
+ options = parse_options(args, 'correct')
41
+ corrector = Appydave::Tools::SubtitleMaster::Correct.new(options[:file])
42
+ result = corrector.correct
43
+ write_output(result, options[:output])
44
+ end
45
+
46
+ def split_subtitles(args)
47
+ options = parse_options(args, 'split', %i[words_per_group])
48
+ splitter = Appydave::Tools::SubtitleMaster::Split.new(options[:file], options[:words_per_group])
49
+ result = splitter.split
50
+ write_output(result, options[:output])
51
+ end
52
+
53
+ def highlight_subtitles(args)
54
+ options = parse_options(args, 'highlight')
55
+ highlighter = Appydave::Tools::SubtitleMaster::Highlight.new(options[:file])
56
+ result = highlighter.highlight
57
+ write_output(result, options[:output])
58
+ end
59
+
60
+ def generate_image_prompts(args)
61
+ options = parse_options(args, 'image_prompts')
62
+ image_prompter = Appydave::Tools::SubtitleMaster::ImagePrompts.new(options[:file])
63
+ result = image_prompter.generate_prompts
64
+ write_output(result, options[:output])
65
+ end
66
+
67
+ def parse_options(args, command, extra_options = [])
68
+ options = { file: nil, output: nil }
69
+ OptionParser.new do |opts|
70
+ opts.banner = "Usage: subtitle_master.rb #{command} [options]"
71
+
72
+ opts.on('-f', '--file FILE', 'SRT file to process') { |v| options[:file] = v }
73
+ opts.on('-o', '--output FILE', 'Output file') { |v| options[:output] = v }
74
+
75
+ extra_options.each do |opt|
76
+ case opt
77
+ when :words_per_group
78
+ opts.on('-w', '--words-per-group WORDS', 'Number of words per group for splitting') { |v| options[:words_per_group] = v.to_i }
79
+ end
80
+ end
81
+
82
+ opts.on_tail('-h', '--help', 'Show this message') do
83
+ puts opts
84
+ exit
85
+ end
86
+ end.parse!(args)
87
+
88
+ unless options[:file] && options[:output]
89
+ puts 'Missing required options. Use -h for help.'
90
+ exit
91
+ end
92
+
93
+ options
94
+ end
95
+
96
+ def write_output(result, output_file)
97
+ File.write(output_file, result)
98
+ puts "Processed file written to #{output_file}"
99
+ end
100
+
101
+ def print_help
102
+ puts 'Usage: subtitle_master.rb [command] [options]'
103
+ puts 'Commands:'
104
+ puts ' clean Clean and normalize SRT files'
105
+ puts ' correct Correct common typos and mistranslations in SRT files'
106
+ puts ' split Split subtitle groups based on word count'
107
+ puts ' highlight Highlight power words in subtitles'
108
+ puts ' image_prompts Generate image prompts from subtitle text'
109
+ puts "Run 'subtitle_master.rb [command] --help' for more information on a command."
110
+ end
111
+ end
112
+
113
+ SubtitleMasterCLI.new.run
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+
6
+ require 'appydave/tools'
7
+
8
+ # Process command line arguments for YouTubeVideoManager operations
9
+ class YouTubeVideoManagerCLI
10
+ def initialize
11
+ @commands = {
12
+ 'get' => method(:fetch_video_details)
13
+ # Additional commands can be added here
14
+ }
15
+ end
16
+
17
+ def run
18
+ command, *args = ARGV
19
+ if @commands.key?(command)
20
+ @commands[command].call(args)
21
+ else
22
+ puts "Unknown command: #{command}"
23
+ print_help
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def fetch_video_details(args)
30
+ options = parse_options(args, 'get')
31
+ manager = Appydave::Tools::YouTubeManager::GetVideo.new
32
+ manager.get(options[:video_id])
33
+ # json = JSON.pretty_generate(details)
34
+ # puts json
35
+
36
+ # report = Appydave::Tools::YouTubeManager::Reports::VideoDetailsReport.new
37
+ # report.print(manager.data)
38
+
39
+ report = Appydave::Tools::YouTubeManager::Reports::VideoContentReport.new
40
+ report.print(manager.data)
41
+ end
42
+
43
+ def parse_options(args, command)
44
+ options = { video_id: nil }
45
+ OptionParser.new do |opts|
46
+ opts.banner = "Usage: youtube_video_manager.rb #{command} [options]"
47
+
48
+ opts.on('-v', '--video-id ID', 'YouTube Video ID') { |v| options[:video_id] = v }
49
+
50
+ opts.on_tail('-h', '--help', 'Show this message') do
51
+ puts opts
52
+ exit
53
+ end
54
+ end.parse!(args)
55
+
56
+ unless options[:video_id]
57
+ puts 'Missing required options. Use -h for help.'
58
+ exit
59
+ end
60
+
61
+ options
62
+ end
63
+
64
+ def print_help
65
+ puts 'Usage: youtube_video_manager.rb [command] [options]'
66
+ puts 'Commands:'
67
+ puts ' get Get details for a YouTube video'
68
+ # Additional commands can be listed here
69
+ puts "Run 'youtube_video_manager.rb [command] --help' for more information on a command."
70
+ end
71
+ end
72
+
73
+ YouTubeVideoManagerCLI.new.run
@@ -10,24 +10,31 @@ module Appydave
10
10
  include KLog::Logging
11
11
 
12
12
  attr_reader :transaction_folder
13
+ attr_reader :output_folder
13
14
  attr_reader :transactions
14
15
 
15
16
  # (config_file)
16
- def initialize(transaction_folder: '/Volumes/Expansion/Sync/bank-reconciliation/original-transactions')
17
+ def initialize(transaction_folder: nil, output_folder: nil)
18
+ # needs to use config.bank_reconciliation.transaction_folder
19
+ transaction_folder ||= '/Volumes/Expansion/Sync/bank-reconciliation/original-transactions'
20
+ output_folder ||= File.join(transaction_folder, 'clean')
21
+
17
22
  @transaction_folder = transaction_folder
23
+ @output_folder = output_folder
18
24
  end
19
25
 
20
- def clean_transactions(input_globs, _output_file)
26
+ def clean_transactions(input_globs, output_file)
21
27
  raw_transactions = grab_raw_transactions(input_globs)
22
28
  transactions, duplicates_count = deduplicate(raw_transactions)
23
29
 
24
30
  transactions = Mapper.new.map(transactions)
25
- # tp transactions
31
+
32
+ # tp transactions, Appydave::Tools::BankReconciliation::Models::Transaction.csv_headers
26
33
 
27
34
  log.kv 'Deduped consolidated transactions', duplicates_count if duplicates_count.positive?
28
35
 
29
- # transactions = normalize(transactions)
30
- # save_to_csv(transactions, output_file)
36
+ save_to_csv(transactions, output_file)
37
+
31
38
  @transactions = transactions
32
39
  end
33
40
 
@@ -42,6 +49,7 @@ module Appydave
42
49
 
43
50
  input_globs.each do |glob|
44
51
  Dir.glob(glob).each do |file|
52
+ log.kv 'Reading transactions from', file
45
53
  raw_transactions = ReadTransactions.new(file).read
46
54
  deduped_transactions, duplicates_count = deduplicate(raw_transactions)
47
55
 
@@ -81,8 +89,11 @@ module Appydave
81
89
  end
82
90
 
83
91
  def save_to_csv(transactions, output_file)
92
+ FileUtils.mkdir_p(output_folder)
93
+ output_file = File.join(output_folder, output_file)
94
+
84
95
  CSV.open(output_file, 'w') do |csv|
85
- csv << ReconciledTransaction.csv_headers
96
+ csv << Appydave::Tools::BankReconciliation::Models::Transaction.csv_headers
86
97
  transactions.each do |transaction|
87
98
  csv << transaction.to_csv_row
88
99
  end
@@ -40,7 +40,9 @@ module Appydave
40
40
  trigram_match(transaction, 0.7, '70%') ||
41
41
  trigram_match(transaction, 0.6, '60%') ||
42
42
  trigram_match(transaction, 0.5, '50%') ||
43
- transaction
43
+ start_with_match(transaction) ||
44
+ includes(transaction)
45
+ transaction
44
46
  end
45
47
 
46
48
  def map_bank_account(transaction)
@@ -56,7 +58,7 @@ module Appydave
56
58
 
57
59
  def equality_match(transaction)
58
60
  coa = config.bank_reconciliation.chart_of_accounts.find do |chart_of_account|
59
- chart_of_account.narration.to_s.delete(' ') == transaction.narration.delete(' ')
61
+ chart_of_account.narration.to_s.delete(' ').downcase == transaction.narration.delete(' ').downcase
60
62
  end
61
63
 
62
64
  return nil unless coa
@@ -66,6 +68,30 @@ module Appydave
66
68
  transaction
67
69
  end
68
70
 
71
+ def start_with_match(transaction)
72
+ coa = config.bank_reconciliation.chart_of_accounts.find do |chart_of_account|
73
+ transaction.narration.to_s.delete(' ').downcase.start_with?(chart_of_account.narration.to_s.downcase)
74
+ end
75
+
76
+ return nil unless coa
77
+
78
+ transaction.coa_match_type = 'starts_with'
79
+ transaction.coa_code = coa.code
80
+ transaction
81
+ end
82
+
83
+ def includes(transaction)
84
+ coa = config.bank_reconciliation.chart_of_accounts.find do |chart_of_account|
85
+ transaction.narration.to_s.delete(' ').downcase.include?(chart_of_account.narration.delete(' ').to_s.downcase)
86
+ end
87
+
88
+ return nil unless coa
89
+
90
+ transaction.coa_match_type = 'includes'
91
+ transaction.coa_code = coa.code
92
+ transaction
93
+ end
94
+
69
95
  def trigram_match(transaction, score_threshold, match_type)
70
96
  scored_transactions = config.bank_reconciliation.chart_of_accounts.map do |coa|
71
97
  {
@@ -21,6 +21,8 @@ module Appydave
21
21
  case platform
22
22
  when :bankwest
23
23
  read_bankwest(csv_lines)
24
+ when :bankwest2
25
+ read_bankwest2(csv_lines)
24
26
  end
25
27
  end
26
28
 
@@ -48,11 +50,35 @@ module Appydave
48
50
  @transactions
49
51
  end
50
52
 
53
+ def read_bankwest2(csv_lines)
54
+ @transactions = []
55
+
56
+ # Skip the header line and parse each subsequent line
57
+ CSV.parse(csv_lines.join, headers: true).each do |row|
58
+ transaction = Models::Transaction.new(
59
+ bsb_number: row['BSB / Account Number'].split(' - ').first,
60
+ account_number: row['BSB / Account Number'].split(' - ').last,
61
+ transaction_date: row['Transaction Date'],
62
+ narration: row['Narration'],
63
+ cheque_number: row['Cheque Number'],
64
+ debit: row['Debit'],
65
+ credit: row['Credit'],
66
+ balance: row['Balance'],
67
+ transaction_type: row['Transaction Type']
68
+ )
69
+ @transactions << transaction
70
+ end
71
+
72
+ @transactions
73
+ end
74
+
51
75
  # For bankwest the first row is the CSV will look like:
52
76
  # BSB Number,Account Number,Transaction Date,Narration,Cheque Number,Debit,Credit,Balance,Transaction Type
53
77
  def detect_platform(csv_lines)
54
78
  return :bankwest if csv_lines.first.start_with?('BSB Number,Account Number,Transaction Date,Narration,Cheque Number,Debit,Credit,Balance,Transaction Type')
79
+ return :bankwest2 if csv_lines.first.start_with?('Account Name,BSB / Account Number,Transaction Date,Narration,Cheque Number,Debit,Credit,Balance,Transaction Type')
55
80
 
81
+ puts "Unknown platform detected. CSV columns are: #{csv_lines.first.strip}"
56
82
  raise Appydave::Tools::Error, 'Unknown platform'
57
83
  end
58
84
  end
@@ -48,6 +48,42 @@ module Appydave
48
48
  @coa_match_type = coa_match_type
49
49
  @account_name = account_name
50
50
  end
51
+
52
+ def self.csv_headers
53
+ %i[
54
+ bsb_number
55
+ account_number
56
+ transaction_date
57
+ narration
58
+ cheque_number
59
+ debit
60
+ credit
61
+ balance
62
+ transaction_type
63
+ platform
64
+ coa_code
65
+ coa_match_type
66
+ account_name
67
+ ]
68
+ end
69
+
70
+ def to_csv_row
71
+ [
72
+ @bsb_number,
73
+ @account_number,
74
+ @transaction_date,
75
+ @narration,
76
+ @cheque_number,
77
+ @debit,
78
+ @credit,
79
+ @balance,
80
+ @transaction_type,
81
+ @platform,
82
+ @coa_code,
83
+ @coa_match_type,
84
+ @account_name
85
+ ]
86
+ end
51
87
  end
52
88
  end
53
89
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ # Hash with indifferent access
6
+ class IndifferentAccessHash < Hash
7
+ def initialize(initial_hash = {})
8
+ super()
9
+ update(initial_hash)
10
+ end
11
+
12
+ def [](key)
13
+ super(convert_key(key))
14
+ end
15
+
16
+ def []=(key, value)
17
+ super(convert_key(key), value)
18
+ end
19
+
20
+ def fetch(key, *args)
21
+ super(convert_key(key), *args)
22
+ end
23
+
24
+ def delete(key)
25
+ super(convert_key(key))
26
+ end
27
+
28
+ private
29
+
30
+ def convert_key(key)
31
+ key.is_a?(Symbol) ? key.to_s : key
32
+ end
33
+
34
+ def update(initial_hash)
35
+ initial_hash.each do |key, value|
36
+ self[convert_key(key)] = value
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ # Subtitle Master
2
+
3
+ [ChatGPT](https://chatgpt.com/c/f80dfca5-8168-4561-b5c6-8efed8672a88)
4
+
5
+ ## SubtitleMaster - Clean Component
6
+
7
+ The `SubtitleMaster::Clean` component is designed to process subtitle (SRT) files to improve their readability and compatibility with various platforms like YouTube. The main functionalities of this component include:
8
+
9
+ - **Removing HTML Tags**: Strips out unnecessary HTML underline tags (`<u>` and `</u>`) that may be present in the subtitle content.
10
+ - **Normalizing Subtitle Lines**: Merges fragmented subtitle lines into coherent sentences. It adjusts the timestamps to ensure that each subtitle entry spans the correct duration, combining lines that were incorrectly split by the subtitle creation software.
11
+ - **Handling Timestamps**: Updates the end timestamp of each merged subtitle to reflect the actual end time of the last occurrence of the text, ensuring accurate timing for each subtitle entry.
12
+
13
+ This component reads the SRT file, processes the content to remove tags and normalize lines, and outputs a cleaned and formatted subtitle file that is easier to read and upload to platforms.
14
+
15
+ ```bash
16
+ ./bin/subtitle_master.rb clean -f path/to/example.srt -o path/to/example_cleaned.srt
17
+
18
+ # Example using alias
19
+
20
+ ad_subtitle_master clean -f transcript/a45-banned-from-midjourney-16-alternatives.srt -o a45-transcript.srt
21
+ ```
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module SubtitleMaster
6
+ # Clean and normalize subtitles
7
+ class Clean
8
+ def initialize(file_path)
9
+ @file_path = file_path
10
+ end
11
+
12
+ def clean
13
+ content = File.read(@file_path, encoding: 'UTF-8')
14
+ content = remove_underscores(content)
15
+ normalize_lines(content)
16
+ end
17
+
18
+ private
19
+
20
+ def remove_underscores(content)
21
+ content.gsub(%r{</?u>}, '')
22
+ end
23
+
24
+ def normalize_lines(content)
25
+ lines = content.split("\n")
26
+ grouped_subtitles = []
27
+ current_subtitle = { text: '', start_time: nil, end_time: nil }
28
+
29
+ lines.each do |line|
30
+ if line =~ /^\d+$/ || line.strip.empty?
31
+ next
32
+ elsif line =~ /^\d{2}:\d{2}:\d{2},\d{3} --> \d{2}:\d{2}:\d{2},\d{3}$/
33
+ if current_subtitle[:start_time]
34
+ grouped_subtitles << current_subtitle.clone
35
+ current_subtitle = { text: '', start_time: nil, end_time: nil }
36
+ end
37
+
38
+ times = line.split(' --> ')
39
+ current_subtitle[:start_time] = times[0]
40
+ current_subtitle[:end_time] = times[1]
41
+ else
42
+ current_subtitle[:text] += ' ' unless current_subtitle[:text].empty?
43
+ current_subtitle[:text] += line.strip
44
+ end
45
+ end
46
+
47
+ grouped_subtitles << current_subtitle unless current_subtitle[:text].empty?
48
+
49
+ grouped_subtitles = merge_subtitles(grouped_subtitles)
50
+
51
+ build_normalized_content(grouped_subtitles)
52
+ end
53
+
54
+ def merge_subtitles(subtitles)
55
+ merged_subtitles = []
56
+ subtitles.each do |subtitle|
57
+ if merged_subtitles.empty? || merged_subtitles.last[:text] != subtitle[:text]
58
+ merged_subtitles << subtitle
59
+ else
60
+ merged_subtitles.last[:end_time] = subtitle[:end_time]
61
+ end
62
+ end
63
+ merged_subtitles
64
+ end
65
+
66
+ def build_normalized_content(grouped_subtitles)
67
+ normalized_content = []
68
+ grouped_subtitles.each_with_index do |subtitle, index|
69
+ normalized_content << (index + 1).to_s
70
+ normalized_content << "#{subtitle[:start_time]} --> #{subtitle[:end_time]}"
71
+ normalized_content << subtitle[:text]
72
+ normalized_content << ''
73
+ end
74
+
75
+ normalized_content.join("\n")
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Appydave
4
4
  module Tools
5
- VERSION = '0.7.0'
5
+ VERSION = '0.9.0'
6
6
  end
7
7
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module YouTubeManager
6
+ # Handle YouTube API Authorization
7
+ class Authorization
8
+ REDIRECT_URI = 'http://localhost:8080/'
9
+ CLIENT_SECRETS_PATH = File.join(Dir.home, '.config', 'appydave-google-youtube.json')
10
+ CREDENTIALS_PATH = File.join(Dir.home, '.credentials', 'ad_youtube.yaml')
11
+
12
+ SCOPE = [
13
+ 'https://www.googleapis.com/auth/youtube.readonly',
14
+ 'https://www.googleapis.com/auth/youtube'
15
+ ].freeze
16
+
17
+ def self.authorize
18
+ FileUtils.mkdir_p(File.dirname(CREDENTIALS_PATH))
19
+
20
+ client_id = Google::Auth::ClientId.from_file(CLIENT_SECRETS_PATH)
21
+ token_store = Google::Auth::Stores::FileTokenStore.new(file: CREDENTIALS_PATH)
22
+ authorizer = Google::Auth::UserAuthorizer.new(client_id, SCOPE, token_store)
23
+ user_id = 'default'
24
+ credentials = authorizer.get_credentials(user_id)
25
+ credentials = wait_for_authorization(authorizer) if credentials.nil?
26
+ credentials
27
+ end
28
+
29
+ def self.wait_for_authorization(authorizer)
30
+ url = authorizer.get_authorization_url(base_url: REDIRECT_URI)
31
+ puts 'Open the following URL in your browser and authorize the application:'
32
+ puts url
33
+
34
+ server = WEBrick::HTTPServer.new(Port: 8080, AccessLog: [], Logger: WEBrick::Log.new(nil, 0))
35
+ trap('INT') { server.shutdown }
36
+
37
+ server.mount_proc '/' do |req, res|
38
+ auth_code = req.query['code']
39
+ res.body = 'Authorization successful. You can close this window now.'
40
+ server.shutdown
41
+ authorizer.get_and_store_credentials_from_code(
42
+ user_id: user_id, code: auth_code, base_url: REDIRECT_URI
43
+ )
44
+ end
45
+
46
+ server.start
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module YouTubeManager
6
+ # Manage YouTube video details
7
+ class GetVideo < YouTubeBase
8
+ attr_reader :video_id
9
+ attr_reader :data
10
+ attr_reader :video
11
+
12
+ def get(video_id)
13
+ @video_id = video_id
14
+ response = @service.list_videos('snippet,contentDetails,status,statistics', id: video_id)
15
+ @video = response.items.first
16
+
17
+ data = {
18
+ id: video.id,
19
+ title: video.snippet.title,
20
+ description: video.snippet.description,
21
+ published_at: video.snippet.published_at,
22
+ channel_id: video.snippet.channel_id,
23
+ channel_title: video.snippet.channel_title,
24
+ view_count: video.statistics.view_count,
25
+ like_count: video.statistics.like_count,
26
+ dislike_count: video.statistics.dislike_count,
27
+ comment_count: video.statistics.comment_count,
28
+ privacy_status: video.status.privacy_status,
29
+ embeddable: video.status.embeddable,
30
+ license: video.status.license,
31
+ recording_location: video.recording_details&.location,
32
+ recording_date: video.recording_details&.recording_date,
33
+ tags: video.snippet.tags,
34
+ thumbnails: video.snippet.thumbnails.to_h,
35
+ duration: video.content_details.duration,
36
+ definition: video.content_details.definition,
37
+ caption: video.content_details.caption,
38
+ licensed_content: video.content_details.licensed_content
39
+ }
40
+
41
+ @data = Appydave::Tools::IndifferentAccessHash.new(data)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module YouTubeManager
6
+ module Reports
7
+ # Report on video content
8
+ class VideoContentReport
9
+ include KLog::Logging
10
+
11
+ def print(data)
12
+ # log.heading 'Video Details Report'
13
+ log.subheading data[:title]
14
+ log.kv 'Published At', data[:published_at]
15
+ log.kv 'View Count', data[:view_count]
16
+ log.kv 'Like Count', data[:like_count]
17
+ log.kv 'Dislike Count', data[:dislike_count]
18
+ log.kv 'Tags', data[:tags].join(', ')
19
+ log.line
20
+ puts data[:description]
21
+ log.line
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module YouTubeManager
6
+ module Reports
7
+ # Print video details
8
+ class VideoDetailsReport
9
+ include KLog::Logging
10
+
11
+ def print(data)
12
+ # log.heading 'Video Details Report'
13
+ # log.subheading 'Video Details Report'
14
+ log.section_heading 'Video Details Report'
15
+ log.kv 'ID', data[:id]
16
+ log.kv 'Title', data[:title]
17
+ log.kv 'Published At', data[:published_at]
18
+ log.kv 'View Count', data[:view_count]
19
+ log.kv 'Like Count', data[:like_count]
20
+ log.kv 'Dislike Count', data[:dislike_count]
21
+ log.kv 'Comment Count', data[:comment_count]
22
+ log.kv 'Privacy Status', data[:privacy_status]
23
+ log.kv 'Channel ID', data[:channel_id]
24
+ log.kv 'Channel Title', data[:channel_title]
25
+ log.kv 'Embeddable', data[:embeddable]
26
+ log.kv 'License', data[:license]
27
+ log.kv 'Recording Location', data[:recording_location]
28
+ log.kv 'Recording Date', data[:recording_date]
29
+ log.kv 'Tags', data[:tags].join(', ')
30
+ log.kv data[:description][0..100]
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appydave
4
+ module Tools
5
+ module YouTubeManager
6
+ # Base class for YouTube API management
7
+ class YouTubeBase
8
+ def initialize
9
+ @service = Google::Apis::YoutubeV3::YouTubeService.new
10
+ @service.client_options.application_name = 'YouTube Video Manager'
11
+ @service.authorization = Authorization.authorize
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -9,7 +9,16 @@ require 'openai'
9
9
  require 'optparse'
10
10
  require 'k_log'
11
11
 
12
+ require 'google/apis/youtube_v3'
13
+ require 'googleauth'
14
+ require 'googleauth/stores/file_token_store'
15
+ require 'webrick'
16
+
17
+ require 'pry'
18
+
12
19
  require 'appydave/tools/version'
20
+ require 'appydave/tools/indifferent_access_hash'
21
+
13
22
  require 'appydave/tools/gpt_context/file_collector'
14
23
 
15
24
  require 'appydave/tools/configuration/openai'
@@ -25,6 +34,14 @@ require 'appydave/tools/bank_reconciliation/clean/read_transactions'
25
34
  require 'appydave/tools/bank_reconciliation/clean/mapper'
26
35
  require 'appydave/tools/bank_reconciliation/models/transaction'
27
36
 
37
+ require 'appydave/tools/subtitle_master/clean'
38
+
39
+ require 'appydave/tools/youtube_manager/youtube_base'
40
+ require 'appydave/tools/youtube_manager/authorization'
41
+ require 'appydave/tools/youtube_manager/get_video'
42
+ require 'appydave/tools/youtube_manager/reports/video_details_report'
43
+ require 'appydave/tools/youtube_manager/reports/video_content_report'
44
+
28
45
  Appydave::Tools::Configuration::Config.set_default do |config|
29
46
  config.config_path = File.expand_path('~/.config/appydave')
30
47
  config.register(:settings, Appydave::Tools::Configuration::Models::SettingsConfig)
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "appydave-tools",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "appydave-tools",
9
- "version": "0.7.0",
9
+ "version": "0.9.0",
10
10
  "devDependencies": {
11
11
  "@klueless-js/semantic-release-rubygem": "github:klueless-js/semantic-release-rubygem",
12
12
  "@semantic-release/changelog": "^6.0.3",
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appydave-tools",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "AppyDave YouTube Automation Tools",
5
5
  "scripts": {
6
6
  "release": "semantic-release"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appydave-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cruwys
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-29 00:00:00.000000000 Z
11
+ date: 2024-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: clipboard
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: google-api-client
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.53'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.53'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: k_log
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,34 @@ dependencies:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
96
  version: '7'
97
+ - !ruby/object:Gem::Dependency
98
+ name: googleauth
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webrick
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
83
125
  description: " AppyDave YouTube Automation Tools\n"
84
126
  email:
85
127
  - david@ideasmen.com.au
@@ -105,6 +147,8 @@ files:
105
147
  - bin/console
106
148
  - bin/gpt_context.rb
107
149
  - bin/setup
150
+ - bin/subtitle_master.rb
151
+ - bin/youtube_manager.rb
108
152
  - images.log
109
153
  - lib/appydave/tools.rb
110
154
  - lib/appydave/tools/bank_reconciliation/_doc.md
@@ -123,9 +167,17 @@ files:
123
167
  - lib/appydave/tools/configuration/openai.rb
124
168
  - lib/appydave/tools/gpt_context/_doc.md
125
169
  - lib/appydave/tools/gpt_context/file_collector.rb
170
+ - lib/appydave/tools/indifferent_access_hash.rb
126
171
  - lib/appydave/tools/name_manager/_doc.md
127
172
  - lib/appydave/tools/name_manager/project_name.rb
173
+ - lib/appydave/tools/subtitle_master/_doc.md
174
+ - lib/appydave/tools/subtitle_master/clean.rb
128
175
  - lib/appydave/tools/version.rb
176
+ - lib/appydave/tools/youtube_manager/authorization.rb
177
+ - lib/appydave/tools/youtube_manager/get_video.rb
178
+ - lib/appydave/tools/youtube_manager/reports/video_content_report.rb
179
+ - lib/appydave/tools/youtube_manager/reports/video_details_report.rb
180
+ - lib/appydave/tools/youtube_manager/youtube_base.rb
129
181
  - package-lock.json
130
182
  - package.json
131
183
  - sig/appydave/tools.rbs