Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redo indexing mechanism #16

Merged
merged 3 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 215 additions & 0 deletions .credo.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# This file contains the configuration for Credo and you are probably reading
# this after creating it with `mix credo.gen.config`.
#
# If you find anything wrong or unclear in this file, please report an
# issue on GitHub: https://github.com/rrrene/credo/issues
#
%{
#
# You can have as many configs as you like in the `configs:` field.
configs: [
%{
#
# Run any config using `mix credo -C <name>`. If no config name is given
# "default" is used.
#
name: "default",
#
# These are the files included in the analysis:
files: %{
#
# You can give explicit globs or simply directories.
# In the latter case `**/*.{ex,exs}` will be used.
#
included: [
"lib/",
"src/",
"test/",
"web/",
"apps/*/lib/",
"apps/*/src/",
"apps/*/test/",
"apps/*/web/"
],
excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"]
},
#
# Load and configure plugins here:
#
plugins: [],
#
# If you create your own checks, you must specify the source files for
# them here, so they can be loaded by Credo before running the analysis.
#
requires: [],
#
# If you want to enforce a style guide and need a more traditional linting
# experience, you can change `strict` to `true` below:
#
strict: false,
#
# To modify the timeout for parsing files, change this value:
#
parse_timeout: 5000,
#
# If you want to use uncolored output by default, you can change `color`
# to `false` below:
#
color: true,
#
# You can customize the parameters of any check by adding a second element
# to the tuple.
#
# To disable a check put `false` as second element:
#
# {Credo.Check.Design.DuplicatedCode, false}
#
checks: %{
enabled: [
#
## Consistency Checks
#
{Credo.Check.Consistency.ExceptionNames, []},
{Credo.Check.Consistency.LineEndings, []},
{Credo.Check.Consistency.ParameterPatternMatching, []},
{Credo.Check.Consistency.SpaceAroundOperators, []},
{Credo.Check.Consistency.SpaceInParentheses, []},
{Credo.Check.Consistency.TabsOrSpaces, []},

#
## Design Checks
#
# You can customize the priority of any check
# Priority values are: `low, normal, high, higher`
#
{Credo.Check.Design.AliasUsage, [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]},
{Credo.Check.Design.TagFIXME, []},
# You can also customize the exit_status of each check.
# If you don't want TODO comments to cause `mix credo` to fail, just
# set this value to 0 (zero).
#
{Credo.Check.Design.TagTODO, [exit_status: 2]},

#
## Readability Checks
#
{Credo.Check.Readability.AliasOrder, []},
{Credo.Check.Readability.FunctionNames, []},
{Credo.Check.Readability.LargeNumbers, []},
{Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},
{Credo.Check.Readability.ModuleAttributeNames, []},
{Credo.Check.Readability.ModuleDoc, []},
{Credo.Check.Readability.ModuleNames, []},
{Credo.Check.Readability.ParenthesesInCondition, []},
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
{Credo.Check.Readability.PipeIntoAnonymousFunctions, []},
{Credo.Check.Readability.PredicateFunctionNames, []},
{Credo.Check.Readability.PreferImplicitTry, []},
{Credo.Check.Readability.RedundantBlankLines, []},
{Credo.Check.Readability.Semicolons, []},
{Credo.Check.Readability.SpaceAfterCommas, []},
{Credo.Check.Readability.StringSigils, []},
{Credo.Check.Readability.TrailingBlankLine, []},
{Credo.Check.Readability.TrailingWhiteSpace, []},
{Credo.Check.Readability.UnnecessaryAliasExpansion, []},
{Credo.Check.Readability.VariableNames, []},
{Credo.Check.Readability.WithSingleClause, []},

#
## Refactoring Opportunities
#
{Credo.Check.Refactor.Apply, []},
{Credo.Check.Refactor.CondStatements, []},
{Credo.Check.Refactor.FilterCount, []},
{Credo.Check.Refactor.FilterFilter, []},
{Credo.Check.Refactor.FunctionArity, []},
{Credo.Check.Refactor.LongQuoteBlocks, []},
{Credo.Check.Refactor.MapJoin, []},
{Credo.Check.Refactor.MatchInCondition, []},
{Credo.Check.Refactor.NegatedConditionsInUnless, []},
{Credo.Check.Refactor.NegatedConditionsWithElse, []},
{Credo.Check.Refactor.Nesting, []},
{Credo.Check.Refactor.RedundantWithClauseResult, []},
{Credo.Check.Refactor.RejectReject, []},
{Credo.Check.Refactor.UnlessWithElse, []},
{Credo.Check.Refactor.WithClauses, []},

#
## Warnings
#
{Credo.Check.Warning.ApplicationConfigInModuleAttribute, []},
{Credo.Check.Warning.BoolOperationOnSameValues, []},
{Credo.Check.Warning.Dbg, []},
{Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},
{Credo.Check.Warning.IExPry, []},
{Credo.Check.Warning.IoInspect, []},
{Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []},
{Credo.Check.Warning.OperationOnSameValues, []},
{Credo.Check.Warning.OperationWithConstantResult, []},
{Credo.Check.Warning.RaiseInsideRescue, []},
{Credo.Check.Warning.SpecWithStruct, []},
{Credo.Check.Warning.UnsafeExec, []},
{Credo.Check.Warning.UnusedEnumOperation, []},
{Credo.Check.Warning.UnusedFileOperation, []},
{Credo.Check.Warning.UnusedKeywordOperation, []},
{Credo.Check.Warning.UnusedListOperation, []},
{Credo.Check.Warning.UnusedPathOperation, []},
{Credo.Check.Warning.UnusedRegexOperation, []},
{Credo.Check.Warning.UnusedStringOperation, []},
{Credo.Check.Warning.UnusedTupleOperation, []},
{Credo.Check.Warning.WrongTestFileExtension, []}
],
disabled: [
#
# Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`)

#
# Controversial and experimental checks (opt-in, just move the check to `:enabled`
# and be sure to use `mix credo --strict` to see low priority checks)
#
{Credo.Check.Refactor.CyclomaticComplexity, []},
{Credo.Check.Consistency.MultiAliasImportRequireUse, []},
{Credo.Check.Consistency.UnusedVariableNames, []},
{Credo.Check.Design.DuplicatedCode, []},
{Credo.Check.Design.SkipTestWithoutComment, []},
{Credo.Check.Readability.AliasAs, []},
{Credo.Check.Readability.BlockPipe, []},
{Credo.Check.Readability.ImplTrue, []},
{Credo.Check.Readability.MultiAlias, []},
{Credo.Check.Readability.NestedFunctionCalls, []},
{Credo.Check.Readability.OneArityFunctionInPipe, []},
{Credo.Check.Readability.OnePipePerLine, []},
{Credo.Check.Readability.SeparateAliasRequire, []},
{Credo.Check.Readability.SingleFunctionToBlockPipe, []},
{Credo.Check.Readability.SinglePipe, []},
{Credo.Check.Readability.Specs, []},
{Credo.Check.Readability.StrictModuleLayout, []},
{Credo.Check.Readability.WithCustomTaggedTuple, []},
{Credo.Check.Refactor.ABCSize, []},
{Credo.Check.Refactor.AppendSingleItem, []},
{Credo.Check.Refactor.DoubleBooleanNegation, []},
{Credo.Check.Refactor.FilterReject, []},
{Credo.Check.Refactor.IoPuts, []},
{Credo.Check.Refactor.MapMap, []},
{Credo.Check.Refactor.ModuleDependencies, []},
{Credo.Check.Refactor.NegatedIsNil, []},
{Credo.Check.Refactor.PassAsyncInTestCases, []},
{Credo.Check.Refactor.PipeChainStart, []},
{Credo.Check.Refactor.RejectFilter, []},
{Credo.Check.Refactor.VariableRebinding, []},
{Credo.Check.Warning.LazyLogging, []},
{Credo.Check.Warning.LeakyEnvironment, []},
{Credo.Check.Warning.MapGetUnsafePass, []},
{Credo.Check.Warning.MixEnv, []},
{Credo.Check.Warning.UnsafeToAtom, []}

# {Credo.Check.Refactor.MapInto, []},

#
# Custom checks can be created using `mix credo.gen.check`.
#
]
}
}
]
}
2 changes: 1 addition & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
subdirectories: ["priv/*/migrations"],
plugins: [Phoenix.LiveView.HTMLFormatter],
inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"],
line_length: 100
line_length: 120
]
2 changes: 1 addition & 1 deletion .iex.exs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ defmodule IexHelpers do
:channel -> channel_url()
end

