appydave-tools 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -5
- data/CHANGELOG.md +17 -0
- data/bin/youtube_automation.rb +38 -0
- data/bin/youtube_manager.rb +13 -6
- data/lib/appydave/tools/configuration/_doc.md +1 -0
- data/lib/appydave/tools/configuration/models/config_base.rb +1 -0
- data/lib/appydave/tools/configuration/models/youtube_automation_config.rb +103 -0
- data/lib/appydave/tools/types/array_type.rb +25 -0
- data/lib/appydave/tools/types/hash_type.rb +25 -0
- data/lib/appydave/tools/types/indifferent_access_hash.rb +43 -0
- data/lib/appydave/tools/version.rb +1 -1
- data/lib/appydave/tools/youtube_automation/_doc.md +12 -0
- data/lib/appydave/tools/youtube_automation/gpt_agent.rb +65 -0
- data/lib/appydave/tools/youtube_manager/_doc.md +3 -0
- data/lib/appydave/tools/youtube_manager/authorization.rb +4 -3
- data/lib/appydave/tools/youtube_manager/get_video.rb +63 -3
- data/lib/appydave/tools/youtube_manager/models/captions.rb +23 -0
- data/lib/appydave/tools/youtube_manager/models/youtube_details.rb +45 -0
- data/lib/appydave/tools/youtube_manager/reports/video_content_report.rb +7 -7
- data/lib/appydave/tools.rb +13 -1
- data/package-lock.json +2 -2
- data/package.json +1 -1
- metadata +26 -3
- data/lib/appydave/tools/indifferent_access_hash.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b6d7348b11097175604d3e0dfa967ad9667a36955b825f3ada9042229d35b86
|
4
|
+
data.tar.gz: 91997ead7011056d971b85e7680ee8a099dee006b944210e0766db23d2a40986
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c454d6b2b206b188cff7529094e9cf5b1895933083e7d712e43d55289e3d93be06b0307bb9dd66f13b4910e625e8108b88e58660df15eeb96abaa28f4e439a6
|
7
|
+
data.tar.gz: 1d1c25c9cf722bea8db372b6edca5d2fa3da909d097b0401e7f12e5d66a2dbb6dc15fe001f0b42a8e484ca4866f7755cd86d1f090608960bc4b980aacd34992b
|
data/.rubocop.yml
CHANGED
@@ -75,8 +75,17 @@ Naming/MethodParameterName:
|
|
75
75
|
Style/EmptyMethod:
|
76
76
|
Exclude:
|
77
77
|
- "**/spec/**/*"
|
78
|
-
|
78
|
+
Lint/EmptyFile:
|
79
79
|
Enabled: false
|
80
|
+
|
81
|
+
RSpec/SpecFilePathFormat:
|
82
|
+
Exclude:
|
83
|
+
- "**/spec/**/*"
|
84
|
+
|
85
|
+
RSpec/SpecFilePathSuffix:
|
86
|
+
Exclude:
|
87
|
+
- "**/spec/**/*"
|
88
|
+
|
80
89
|
Metrics/ParameterLists:
|
81
90
|
Exclude:
|
82
91
|
- "**/spec/**/*"
|
@@ -97,10 +106,6 @@ Layout/SpaceBeforeComma:
|
|
97
106
|
# My Preferences - End
|
98
107
|
|
99
108
|
# RSpec Cops
|
100
|
-
RSpec/FilePath:
|
101
|
-
Exclude:
|
102
|
-
- "**/spec/**/*"
|
103
|
-
|
104
109
|
RSpec/NamedSubject:
|
105
110
|
Exclude:
|
106
111
|
- "**/spec/**/*"
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
## [0.9.3](https://github.com/klueless-io/appydave-tools/compare/v0.9.2...v0.9.3) (2024-06-12)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* preperation fo the youtube automation tool ([42c1e1d](https://github.com/klueless-io/appydave-tools/commit/42c1e1d3a4ea53491b72140bbf35850e41ce0f1c))
|
7
|
+
* update rubocop version number ([b64739d](https://github.com/klueless-io/appydave-tools/commit/b64739d46a5b3ea6177c2f7512c720b6ed4e6257))
|
8
|
+
|
9
|
+
## [0.9.2](https://github.com/klueless-io/appydave-tools/compare/v0.9.1...v0.9.2) (2024-06-11)
|
10
|
+
|
11
|
+
|
12
|
+
### Bug Fixes
|
13
|
+
|
14
|
+
* check for ci errors with openai ([2a9d549](https://github.com/klueless-io/appydave-tools/commit/2a9d549eb95246568ca54c1ec0fb9735cc26cac9))
|
15
|
+
* prepare openai for CI testing ([09c0d5a](https://github.com/klueless-io/appydave-tools/commit/09c0d5a998d00abb52eb3f09125bb955af1917d9))
|
16
|
+
* prepare openai for CI testing [#2](https://github.com/klueless-io/appydave-tools/issues/2) ([82247fc](https://github.com/klueless-io/appydave-tools/commit/82247fc2f421bd88859d25b0fbe88493b6e6d87e))
|
17
|
+
|
1
18
|
## [0.9.1](https://github.com/klueless-io/appydave-tools/compare/v0.9.0...v0.9.1) (2024-06-11)
|
2
19
|
|
3
20
|
|
@@ -0,0 +1,38 @@
|
|
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
|
+
options = {
|
9
|
+
sequence: nil,
|
10
|
+
debug: false
|
11
|
+
}
|
12
|
+
|
13
|
+
OptionParser.new do |opts|
|
14
|
+
opts.banner = 'Usage: youtube_automation.rb [options]'
|
15
|
+
|
16
|
+
opts.on('-s', '--sequence SEQUENCE', 'Sequence number (e.g., 01-1)') do |sequence|
|
17
|
+
options[:sequence] = sequence
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on('-d', '--debug', 'Enable debug mode') do
|
21
|
+
options[:debug] = true
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
25
|
+
puts opts
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
end.parse!
|
29
|
+
|
30
|
+
if options[:sequence].nil?
|
31
|
+
puts 'Error: Sequence number is required. Use -h for help.'
|
32
|
+
exit 1
|
33
|
+
end
|
34
|
+
|
35
|
+
Appydave::Tools::Configuration::Config.configure
|
36
|
+
|
37
|
+
automation = Appydave::Tools::YoutubeAutomation::GptAgent.new(options[:sequence], options[:debug])
|
38
|
+
automation.run
|
data/bin/youtube_manager.rb
CHANGED
@@ -7,6 +7,8 @@ require 'appydave/tools'
|
|
7
7
|
|
8
8
|
# Process command line arguments for YouTubeVideoManager operations
|
9
9
|
class YouTubeVideoManagerCLI
|
10
|
+
include KLog::Logging
|
11
|
+
|
10
12
|
def initialize
|
11
13
|
@commands = {
|
12
14
|
'get' => method(:fetch_video_details)
|
@@ -30,14 +32,19 @@ class YouTubeVideoManagerCLI
|
|
30
32
|
options = parse_options(args, 'get')
|
31
33
|
manager = Appydave::Tools::YouTubeManager::GetVideo.new
|
32
34
|
manager.get(options[:video_id])
|
33
|
-
# json = JSON.pretty_generate(details)
|
34
|
-
# puts json
|
35
35
|
|
36
|
-
|
37
|
-
|
36
|
+
if manager.video?
|
37
|
+
# json = JSON.pretty_generate(details)
|
38
|
+
# puts json
|
39
|
+
|
40
|
+
# report = Appydave::Tools::YouTubeManager::Reports::VideoDetailsReport.new
|
41
|
+
# report.print(manager.data)
|
38
42
|
|
39
|
-
|
40
|
-
|
43
|
+
report = Appydave::Tools::YouTubeManager::Reports::VideoContentReport.new
|
44
|
+
report.print(manager.data)
|
45
|
+
else
|
46
|
+
log.error "Video not found! Maybe it's private or deleted. ID: #{options[:video_id]}"
|
47
|
+
end
|
41
48
|
end
|
42
49
|
|
43
50
|
def parse_options(args, command)
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
[ChatGPT conversation](https://chatgpt.com/g/g-4dMsIRK3E-ruby-script-assistant/c/d8ea5960-071b-48aa-9fd9-554ca302c7dd)
|
4
4
|
[ChatGPT conversation for Schema](https://chatgpt.com/c/bb93e7ac-f139-44f9-8b9c-4e74ac2fa461)
|
5
|
+
[ChatGPT conversation for the YoutubeAutomationConfig][https://chatgpt.com/c/a7993d3f-35c9-4ec6-8cd6-5a506bfa22df]
|
5
6
|
|
6
7
|
|
7
8
|
## Schema
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Appydave
|
4
|
+
module Tools
|
5
|
+
module Configuration
|
6
|
+
module Models
|
7
|
+
# Configuration model for Youtube automation
|
8
|
+
class YoutubeAutomationConfig < ConfigBase
|
9
|
+
def workflow_groups
|
10
|
+
data['workflow_groups'].map do |group|
|
11
|
+
WorkflowGroup.new(group)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def workflow_steps
|
16
|
+
data['workflow_steps'].map do |step|
|
17
|
+
WorkflowStep.new(step)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_workflow_group(group_id)
|
22
|
+
group_data = data['workflow_groups'].find { |group| group['group'] == group_id }
|
23
|
+
WorkflowGroup.new(group_data) if group_data
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_workflow_steps(group_id)
|
27
|
+
data['workflow_steps'].select { |step| step['group'] == group_id }.map do |step|
|
28
|
+
WorkflowStep.new(step)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_sequence(sequence)
|
33
|
+
group, seq = sequence.split('-')
|
34
|
+
workflow_steps.find { |step| step.group == group && step.sequence == seq }
|
35
|
+
end
|
36
|
+
|
37
|
+
def next_sequence(sequence)
|
38
|
+
group, seq = sequence.split('-')
|
39
|
+
current_index = workflow_steps.find_index { |step| step.group == group && step.sequence == seq }
|
40
|
+
workflow_steps[current_index + 1] if current_index && current_index < workflow_steps.size - 1
|
41
|
+
end
|
42
|
+
|
43
|
+
def previous_sequence(sequence)
|
44
|
+
group, seq = sequence.split('-')
|
45
|
+
current_index = workflow_steps.find_index { |step| step.group == group && step.sequence == seq }
|
46
|
+
workflow_steps[current_index - 1] if current_index&.positive?
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def default_data
|
52
|
+
{
|
53
|
+
'workflow_groups' => [],
|
54
|
+
'workflow_steps' => []
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
# Model for workflow groups
|
59
|
+
class WorkflowGroup
|
60
|
+
attr_accessor :group, :description
|
61
|
+
|
62
|
+
def initialize(data)
|
63
|
+
@group = data['group']
|
64
|
+
@description = data['description']
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_h
|
68
|
+
{
|
69
|
+
'group' => @group,
|
70
|
+
'description' => @description
|
71
|
+
}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Model for workflow steps
|
76
|
+
class WorkflowStep
|
77
|
+
attr_accessor :group, :sequence, :name, :description
|
78
|
+
|
79
|
+
def initialize(data)
|
80
|
+
@group = data['group']
|
81
|
+
@sequence = data['sequence']
|
82
|
+
@name = data['name']
|
83
|
+
@description = data['description']
|
84
|
+
end
|
85
|
+
|
86
|
+
def filename(ext)
|
87
|
+
"#{@group}-#{@sequence}-#{@name}.#{ext}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_h
|
91
|
+
{
|
92
|
+
'group' => @group,
|
93
|
+
'sequence' => @sequence,
|
94
|
+
'name' => @name,
|
95
|
+
'description' => @description
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Appydave
|
4
|
+
module Tools
|
5
|
+
module Types
|
6
|
+
# Used by the ActiveModel attributes API to cast values to arrays
|
7
|
+
class ArrayType < ActiveModel::Type::Value
|
8
|
+
def cast(value)
|
9
|
+
case value
|
10
|
+
when String
|
11
|
+
value.split(',')
|
12
|
+
when Array
|
13
|
+
value
|
14
|
+
else
|
15
|
+
raise ArgumentError, "Cannot cast #{value.class} to Array"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def serialize(value)
|
20
|
+
value.join(',')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Appydave
|
4
|
+
module Tools
|
5
|
+
module Types
|
6
|
+
# Used by the ActiveModel attributes API to cast values to hashes
|
7
|
+
class HashType < ActiveModel::Type::Value
|
8
|
+
def cast(value)
|
9
|
+
case value
|
10
|
+
when String
|
11
|
+
JSON.parse(value)
|
12
|
+
when Hash
|
13
|
+
value
|
14
|
+
else
|
15
|
+
raise ArgumentError, "Cannot cast #{value.class} to Hash"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def serialize(value)
|
20
|
+
value.to_json
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Appydave
|
4
|
+
module Tools
|
5
|
+
module Types
|
6
|
+
# Hash with indifferent access
|
7
|
+
class IndifferentAccessHash < Hash
|
8
|
+
def initialize(initial_hash = {})
|
9
|
+
super()
|
10
|
+
update(initial_hash)
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](key)
|
14
|
+
super(convert_key(key))
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, value)
|
18
|
+
super(convert_key(key), value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch(key, *args)
|
22
|
+
super(convert_key(key), *args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(key)
|
26
|
+
super(convert_key(key))
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def convert_key(key)
|
32
|
+
key.is_a?(Symbol) ? key.to_s : key
|
33
|
+
end
|
34
|
+
|
35
|
+
def update(initial_hash)
|
36
|
+
initial_hash.each do |key, value|
|
37
|
+
self[convert_key(key)] = value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Youtube Automation
|
2
|
+
|
3
|
+
ChatGPT: https://chatgpt.com/c/2971aaeb-71f7-4981-9c85-21f1dabef9d9
|
4
|
+
|
5
|
+
YouTube automation is a set of tools for building YouTube videos automatically. The tools are designed to help with the following tasks:
|
6
|
+
|
7
|
+
[REWRITE THIS INFORMATION ONCE WE HAVE MORE DETAILS]
|
8
|
+
|
9
|
+
- Take a video concept or topic as simple
|
10
|
+
- Build out a simple title based on the topic
|
11
|
+
- Develop basic research around the area of keyboards
|
12
|
+
- Develop a fat sheet that has the basic information about the topic
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Appydave
|
4
|
+
module Tools
|
5
|
+
module YoutubeAutomation
|
6
|
+
# GPT Agent interacts with OpenAI's GPT API to generate responses based on prompts for YouTube automation.
|
7
|
+
class GptAgent
|
8
|
+
include Appydave::Tools::Configuration::Configurable
|
9
|
+
|
10
|
+
def initialize(sequence, debug: false)
|
11
|
+
@sequence = sequence
|
12
|
+
@debug = debug
|
13
|
+
@prompts_path = '/Users/davidcruwys/Library/CloudStorage/Dropbox/team-tldr/_common/raw_prompts'
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
step = config.youtube_automation.get_sequence(@sequence)
|
18
|
+
unless step
|
19
|
+
puts "Error: Step #{@sequence} not found in the configuration."
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
|
23
|
+
prompt_content = read_prompt(step)
|
24
|
+
|
25
|
+
puts "Running sequence: #{@sequence}"
|
26
|
+
puts "Prompt file: #{prompt_file}"
|
27
|
+
puts "Prompt content:\n#{prompt_content}" if @debug
|
28
|
+
|
29
|
+
response = run_prompt(prompt_content)
|
30
|
+
|
31
|
+
puts "Response:\n#{response}"
|
32
|
+
save_response(prompt_file, response)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def read_prompt(_step)
|
38
|
+
File.read(filename)
|
39
|
+
rescue Errno::ENOENT
|
40
|
+
puts "Error: Prompt file #{filename} not found."
|
41
|
+
exit 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def run_prompt(prompt_content)
|
45
|
+
client = OpenAI::Client.new(access_token: ENV.fetch('OPENAI_ACCESS_TOKEN', nil))
|
46
|
+
response = client.completions(
|
47
|
+
engine: 'davinci-codex',
|
48
|
+
prompt: prompt_content,
|
49
|
+
max_tokens: 1000
|
50
|
+
)
|
51
|
+
response['choices'][0]['text'].strip
|
52
|
+
rescue StandardError => e
|
53
|
+
puts "Error: Failed to run prompt. #{e.message}"
|
54
|
+
exit 1
|
55
|
+
end
|
56
|
+
|
57
|
+
def save_response(filename, response)
|
58
|
+
output_filename = filename.gsub('.md', '-output.md')
|
59
|
+
File.write(output_filename, response)
|
60
|
+
puts "Response saved to #{output_filename}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -11,7 +11,8 @@ module Appydave
|
|
11
11
|
|
12
12
|
SCOPE = [
|
13
13
|
'https://www.googleapis.com/auth/youtube.readonly',
|
14
|
-
'https://www.googleapis.com/auth/youtube'
|
14
|
+
'https://www.googleapis.com/auth/youtube',
|
15
|
+
'https://www.googleapis.com/auth/youtube.force-ssl'
|
15
16
|
].freeze
|
16
17
|
|
17
18
|
def self.authorize
|
@@ -22,11 +23,11 @@ module Appydave
|
|
22
23
|
authorizer = Google::Auth::UserAuthorizer.new(client_id, SCOPE, token_store)
|
23
24
|
user_id = 'default'
|
24
25
|
credentials = authorizer.get_credentials(user_id)
|
25
|
-
credentials = wait_for_authorization(authorizer) if credentials.nil?
|
26
|
+
credentials = wait_for_authorization(authorizer, user_id) if credentials.nil?
|
26
27
|
credentials
|
27
28
|
end
|
28
29
|
|
29
|
-
def self.wait_for_authorization(authorizer)
|
30
|
+
def self.wait_for_authorization(authorizer, user_id)
|
30
31
|
url = authorizer.get_authorization_url(base_url: REDIRECT_URI)
|
31
32
|
puts 'Open the following URL in your browser and authorize the application:'
|
32
33
|
puts url
|
@@ -7,13 +7,25 @@ module Appydave
|
|
7
7
|
class GetVideo < YouTubeBase
|
8
8
|
attr_reader :video_id
|
9
9
|
attr_reader :data
|
10
|
-
attr_reader :video
|
11
10
|
|
12
11
|
def get(video_id)
|
12
|
+
@data = nil
|
13
13
|
@video_id = video_id
|
14
14
|
response = @service.list_videos('snippet,contentDetails,status,statistics', id: video_id)
|
15
|
-
|
15
|
+
video = response.items.first
|
16
16
|
|
17
|
+
return unless video
|
18
|
+
|
19
|
+
build_data(video)
|
20
|
+
end
|
21
|
+
|
22
|
+
def video?
|
23
|
+
!data.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def build_data(video)
|
17
29
|
data = {
|
18
30
|
id: video.id,
|
19
31
|
title: video.snippet.title,
|
@@ -37,8 +49,56 @@ module Appydave
|
|
37
49
|
caption: video.content_details.caption,
|
38
50
|
licensed_content: video.content_details.licensed_content
|
39
51
|
}
|
52
|
+
# HAVE NOT FIGURED OUT THE PERMISSION ISSUE WITH THIS YET
|
53
|
+
# captions: get_captions(video.id) # Fetch captions and associate them
|
54
|
+
@data = Appydave::Tools::YouTubeManager::Models::YouTubeDetails.new(data)
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_captions(video_id)
|
58
|
+
captions_response = @service.list_captions('snippet', video_id)
|
59
|
+
captions_response.items.map do |caption|
|
60
|
+
puts "Caption ID: #{caption.id}"
|
61
|
+
puts "Language: #{caption.snippet.language}"
|
62
|
+
puts "Status: #{caption.snippet.status}"
|
63
|
+
puts "Track Kind: #{caption.snippet.track_kind}"
|
64
|
+
puts "Name: #{caption.snippet.name}"
|
65
|
+
puts "Is Auto-Synced: #{caption.snippet.is_auto_synced}"
|
66
|
+
puts "Is CC: #{caption.snippet.is_cc}"
|
67
|
+
puts "Is Draft: #{caption.snippet.is_draft}"
|
68
|
+
puts "Last Updated: #{caption.snippet.last_updated}"
|
69
|
+
|
70
|
+
next unless caption.snippet.status == 'serving'
|
71
|
+
|
72
|
+
caption_data = {
|
73
|
+
caption_id: caption.id,
|
74
|
+
language: caption.snippet.language,
|
75
|
+
name: caption.snippet.name,
|
76
|
+
status: caption.snippet.status,
|
77
|
+
content: download_caption(caption)
|
78
|
+
}
|
79
|
+
caption_data
|
80
|
+
end.compact
|
81
|
+
end
|
82
|
+
|
83
|
+
def download_caption(caption)
|
84
|
+
content = ''
|
85
|
+
formats = %w[srv1 vtt srt ttml] # Try multiple formats to find a compatible one
|
86
|
+
formats.each do |format|
|
87
|
+
break if content.present?
|
40
88
|
|
41
|
-
|
89
|
+
begin
|
90
|
+
@service.download_caption(caption.id, tfmt: format) do |result, error|
|
91
|
+
if error.nil?
|
92
|
+
content = result
|
93
|
+
else
|
94
|
+
puts "An error occurred while downloading caption #{caption.id} with format #{format}: #{error.message}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
rescue Google::Apis::ClientError => e
|
98
|
+
puts "An error occurred while downloading caption #{caption.id} with format #{format}: #{e.message}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
content
|
42
102
|
end
|
43
103
|
end
|
44
104
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model'
|
4
|
+
|
5
|
+
module Appydave
|
6
|
+
module Tools
|
7
|
+
module YouTubeManager
|
8
|
+
module Models
|
9
|
+
# Model to store YouTube video captions (subtitles)
|
10
|
+
class Captions
|
11
|
+
include ActiveModel::Model
|
12
|
+
include ActiveModel::Attributes
|
13
|
+
|
14
|
+
attribute :caption_id, :string
|
15
|
+
attribute :language, :string
|
16
|
+
attribute :name, :string
|
17
|
+
attribute :status, :string
|
18
|
+
attribute :content, :string
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_model'
|
4
|
+
|
5
|
+
module Appydave
|
6
|
+
module Tools
|
7
|
+
module YouTubeManager
|
8
|
+
module Models
|
9
|
+
# Model to store YouTube video details
|
10
|
+
class YouTubeDetails
|
11
|
+
include ActiveModel::Model
|
12
|
+
include ActiveModel::Attributes
|
13
|
+
|
14
|
+
attribute :id, :string
|
15
|
+
attribute :title, :string
|
16
|
+
attribute :description, :string
|
17
|
+
attribute :published_at, :datetime
|
18
|
+
attribute :channel_id, :string
|
19
|
+
attribute :channel_title, :string
|
20
|
+
attribute :view_count, :integer
|
21
|
+
attribute :like_count, :integer
|
22
|
+
attribute :dislike_count, :integer
|
23
|
+
attribute :comment_count, :integer
|
24
|
+
attribute :privacy_status, :string
|
25
|
+
attribute :embeddable, :boolean
|
26
|
+
attribute :license, :string
|
27
|
+
attribute :recording_location, :string
|
28
|
+
attribute :recording_date, :datetime
|
29
|
+
attribute :tags, array: true
|
30
|
+
attribute :thumbnails, :hash
|
31
|
+
attribute :duration, :string
|
32
|
+
attribute :definition, :string
|
33
|
+
attribute :caption, :boolean
|
34
|
+
attribute :licensed_content, :boolean
|
35
|
+
# attribute :captions, :array, default: []
|
36
|
+
|
37
|
+
# def initialize(attributes = {})
|
38
|
+
# super
|
39
|
+
# self.captions = attributes[:captions].map { |caption| Captions.new(caption) } if attributes[:captions]
|
40
|
+
# end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -10,14 +10,14 @@ module Appydave
|
|
10
10
|
|
11
11
|
def print(data)
|
12
12
|
# log.heading 'Video Details Report'
|
13
|
-
log.subheading data
|
14
|
-
log.kv 'Published At', data
|
15
|
-
log.kv 'View Count', data
|
16
|
-
log.kv 'Like Count', data
|
17
|
-
log.kv 'Dislike Count', data
|
18
|
-
log.kv 'Tags', data
|
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
19
|
log.line
|
20
|
-
puts data
|
20
|
+
puts data.description
|
21
21
|
log.line
|
22
22
|
end
|
23
23
|
end
|
data/lib/appydave/tools.rb
CHANGED
@@ -8,6 +8,7 @@ require 'open3'
|
|
8
8
|
require 'openai'
|
9
9
|
require 'optparse'
|
10
10
|
require 'k_log'
|
11
|
+
require 'active_model'
|
11
12
|
|
12
13
|
require 'google/apis/youtube_v3'
|
13
14
|
require 'googleauth'
|
@@ -17,7 +18,12 @@ require 'webrick'
|
|
17
18
|
require 'pry'
|
18
19
|
|
19
20
|
require 'appydave/tools/version'
|
20
|
-
require 'appydave/tools/indifferent_access_hash'
|
21
|
+
require 'appydave/tools/types/indifferent_access_hash'
|
22
|
+
require 'appydave/tools/types/hash_type'
|
23
|
+
require 'appydave/tools/types/array_type'
|
24
|
+
|
25
|
+
ActiveModel::Type.register(:array, Appydave::Tools::Types::ArrayType)
|
26
|
+
ActiveModel::Type.register(:hash, Appydave::Tools::Types::HashType)
|
21
27
|
|
22
28
|
require 'appydave/tools/gpt_context/file_collector'
|
23
29
|
|
@@ -28,6 +34,7 @@ require 'appydave/tools/configuration/models/config_base'
|
|
28
34
|
require 'appydave/tools/configuration/models/settings_config'
|
29
35
|
require 'appydave/tools/configuration/models/bank_reconciliation_config'
|
30
36
|
require 'appydave/tools/configuration/models/channels_config'
|
37
|
+
require 'appydave/tools/configuration/models/youtube_automation_config'
|
31
38
|
require 'appydave/tools/name_manager/project_name'
|
32
39
|
require 'appydave/tools/bank_reconciliation/clean/clean_transactions'
|
33
40
|
require 'appydave/tools/bank_reconciliation/clean/read_transactions'
|
@@ -36,6 +43,10 @@ require 'appydave/tools/bank_reconciliation/models/transaction'
|
|
36
43
|
|
37
44
|
require 'appydave/tools/subtitle_master/clean'
|
38
45
|
|
46
|
+
require 'appydave/tools/youtube_automation/gpt_agent'
|
47
|
+
|
48
|
+
require 'appydave/tools/youtube_manager/models/youtube_details'
|
49
|
+
require 'appydave/tools/youtube_manager/models/captions'
|
39
50
|
require 'appydave/tools/youtube_manager/youtube_base'
|
40
51
|
require 'appydave/tools/youtube_manager/authorization'
|
41
52
|
require 'appydave/tools/youtube_manager/get_video'
|
@@ -47,6 +58,7 @@ Appydave::Tools::Configuration::Config.set_default do |config|
|
|
47
58
|
config.register(:settings, Appydave::Tools::Configuration::Models::SettingsConfig)
|
48
59
|
config.register(:bank_reconciliation, Appydave::Tools::Configuration::Models::BankReconciliationConfig)
|
49
60
|
config.register(:channels, Appydave::Tools::Configuration::Models::ChannelsConfig)
|
61
|
+
config.register(:youtube_automation, Appydave::Tools::Configuration::Models::YoutubeAutomationConfig)
|
50
62
|
end
|
51
63
|
|
52
64
|
module Appydave
|
data/package-lock.json
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"name": "appydave-tools",
|
3
|
-
"version": "0.9.
|
3
|
+
"version": "0.9.4",
|
4
4
|
"lockfileVersion": 3,
|
5
5
|
"requires": true,
|
6
6
|
"packages": {
|
7
7
|
"": {
|
8
8
|
"name": "appydave-tools",
|
9
|
-
"version": "0.9.
|
9
|
+
"version": "0.9.4",
|
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
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: appydave-tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.4
|
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-06-
|
11
|
+
date: 2024-06-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activemodel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: clipboard
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -148,6 +162,7 @@ files:
|
|
148
162
|
- bin/gpt_context.rb
|
149
163
|
- bin/setup
|
150
164
|
- bin/subtitle_master.rb
|
165
|
+
- bin/youtube_automation.rb
|
151
166
|
- bin/youtube_manager.rb
|
152
167
|
- images.log
|
153
168
|
- lib/appydave/tools.rb
|
@@ -164,17 +179,25 @@ files:
|
|
164
179
|
- lib/appydave/tools/configuration/models/config_base.rb
|
165
180
|
- lib/appydave/tools/configuration/models/settings_config copy.xrb
|
166
181
|
- lib/appydave/tools/configuration/models/settings_config.rb
|
182
|
+
- lib/appydave/tools/configuration/models/youtube_automation_config.rb
|
167
183
|
- lib/appydave/tools/configuration/openai.rb
|
168
184
|
- lib/appydave/tools/gpt_context/_doc.md
|
169
185
|
- lib/appydave/tools/gpt_context/file_collector.rb
|
170
|
-
- lib/appydave/tools/indifferent_access_hash.rb
|
171
186
|
- lib/appydave/tools/name_manager/_doc.md
|
172
187
|
- lib/appydave/tools/name_manager/project_name.rb
|
173
188
|
- lib/appydave/tools/subtitle_master/_doc.md
|
174
189
|
- lib/appydave/tools/subtitle_master/clean.rb
|
190
|
+
- lib/appydave/tools/types/array_type.rb
|
191
|
+
- lib/appydave/tools/types/hash_type.rb
|
192
|
+
- lib/appydave/tools/types/indifferent_access_hash.rb
|
175
193
|
- lib/appydave/tools/version.rb
|
194
|
+
- lib/appydave/tools/youtube_automation/_doc.md
|
195
|
+
- lib/appydave/tools/youtube_automation/gpt_agent.rb
|
196
|
+
- lib/appydave/tools/youtube_manager/_doc.md
|
176
197
|
- lib/appydave/tools/youtube_manager/authorization.rb
|
177
198
|
- lib/appydave/tools/youtube_manager/get_video.rb
|
199
|
+
- lib/appydave/tools/youtube_manager/models/captions.rb
|
200
|
+
- lib/appydave/tools/youtube_manager/models/youtube_details.rb
|
178
201
|
- lib/appydave/tools/youtube_manager/reports/video_content_report.rb
|
179
202
|
- lib/appydave/tools/youtube_manager/reports/video_details_report.rb
|
180
203
|
- lib/appydave/tools/youtube_manager/youtube_base.rb
|
@@ -1,41 +0,0 @@
|
|
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
|