diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3da0a2df..852c5f35 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 1000` -# on 2024-07-14 00:55:29 UTC using RuboCop version 1.65.0. +# on 2024-07-15 20:10:17 UTC using RuboCop version 1.65.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -27,21 +27,25 @@ Lint/UnreachableCode: Exclude: - 'lib/tasks/upload.rake' -# Offense count: 8 +# Offense count: 11 # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max. Metrics/AbcSize: Exclude: - 'app/listeners/mkv_progress_listener.rb' - 'app/models/video_blob.rb' + - 'app/services/create_disks_service.rb' + - 'app/services/create_mkv_service.rb' - 'app/services/eject_disk_service.rb' - 'app/workers/rip_worker.rb' - 'app/workers/scan_plex_worker.rb' -# Offense count: 1 +# Offense count: 3 # Configuration parameters: CountComments, Max, CountAsOne. Metrics/ClassLength: Exclude: - 'app/listeners/mkv_progress_listener.rb' + - 'app/models/video_blob.rb' + - 'app/workers/scan_plex_worker.rb' # Offense count: 3 # Configuration parameters: AllowedMethods, AllowedPatterns, Max. @@ -50,11 +54,13 @@ Metrics/CyclomaticComplexity: - 'app/controllers/start_controller.rb' - 'app/models/video_blob.rb' -# Offense count: 5 +# Offense count: 9 # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: Exclude: - 'app/models/video_blob.rb' + - 'app/services/create_disks_service.rb' + - 'app/services/create_mkv_service.rb' - 'app/workers/scan_plex_worker.rb' # Offense count: 3 @@ -84,7 +90,7 @@ RSpec/MultipleMemoizedHelpers: Exclude: - 'spec/workers/rip_worker_spec.rb' -# Offense count: 11 +# Offense count: 12 Rails/I18nLocaleTexts: Exclude: - 'app/controllers/config/make_mkvs_controller.rb' diff --git a/app/components/movie_title_table_component.html.erb b/app/components/movie_title_table_component.html.erb index 5e77727d..42d4bce2 100644 --- a/app/components/movie_title_table_component.html.erb +++ b/app/components/movie_title_table_component.html.erb @@ -1,22 +1,46 @@ <% if disks.any? %> -
# | -Name(video title) | -Duration (<%= distance_of_time_in_words @movie.movie_runtime.seconds %>) | -Size | -- |
---|
# | +Ripped | +Uploaded | +Name(video title) | +Duration (<%= distance_of_time_in_words @movie.movie_runtime.seconds %>) | +Size | ++ | ||
---|---|---|---|---|---|---|---|---|
<%= disk_title.id %> | +<%= disk_title.id %> | +
+ + <% if movie.ripped_disk_titles.any? %> + <%= icon('square-check') %> + <% else %> + <%= icon('square') %> + <% end %> ++ |
+
+ + <% if disk_title.video_blob&.uploaded? %> + <%= icon('square-check') %> + <% else %> + <%= icon('square') %> + <% end %> ++ |
+
<%= disk_title.name %> <% if disk_title.video %> @@ -24,20 +48,24 @@ <% end %> | <%= distance_of_time_in_words(disk_title.duration.seconds) %> | -+ | <%= number_to_human_size(disk_title.size, precision: 3) %> <% if disk_title.size >= free_disk_space %> - WARNING: Not enough space available to rip need another <%= number_to_human_size(disk_title.size - free_disk_space, precision: 3) %> + WARNING: There Might Not enough space available to rip, needs another <%= number_to_human_size(disk_title.size - free_disk_space, precision: 3) %> <% end %> | - <%= link_to 'Rip', rip_movie_path(movie, disk_title_id: disk_title.id), data: { turbo_method: :post }, class: 'btn btn-outline-light' %> + + <%= select_tag "movies[][extra_type]", options_for_select(VideoBlob.extra_types.keys), prompt: "Extra Type" %> |
Stand by the disk is still being loading from the CD drive this could take a while. Page will update once the disk is ready so no need to refresh the page but if you do it won't hurt anything.
<% else %> diff --git a/app/components/movie_title_table_component.rb b/app/components/movie_title_table_component.rb index 7cb901ff..59e7aeb1 100644 --- a/app/components/movie_title_table_component.rb +++ b/app/components/movie_title_table_component.rb @@ -2,6 +2,7 @@ class MovieTitleTableComponent < ViewComponent::Base extend Dry::Initializer + include IconHelper option :disks, Types::Coercible::Array.of(Types.Instance(Disk)) option :movie, Types.Instance(Movie) diff --git a/app/controllers/config/make_mkvs_controller.rb b/app/controllers/config/make_mkvs_controller.rb index 1c8bd2dd..9ab7b6f2 100644 --- a/app/controllers/config/make_mkvs_controller.rb +++ b/app/controllers/config/make_mkvs_controller.rb @@ -14,15 +14,21 @@ def create @config_make_mkv = Config::MakeMkv.new(make_mkv_params) if @config_make_mkv.save - redirect_to root_path, notice: 'Make MKV Config was successfully created.' + redirect_to root_path, success: 'Make MKV Config was successfully created.' else + flash.now[:error] = 'Could not create MKV Config' render :new end end def update - flash[:success] = 'Updated Make MKV' if @config_make_mkv.update(make_mkv_params) - redirect_to root_path + if @config_make_mkv.update(make_mkv_params) + flash[:success] = 'Updated Make MKV' + redirect_to root_path + else + flash.now[:error] = 'Could not update MKV Config' + render :edit + end end def install @@ -42,7 +48,7 @@ def set_make_mkv end def make_mkv_params - params.require(:config_slack).permit(:settings_makemkvcon_path, :settings_registration_key) + params.require(:config_make_mkv).permit(:settings_makemkvcon_path, :settings_registration_key) end end end diff --git a/app/controllers/config/plexes_controller.rb b/app/controllers/config/plexes_controller.rb index 04933e65..39fad224 100644 --- a/app/controllers/config/plexes_controller.rb +++ b/app/controllers/config/plexes_controller.rb @@ -22,6 +22,7 @@ def create @config_plex = Config::Plex.new(config_plex_params) if @config_plex.save + ScanPlexWorker.perform_async redirect_to root_path, notice: 'Plex was successfully created.' else render :new @@ -30,6 +31,7 @@ def create def update if @config_plex.update(config_plex_params) + ScanPlexWorker.perform_async redirect_to root_path, notice: 'Plex was successfully updated.' else render :edit diff --git a/app/controllers/movies_controller.rb b/app/controllers/movies_controller.rb index 313aab79..fb92c04d 100644 --- a/app/controllers/movies_controller.rb +++ b/app/controllers/movies_controller.rb @@ -16,9 +16,29 @@ def show # rip_movie POST /movies/:id/rip(.:format) def rip movie = Movie.find(params[:id]) - disk_title = DiskTitle.find(params[:disk_title_id]) - disk_title.update!(video: movie) - job = RipWorker.perform_async(disk_id: disk_title.disk.id, disk_title_ids: [disk_title.id]) + disk = Disk.find(params[:disk_id]) + disk_titles = rip_disk_titles(disk, movie) + job = RipWorker.perform_async( + disk_id: disk.id, + disk_title_ids: disk_titles.map(&:id), + extra_types: movies_params.pluck(:extra_type).compact_blank + ) redirect_to job_path(job) end + + private + + def rip_disk_titles(disk, movie) + movies_params.filter_map do |movie_params| + next if movie_params[:extra_type].blank? + + disk_title = disk.disk_titles.find(movie_params[:disk_title_id]) + disk_title.update!(video: movie) + disk_title + end + end + + def movies_params + params.required(:movies) + end end diff --git a/app/listeners/the_movie_db/video_listener.rb b/app/listeners/the_movie_db/video_listener.rb index db54aa1d..878eea6d 100644 --- a/app/listeners/the_movie_db/video_listener.rb +++ b/app/listeners/the_movie_db/video_listener.rb @@ -2,11 +2,11 @@ module TheMovieDb class VideoListener - def tv_saving(tv) + def tv_validating(tv) TheMovieDb::TvUpdateService.call(tv) end - def movie_saving(movie) + def movie_validating(movie) TheMovieDb::MovieUpdateService.call(movie) end end diff --git a/app/listeners/upload_progress_listener.rb b/app/listeners/upload_progress_listener.rb index 072661bc..c08ba4c9 100644 --- a/app/listeners/upload_progress_listener.rb +++ b/app/listeners/upload_progress_listener.rb @@ -7,7 +7,7 @@ class UploadProgressListener delegate :render, to: :ApplicationController - option :disk_title, Types.Instance(::DiskTitle) + option :video_blob, Types.Instance(::VideoBlob) option :file_size, Types::Integer attr_reader :completed @@ -63,13 +63,13 @@ def next_update end def title - if disk_title.video.is_a?(Tv) - episode = disk_title.episode + if video_blob.video.is_a?(Tv) + episode = video_blob.episode season = episode.season - "#{disk_title.video.title} - S#{season.season_number}E#{episode.episode_number} " \ + "#{video_blob.video.title} - S#{season.season_number}E#{episode.episode_number} " \ "#{episode.name}" else - disk_title.video.title + video_blob.video.title end end end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 71fbba5b..e2edc4dc 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -2,4 +2,10 @@ class ApplicationRecord < ActiveRecord::Base self.abstract_class = true + + def unmark_for_destruction + return unless instance_variable_defined?(:@marked_for_destruction) + + remove_instance_variable(:@marked_for_destruction) + end end diff --git a/app/models/config/slack.rb b/app/models/config/slack.rb index 6fd43c81..52583ab9 100644 --- a/app/models/config/slack.rb +++ b/app/models/config/slack.rb @@ -1,5 +1,15 @@ # frozen_string_literal: true +# == Schema Information +# +# Table name: configs +# +# id :integer not null, primary key +# settings :text +# type :string default("Config"), not null +# created_at :datetime not null +# updated_at :datetime not null +# class Config class Slack < Config setting do |s| diff --git a/app/models/disk_title.rb b/app/models/disk_title.rb index b9d0edbb..48e052fa 100644 --- a/app/models/disk_title.rb +++ b/app/models/disk_title.rb @@ -15,6 +15,7 @@ # episode_id :integer # mkv_progress_id :bigint # title_id :integer not null +# video_blob_id :integer # video_id :integer # # Indexes @@ -23,6 +24,7 @@ # index_disk_titles_on_episode_id (episode_id) # index_disk_titles_on_mkv_progress_id (mkv_progress_id) # index_disk_titles_on_video (video_id) +# index_disk_titles_on_video_blob_id (video_blob_id) # class DiskTitle < ApplicationRecord include ActionView::Helpers::DateHelper @@ -31,6 +33,8 @@ class DiskTitle < ApplicationRecord belongs_to :episode, optional: true belongs_to :disk, optional: true + belongs_to :video_blob, optional: true + scope :not_ripped, -> { where(ripped_at: nil) } scope :ripped, -> { where.not(ripped_at: nil) } @@ -41,37 +45,4 @@ def duration def to_label "##{title_id} #{name} #{distance_of_time_in_words(duration)}" end - - def tmp_plex_path - require_movie_or_episode! - video.is_a?(Tv) ? episode.tmp_plex_path : video.tmp_plex_path - end - - def plex_path - require_movie_or_episode! - video.is_a?(Tv) ? episode.plex_path : video.plex_path - end - - def plex_name - require_movie_or_episode! - video.is_a?(Tv) ? episode.plex_name : video.plex_name - end - - def tmp_plex_dir - require_movie_or_episode! - video.is_a?(Tv) ? episode.tmp_plex_dir : video.tmp_plex_dir - end - - def tmp_plex_path_exists? - return false if video.nil? - - video.is_a?(Tv) ? episode.tmp_plex_path_exists? : video.tmp_plex_path_exists? - end - - def require_movie_or_episode! - return if video.is_a?(Movie) - return if video.is_a?(Tv) && episode - - raise 'requires episode or movie to rip' - end end diff --git a/app/models/episode.rb b/app/models/episode.rb index bf298be2..c92aa73b 100644 --- a/app/models/episode.rb +++ b/app/models/episode.rb @@ -31,6 +31,7 @@ class Episode < ApplicationRecord scope :order_by_episode_number, -> { order(:episode_number) } delegate :tv, to: :season + delegate :title, :plex_name, to: :tv, prefix: true def runtime @runtime ||= super&.minutes @@ -42,63 +43,11 @@ def runtime_range @runtime_range ||= (runtime - 3.minutes)...(runtime + 3.minutes) end - def plex_path - raise 'plex config is missing and is required' unless Config::Plex.any? - - @plex_path ||= Pathname.new( - "#{Config::Plex.newest.settings_tv_path}/#{tv_plex_name}/#{season_name}/#{plex_name}" - ) - end - - def tmp_plex_dir - @tmp_plex_dir ||= Rails.root.join("tmp/tv/#{tv_plex_name}/#{season_name}") - end - - def tmp_plex_path - @tmp_plex_path ||= tmp_plex_dir.join(plex_name) - end - def plex_name - "#{episode_plex_name} - s#{format_season_number}e#{format_episode_number} - #{name}.mkv" - end - - def episode_first_air_date - season.tv.episode_first_air_date - end - - def title - season.tv.title - end - - def season_name - "Season #{format_season_number}" - end - - def format_season_number - format('%02d', season.season_number) + [tv.plex_name, "s#{season.format_season_number}e#{format_episode_number}", name].compact_blank.join(' - ') end def format_episode_number format('%02d', episode_number) end - - def episode_plex_name - if episode_first_air_date - "#{title} (#{episode_first_air_date.year})" - else - title - end - end - - def tv_plex_name - @tv_plex_name ||= if episode_first_air_date - "#{title} (#{episode_first_air_date.year})" - else - title - end - end - - def tmp_plex_path_exists? - File.exist?(tmp_plex_path) - end end diff --git a/app/models/movie.rb b/app/models/movie.rb index e2ad0eec..9708d192 100644 --- a/app/models/movie.rb +++ b/app/models/movie.rb @@ -15,7 +15,6 @@ # poster_path :string # rating :integer default("N/A"), not null # release_date :date -# synced_on :datetime # title :string # type :string # created_at :datetime not null @@ -27,6 +26,7 @@ # index_videos_on_type_and_the_movie_db_id (type,the_movie_db_id) UNIQUE # class Movie < Video + before_validation { broadcast(:movie_validating, self) } before_save { broadcast(:movie_saving, self) } after_commit { broadcast(:movie_saved, self) } @@ -40,32 +40,4 @@ class Movie < Video def runtime_range (movie_runtime - MOVIE_RUNNTIME_MARGIN)..(movie_runtime + MOVIE_RUNNTIME_MARGIN) end - - def plex_path - raise 'plex config is missing and is required' unless Config::Plex.any? - - @plex_path ||= Pathname.new("#{Config::Plex.newest.settings_movie_path}/#{plex_name}/#{plex_name}.mkv") - end - - def tmp_plex_dir - @tmp_plex_dir ||= Rails.root.join("tmp/movies/#{plex_name}") - end - - def tmp_plex_path - @tmp_plex_path ||= tmp_plex_dir.join("#{plex_name}.mkv") - end - - def plex_name - @plex_name ||= (release_date ? "#{title} (#{release_date.year})" : title) - end - - def update_maxlength(max) - return config.maxlength = (max + MOVIE_DURATION_MARGIN) if max.to_i > (config.minlength / 60) - - config.maxlength = nil - end - - def tmp_plex_path_exists? - File.exist?(tmp_plex_path) - end end diff --git a/app/models/season.rb b/app/models/season.rb index 377647d5..06688aae 100644 --- a/app/models/season.rb +++ b/app/models/season.rb @@ -33,4 +33,12 @@ class Season < ApplicationRecord before_save { broadcast(:season_saving, self) } after_commit { broadcast(:season_saved, id, async: true) } + + def season_name + "Season #{format_season_number}" + end + + def format_season_number + format('%02d', season_number) + end end diff --git a/app/models/tv.rb b/app/models/tv.rb index afa46e39..8abcaa84 100644 --- a/app/models/tv.rb +++ b/app/models/tv.rb @@ -15,7 +15,6 @@ # poster_path :string # rating :integer default("N/A"), not null # release_date :date -# synced_on :datetime # title :string # type :string # created_at :datetime not null @@ -39,6 +38,7 @@ class Tv < Video has_many :seasons, -> { order_by_season_number }, dependent: :destroy, inverse_of: :tv + before_validation { broadcast(:tv_validating, self) } before_save { broadcast(:tv_saving, self) } after_commit { broadcast(:tv_saved, self) } diff --git a/app/models/video.rb b/app/models/video.rb index 1a4acfac..a558696c 100644 --- a/app/models/video.rb +++ b/app/models/video.rb @@ -15,7 +15,6 @@ # poster_path :string # rating :integer default("N/A"), not null # release_date :date -# synced_on :datetime # title :string # type :string # created_at :datetime not null @@ -28,11 +27,11 @@ # class Video < ApplicationRecord include Wisper::Publisher - enum rating: { 'N/A': 0, NR: 1, 'NC-17': 2, R: 3, 'PG-13': 4, PG: 5, G: 6 } has_many :disk_titles, dependent: :nullify - has_many :video_blobs, dependent: :destroy + has_many :ripped_disk_titles, -> { ripped }, class_name: 'DiskTitle', dependent: false, inverse_of: :video + has_many :video_blobs, dependent: :nullify has_many :optimized_video_blobs, lambda { VideoBlob.optimized }, class_name: 'VideoBlob', inverse_of: :video, dependent: :destroy @@ -41,6 +40,16 @@ class Video < ApplicationRecord scope :optimized, -> { includes(:optimized_video_blobs).where.not(video_blobs: { id: nil }) } scope :optimized_with_checksum, -> { optimized.merge(VideoBlob.checksum) } + validates :title, presence: true + + def movie? + is_a?(::Movie) + end + + def tv? + is_a?(::Tv) + end + def credits @credits ||= "TheMovieDb::#{type}::Credits".constantize.new(the_movie_db_id).results end @@ -60,6 +69,14 @@ def ratings end def release_or_air_date - release_date || episode_first_air_date + if is_a?(Movie) + release_date + elsif is_a?(Tv) + episode_first_air_date + end + end + + def plex_name + release_or_air_date ? "#{title} (#{release_or_air_date.year})" : title end end diff --git a/app/models/video_blob.rb b/app/models/video_blob.rb index 5d76e1e9..31217e70 100644 --- a/app/models/video_blob.rb +++ b/app/models/video_blob.rb @@ -4,24 +4,28 @@ # # Table name: video_blobs # -# id :integer not null, primary key -# byte_size :bigint not null -# checksum :text -# content_type :string not null -# filename :string not null -# key :string not null -# metadata :text -# optimized :boolean default(FALSE), not null -# service_name :string not null -# created_at :datetime not null -# updated_at :datetime not null -# episode_id :bigint -# video_id :integer +# id :integer not null, primary key +# byte_size :bigint not null +# checksum :text +# content_type :string not null +# extra_type :integer default("feature_films") +# extra_type_number :integer not null +# filename :string not null +# key :string not null +# metadata :text +# optimized :boolean default(FALSE), not null +# uploaded_on :datetime +# created_at :datetime not null +# updated_at :datetime not null +# episode_id :bigint +# video_id :integer # # Indexes # -# index_video_blobs_on_key_and_service_name (key,service_name) UNIQUE -# index_video_blobs_on_video (video_id) +# idx_on_extra_type_number_video_id_extra_type_1978193db6 (extra_type_number,video_id,extra_type) UNIQUE +# index_video_blobs_on_key (key) UNIQUE +# index_video_blobs_on_key_and_service_name (key) UNIQUE +# index_video_blobs_on_video (video_id) # class VideoBlob < ApplicationRecord Movie = Struct.new(:title, :year) do @@ -34,6 +38,9 @@ def episode end end TvShow = Struct.new(:title, :year, :season, :episode) + EXTRA_TYPES = %i[feature_films behind_the_scenes deleted_scenes featurettes interviews scenes shorts trailers + other].freeze + EXTRA_TYPE_TO_DIRECTORY = EXTRA_TYPES.to_h { [_1, _1.to_s.humanize.titleize] } VIDEO_FORMATS = [ '.avi', '.mp4', '.mkv', '.mov', '.wmv', '.flv', '.webm', '.mpeg', @@ -53,37 +60,136 @@ def episode TV_SHOW_WITHOUT_NUMBER = /#{MATCHER_WITH_YEAR}.*-\s+(?