SourceDetails.get_video_ids(source)
SourceDetails.get_media_attributes(source)
end
end

Expand Down
61 changes: 56 additions & 5 deletions lib/pinchflat/media.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,31 @@ defmodule Pinchflat.Media do

@doc """
Returns a list of pending media_items for a given source, where
pending means the `media_filepath` is `nil`.
pending means the `media_filepath` is `nil` AND the media_item
matches the format selection rules of the parent media_profile.

See `build_format_clauses` but tl;dr is it _may_ filter based
on shorts or livestreams depending on the media_profile settings.

Returns [%MediaItem{}, ...].
"""
def list_pending_media_items_for(%Source{} = source) do
from(
m in MediaItem,
where: m.source_id == ^source.id and is_nil(m.media_filepath)
)
media_profile = Repo.preload(source, :media_profile).media_profile

MediaItem
|> where([mi], mi.source_id == ^source.id and is_nil(mi.media_filepath))
|> where(^build_format_clauses(media_profile))
|> Repo.all()
end

@doc """
Returns a list of downloaded media_items for a given source.

Returns [%MediaItem{}, ...].
"""
def list_downloaded_media_items_for(%Source{} = source) do
MediaItem
|> where([mi], mi.source_id == ^source.id and not is_nil(mi.media_filepath))
|> Repo.all()
end

