deadlist 1.1.0 → 1.2.0
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/lib/deadlist/cli/argument_parser.rb +28 -8
- data/lib/deadlist/cli/client.rb +2 -2
- data/lib/deadlist/cli/downloader.rb +20 -4
- data/lib/deadlist/cli.rb +39 -25
- data/lib/deadlist/models/show.rb +20 -12
- data/lib/deadlist/models/track.rb +2 -2
- data/lib/deadlist/version.rb +3 -0
- data/lib/deadlist.rb +44 -15
- metadata +3 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a7a8fcc71296e7c196d233a170985d82a143da192420557a869ec6924bc165bc
|
|
4
|
+
data.tar.gz: 1bffff8c94561151b48ec9adabcdb418e7df13971b810f7deb61d5f4ceda678b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 142ff3d85a10c2ead5b00a14692dbbd0736c3f9b0c8b7ff0ec5a1c92b278bd11038459ae2fc0f175b85035f7222fa8f7a705541315a60bda213beccc1e6247c8
|
|
7
|
+
data.tar.gz: 6c86fae1e34b504a2385e241ec97b54c1944db52caa4025d6d03f982c8d2199c1c07ec500277d0966a5b0c2d30a3383f844dc995d84087f7149dbd324b3f0d85
|
|
@@ -7,8 +7,8 @@ class ArgumentParser
|
|
|
7
7
|
opts.separator ""
|
|
8
8
|
opts.separator "Required options:"
|
|
9
9
|
|
|
10
|
-
opts.on("-i", "--id ID", "ID of show to download") do |id|
|
|
11
|
-
params[:
|
|
10
|
+
opts.on("-i", "--id ID", "ID of show(s) to download (comma-separated for multiple)") do |id|
|
|
11
|
+
params[:ids] = id.split(',').map(&:strip)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
opts.on("-f", "--format FORMAT", "Format to download (mp3, flac, ogg)") do |format|
|
|
@@ -18,6 +18,10 @@ class ArgumentParser
|
|
|
18
18
|
opts.separator ""
|
|
19
19
|
opts.separator "Other options:"
|
|
20
20
|
|
|
21
|
+
opts.on("-d", "--directory PATH", "Directory to save show(s) to. Will default to /shows/ in the execution directory") do |dir|
|
|
22
|
+
params[:directory] = dir
|
|
23
|
+
end
|
|
24
|
+
|
|
21
25
|
opts.on("-h", "--help", "Show this help") do
|
|
22
26
|
puts opts
|
|
23
27
|
exit
|
|
@@ -27,6 +31,14 @@ class ArgumentParser
|
|
|
27
31
|
puts "deadlist v#{version}"
|
|
28
32
|
exit
|
|
29
33
|
end
|
|
34
|
+
|
|
35
|
+
opts.on('-q', '--quiet', 'Run silently') do
|
|
36
|
+
params[:quiet] = true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
opts.on('--dry-run', 'List tracks to be downloaded') do
|
|
40
|
+
params[:dry_run] = true
|
|
41
|
+
end
|
|
30
42
|
end
|
|
31
43
|
|
|
32
44
|
parser.parse!(args)
|
|
@@ -41,12 +53,20 @@ class ArgumentParser
|
|
|
41
53
|
private
|
|
42
54
|
|
|
43
55
|
def self.validate_required_params!(params, parser)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
puts "Error:
|
|
56
|
+
has_ids = params[:ids]&.any?
|
|
57
|
+
has_format = params[:format]
|
|
58
|
+
|
|
59
|
+
# If one is provided, both must be provided
|
|
60
|
+
if !has_ids && !has_format
|
|
61
|
+
puts "Error: Arguments are required for DeadList, try --help for more info"
|
|
62
|
+
puts parser
|
|
63
|
+
exit(1)
|
|
64
|
+
elsif has_ids && !has_format
|
|
65
|
+
puts "Error: --format is required when --id is provided"
|
|
66
|
+
puts parser
|
|
67
|
+
exit(1)
|
|
68
|
+
elsif has_format && !has_ids
|
|
69
|
+
puts "Error: --id is required when --format is provided"
|
|
50
70
|
puts parser
|
|
51
71
|
exit(1)
|
|
52
72
|
end
|
data/lib/deadlist/cli/client.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# The Client class manages HTML scraping and parsing for the CLI and other classes above it. Any HTML work should be handled here.
|
|
2
2
|
class Client
|
|
3
3
|
def query_show_info(show_id)
|
|
4
|
-
url =
|
|
4
|
+
url = "https://archive.org/metadata/#{show_id}"
|
|
5
5
|
response = HTTParty.get(url)
|
|
6
6
|
|
|
7
7
|
unless response.success?
|
|
@@ -22,7 +22,7 @@ class Client
|
|
|
22
22
|
files: response["files"]
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
show_data
|
|
26
26
|
rescue HTTParty::Error, StandardError => e
|
|
27
27
|
raise "Failed to fetch show data: #{e.message}"
|
|
28
28
|
end
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
class Downloader
|
|
4
4
|
BASE_API_URL = 'https://archive.org'
|
|
5
5
|
|
|
6
|
-
def initialize(path, format)
|
|
6
|
+
def initialize(path, format, logger: Logger.new($stdout))
|
|
7
|
+
@logger = logger
|
|
7
8
|
@path = path
|
|
8
9
|
@format = format
|
|
9
10
|
end
|
|
@@ -16,10 +17,25 @@ class Downloader
|
|
|
16
17
|
def get(root_url, track_object)
|
|
17
18
|
uri = URI.parse(root_url + track_object.filename); raise ArgumentError, "Only HTTP(S) URLs allowed" unless uri.is_a?(URI::HTTP)
|
|
18
19
|
download = uri.open
|
|
19
|
-
filename = "#{@path}/#{track_object.pos} -- #{track_object.title}.#{@format}"
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
# Extract disc number from filename
|
|
22
|
+
disc_match = track_object.filename.match(/(?<!g)d(\d+)/)
|
|
23
|
+
sanitized_title = track_object.title.gsub('/', '-')
|
|
24
|
+
|
|
25
|
+
if disc_match
|
|
26
|
+
# Multi-disc: use disc-track format (1-01, 2-01, etc.)
|
|
27
|
+
disc_num = disc_match[1]
|
|
28
|
+
padded_track = track_object.pos.rjust(2, '0')
|
|
29
|
+
filename = "#{@path}/#{disc_num}-#{padded_track} -- #{sanitized_title}.#{@format}"
|
|
30
|
+
else
|
|
31
|
+
# Single disc: regular format
|
|
32
|
+
filename = "#{@path}/#{track_object.pos} -- #{sanitized_title}.#{@format}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
IO.copy_stream(download, filename)
|
|
36
|
+
true
|
|
22
37
|
rescue => e
|
|
23
|
-
|
|
38
|
+
@logger.error "❌ Download failed for '#{track_object.title}': #{e.message}"
|
|
39
|
+
false
|
|
24
40
|
end
|
|
25
41
|
end
|
data/lib/deadlist/cli.rb
CHANGED
|
@@ -2,16 +2,22 @@ require_relative 'cli/client'
|
|
|
2
2
|
require_relative 'cli/downloader'
|
|
3
3
|
require_relative 'models/show'
|
|
4
4
|
require_relative 'models/track'
|
|
5
|
-
require_relative 'cli/argument_parser
|
|
5
|
+
require_relative 'cli/argument_parser'
|
|
6
6
|
require 'fileutils'
|
|
7
7
|
require 'optparse'
|
|
8
8
|
|
|
9
9
|
# The CLI is the 'session' created by the main class, managing arguments passed in and housing methods for scraping and downloading shows.
|
|
10
10
|
class CLI
|
|
11
|
-
|
|
11
|
+
attr_reader :args, :show
|
|
12
|
+
|
|
13
|
+
def initialize(version, args, logger: Logger.new($stdout))
|
|
12
14
|
@version = version
|
|
13
15
|
@args = {}
|
|
14
16
|
@show = nil
|
|
17
|
+
@logger = logger
|
|
18
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
|
19
|
+
"#{msg}\n"
|
|
20
|
+
end
|
|
15
21
|
|
|
16
22
|
startup_text
|
|
17
23
|
parse_arguments(args)
|
|
@@ -19,34 +25,37 @@ class CLI
|
|
|
19
25
|
|
|
20
26
|
# Creates new show object with link given populated with metadata and track details
|
|
21
27
|
def create_show
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
show_id = @args[:ids].first
|
|
29
|
+
extracted_id = extract_show_id(show_id)
|
|
30
|
+
@show = Show.new(extracted_id, @args[:format], logger: @logger)
|
|
24
31
|
|
|
25
|
-
|
|
32
|
+
@logger.info "💿 #{@show.name} - #{@show.tracks.length} tracks found!"
|
|
26
33
|
rescue => e
|
|
27
|
-
|
|
34
|
+
@logger.error "❌ Scraping failed: #{e.message}"
|
|
28
35
|
end
|
|
29
36
|
|
|
30
|
-
#
|
|
37
|
+
# Downloads show tracks or displays dry-run preview
|
|
31
38
|
def download_show
|
|
32
|
-
if @args[:
|
|
33
|
-
|
|
39
|
+
if @args[:dry_run]
|
|
40
|
+
@logger.info "🔍 Dry Run: #{@show.name} will be downloaded with #{@show.tracks.count} tracks"
|
|
41
|
+
@show.tracks.each do |track|
|
|
42
|
+
@logger.info " #{track.pos} - #{track.title}"
|
|
43
|
+
end
|
|
34
44
|
else
|
|
35
|
-
download_directory = setup_directories(@show)
|
|
45
|
+
download_directory = setup_directories(@show, @args[:directory])
|
|
36
46
|
@show.download_tracks(download_directory)
|
|
37
47
|
end
|
|
38
48
|
rescue => e
|
|
39
|
-
|
|
49
|
+
@logger.error "❌ Download failed: #{e.message}"
|
|
40
50
|
end
|
|
41
51
|
|
|
42
52
|
private
|
|
43
53
|
|
|
44
54
|
# Deadlist starts with some friendly text
|
|
45
55
|
def startup_text
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
puts '='*52
|
|
56
|
+
@logger.info '='*52
|
|
57
|
+
@logger.info "🌹⚡️ One man gathers what another man spills... ⚡️🌹"
|
|
58
|
+
@logger.info '='*52
|
|
50
59
|
end
|
|
51
60
|
|
|
52
61
|
# Reads arguments passed at the command line and maps them to an instance object
|
|
@@ -63,17 +72,22 @@ class CLI
|
|
|
63
72
|
end
|
|
64
73
|
|
|
65
74
|
# Configures directories that will be used by the downloader
|
|
66
|
-
def setup_directories(show,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
def setup_directories(show, custom_path = nil)
|
|
76
|
+
if custom_path
|
|
77
|
+
# Custom path: use it directly
|
|
78
|
+
base_dir = custom_path
|
|
79
|
+
else
|
|
80
|
+
# Default: add shows subdirectory
|
|
81
|
+
base_dir = File.join(Dir.pwd, "shows")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
FileUtils.mkdir_p(base_dir)
|
|
85
|
+
|
|
86
|
+
show_dir = File.join(base_dir, show.name)
|
|
73
87
|
FileUtils.mkdir_p(show_dir)
|
|
74
88
|
|
|
75
|
-
|
|
89
|
+
show_dir
|
|
76
90
|
rescue => e
|
|
77
|
-
|
|
91
|
+
@logger.error "❌ Directory creation failed: #{e.message}"
|
|
78
92
|
end
|
|
79
|
-
end
|
|
93
|
+
end
|
data/lib/deadlist/models/show.rb
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# Object to handle Show data and the array of Track objects to be used in downloading.
|
|
2
2
|
class Show
|
|
3
|
+
AUDIO_FORMATS = %w[mp3 flac ogg m4a].freeze
|
|
4
|
+
|
|
3
5
|
attr_reader :name, :venue, :date, :location, :duration, :transferred_by, :tracks, :available_formats
|
|
4
6
|
|
|
5
|
-
def initialize(show_id, format)
|
|
7
|
+
def initialize(show_id, format, logger: Logger.new($stdout))
|
|
8
|
+
@logger = logger
|
|
6
9
|
@show_id = show_id
|
|
7
10
|
@format = format
|
|
8
11
|
@name = nil
|
|
@@ -19,14 +22,21 @@ class Show
|
|
|
19
22
|
|
|
20
23
|
# Initializes a Downloader and passes track details
|
|
21
24
|
def download_tracks(path)
|
|
22
|
-
dl = Downloader.new(path, @format)
|
|
25
|
+
dl = Downloader.new(path, @format, logger: @logger)
|
|
23
26
|
download_url = dl.download_url_for_show(@show_id)
|
|
27
|
+
succesful_downloads = 0
|
|
24
28
|
|
|
25
29
|
@tracks.each do |track|
|
|
26
|
-
|
|
30
|
+
@logger.info "⬇️ Downloading #{track.pos} - #{track.title}..."
|
|
31
|
+
|
|
32
|
+
if dl.get(download_url, track)
|
|
33
|
+
succesful_downloads += 1
|
|
34
|
+
end
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
@logger.info "⚡️ #{track.pos} - #{track.title} downloaded successfully"
|
|
29
37
|
end
|
|
38
|
+
|
|
39
|
+
@logger.info "✅ Downloaded #{succesful_downloads}/#{@tracks.length} tracks successfully!"
|
|
30
40
|
end
|
|
31
41
|
|
|
32
42
|
private
|
|
@@ -42,9 +52,9 @@ class Show
|
|
|
42
52
|
@transferred_by = show_data[:transferred_by]
|
|
43
53
|
@name = "#{show_data[:date]} - #{show_data[:venue]} - #{show_data[:location]}"
|
|
44
54
|
@tracks = set_tracks(show_data[:files])
|
|
45
|
-
@url = "https://archive.org/metadata
|
|
55
|
+
@url = "https://archive.org/metadata/#{show_data[:dir]}/"
|
|
46
56
|
|
|
47
|
-
|
|
57
|
+
@logger.info "🌹💀 Show #{name} found!"
|
|
48
58
|
end
|
|
49
59
|
|
|
50
60
|
# Converts track lists to Track objects
|
|
@@ -53,20 +63,18 @@ class Show
|
|
|
53
63
|
.select { |file| matches_format?(file, @format) }
|
|
54
64
|
|
|
55
65
|
if audio_files.empty?
|
|
56
|
-
|
|
66
|
+
@logger.error "❌ No #{@format} files found"
|
|
57
67
|
return []
|
|
58
68
|
end
|
|
59
69
|
|
|
60
|
-
@tracks = audio_files.map { |track| Track.new(track) }
|
|
70
|
+
@tracks = audio_files.map.with_index(1) { |track, index| Track.new(track, index) }
|
|
61
71
|
end
|
|
62
72
|
|
|
63
|
-
private
|
|
64
|
-
|
|
65
73
|
def audio_file?(file)
|
|
66
|
-
|
|
74
|
+
AUDIO_FORMATS.include?(File.extname(file["name"]).delete('.').downcase)
|
|
67
75
|
end
|
|
68
76
|
|
|
69
77
|
def matches_format?(file, format)
|
|
70
|
-
File.extname(file["name"]).delete('.') == format
|
|
78
|
+
File.extname(file["name"]).delete('.').downcase == format
|
|
71
79
|
end
|
|
72
80
|
end
|
data/lib/deadlist.rb
CHANGED
|
@@ -1,34 +1,63 @@
|
|
|
1
1
|
require 'httparty'
|
|
2
|
-
require 'nokogiri'
|
|
3
2
|
require 'open-uri'
|
|
4
3
|
require 'pry'
|
|
4
|
+
require 'logger'
|
|
5
5
|
|
|
6
|
-
require_relative 'deadlist/
|
|
6
|
+
require_relative 'deadlist/version'
|
|
7
|
+
require_relative 'deadlist/cli'
|
|
7
8
|
|
|
8
9
|
# Main DeadList class.
|
|
9
10
|
class DeadList
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
attr_reader :current_version
|
|
12
|
+
|
|
13
|
+
def initialize(logger: Logger.new($stdout))
|
|
14
|
+
@logger = logger
|
|
15
|
+
@logger.level = Logger::INFO # Default level
|
|
16
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
|
17
|
+
"#{msg}\n"
|
|
18
|
+
end
|
|
19
|
+
@current_version = VERSION
|
|
12
20
|
end
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
def run(argv = ARGV)
|
|
23
|
+
# Parse arguments to get show IDs and options
|
|
24
|
+
parsed_args = ArgumentParser.parse(argv, @current_version)
|
|
25
|
+
show_ids = parsed_args[:ids]
|
|
26
|
+
|
|
27
|
+
# Check for --quiet flag and adjust logger level
|
|
28
|
+
if parsed_args[:quiet]
|
|
29
|
+
@logger.level = Logger::ERROR
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Process each show
|
|
33
|
+
show_ids.each_with_index do |show_id, index|
|
|
34
|
+
@logger.info "📻 Processing show #{index + 1}/#{show_ids.count}: #{show_id}"
|
|
35
|
+
|
|
36
|
+
# Build arguments for this specific show
|
|
37
|
+
show_argv = ['--id', show_id, '--format', parsed_args[:format]]
|
|
38
|
+
show_argv += ['--directory', parsed_args[:directory]] if parsed_args[:directory]
|
|
39
|
+
show_argv += ['--quiet'] if parsed_args[:quiet]
|
|
40
|
+
show_argv += ['--dry-run'] if parsed_args[:dry_run]
|
|
15
41
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# In future this could be abstracted to pass the show link vs all args, so a 'session' is started per show.
|
|
19
|
-
session = CLI.new(@current_version, ARGV)
|
|
42
|
+
# Create CLI session for this show
|
|
43
|
+
session = CLI.new(@current_version, show_argv, logger: @logger)
|
|
20
44
|
|
|
21
45
|
# Scrape links and metadata for given show
|
|
22
46
|
session.create_show
|
|
23
47
|
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
48
|
+
# Check if show has tracks in requested format
|
|
49
|
+
if session.show && session.show.tracks.empty?
|
|
50
|
+
@logger.error "❌ #{show_id} not available in #{parsed_args[:format]} format"
|
|
51
|
+
if session.show.available_formats && !session.show.available_formats.empty?
|
|
52
|
+
@logger.error " Available formats: #{session.show.available_formats.join(', ')}"
|
|
53
|
+
end
|
|
54
|
+
@logger.error " Skipping..."
|
|
55
|
+
next # Skip to next show
|
|
56
|
+
end
|
|
29
57
|
|
|
30
|
-
# Create folder
|
|
58
|
+
# Create folder and begin track downloads
|
|
31
59
|
session.download_show
|
|
60
|
+
end
|
|
32
61
|
end
|
|
33
62
|
end
|
|
34
63
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: deadlist
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- nazwr
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-12-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: httparty
|
|
@@ -24,20 +24,6 @@ dependencies:
|
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0.21'
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: nokogiri
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - "~>"
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '1.10'
|
|
34
|
-
type: :runtime
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - "~>"
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '1.10'
|
|
41
27
|
description: A Ruby gem for downloading Grateful Dead concert recordings from the
|
|
42
28
|
Internet Archive
|
|
43
29
|
email: nathan@azotiwright.com
|
|
@@ -54,6 +40,7 @@ files:
|
|
|
54
40
|
- lib/deadlist/cli/downloader.rb
|
|
55
41
|
- lib/deadlist/models/show.rb
|
|
56
42
|
- lib/deadlist/models/track.rb
|
|
43
|
+
- lib/deadlist/version.rb
|
|
57
44
|
homepage: https://github.com/nazwr/deadlist
|
|
58
45
|
licenses:
|
|
59
46
|
- MIT
|