Expand Down Expand Up @@ -72,4 +88,39 @@ defmodule Pinchflat.Media do
def change_media_item(%MediaItem{} = media_item, attrs \\ %{}) do
MediaItem.changeset(media_item, attrs)
end

defp build_format_clauses(media_profile) do
mapped_struct = Map.from_struct(media_profile)

Enum.reduce(mapped_struct, dynamic(true), fn attr, dynamic ->
case {attr, media_profile} do
{{:shorts_behaviour, :only}, %{livestream_behaviour: :only}} ->
dynamic([mi], ^dynamic and (mi.livestream == true or fragment("? ILIKE ?", mi.original_url, "%/shorts/%")))

# Technically redundant, but makes the other clauses easier to parse
# (redundant because this condition is the same as the condition above, just flipped)
{{:livestream_behaviour, :only}, %{shorts_behaviour: :only}} ->
dynamic

{{:shorts_behaviour, :only}, _} ->
# return records with /shorts/ in the original_url
dynamic([mi], ^dynamic and fragment("? ILIKE ?", mi.original_url, "%/shorts/%"))

{{:livestream_behaviour, :only}, _} ->
# return records with livestream: true
dynamic([mi], ^dynamic and mi.livestream == true)

{{:shorts_behaviour, :exclude}, %{livestream_behaviour: lb}} when lb != :only ->
# return records without /shorts/ in the original_url
dynamic([mi], ^dynamic and fragment("? NOT ILIKE ?", mi.original_url, "%/shorts/%"))

{{:livestream_behaviour, :exclude}, %{shorts_behaviour: sb}} when sb != :only ->
# return records with livestream: false
dynamic([mi], ^dynamic and mi.livestream == false)

_ ->
dynamic
end
end)
end
end
6 changes: 5 additions & 1 deletion lib/pinchflat/media/media_item.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ defmodule Pinchflat.Media.MediaItem do
@allowed_fields ~w(
title
media_id
original_url
livestream
media_filepath
source_id
subtitle_filepaths
thumbnail_filepath
metadata_filepath
)a
@required_fields ~w(media_id source_id)a
@required_fields ~w(title original_url livestream media_id source_id)a

schema "media_items" do
field :title, :string
field :media_id, :string
field :original_url, :string
field :livestream, :boolean, default: false
field :media_filepath, :string
field :thumbnail_filepath, :string
field :metadata_filepath, :string
Expand Down
20 changes: 14 additions & 6 deletions lib/pinchflat/media_client/backends/yt_dlp/video_collection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollection do
videos (aka: a source [ie: channels, playlists]).
"""

alias Pinchflat.Utils.FunctionUtils

@doc """
Returns a list of strings representing the video ids in the collection.
Returns a list of maps representing the videos in the collection.

Returns {:ok, [binary()]} | {:error, any, ...}.
Returns {:ok, [map()]} | {:error, any, ...}.
"""
def get_video_ids(url, command_opts \\ []) do
def get_media_attributes(url, command_opts \\ []) do
runner = Application.get_env(:pinchflat, :yt_dlp_runner)
opts = command_opts ++ [:simulate, :skip_download]

case runner.run(url, opts, "%(id)s") do
{:ok, output} -> {:ok, String.split(output, "\n", trim: true)}
res -> res
case runner.run(url, opts, "%(.{id,title,was_live,original_url})j") do
{:ok, output} ->
output
|> String.split("\n", trim: true)
|> Enum.map(&Phoenix.json_library().decode!/1)
|> FunctionUtils.wrap_ok()

res ->
res
end
end

Expand Down
Loading
Loading