Skip to content

Commit

Permalink
Add basic audio_image record creation
Browse files Browse the repository at this point in the history
  • Loading branch information
skanderm committed Aug 23, 2024
1 parent e3c1be1 commit 33f31dc
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 45 deletions.
17 changes: 13 additions & 4 deletions server/audio_viz/core/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import librosa
import matplotlib
import os.path
import sys

SpectrogramJob = TypedDict(
"SpectrogramJob",
Expand Down Expand Up @@ -43,13 +44,15 @@ def lambda_handler(event, context):
Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
"""
make_spectrogram(event)
result = make_spectrogram(event)

return {
"statusCode": 200,
"status": 200,
"image_size": result["image_size"],
"sample_rate": result["sample_rate"],
}

def make_spectrogram(job: SpectrogramJob, store_audio=False, show_image=False, store_image=False) -> None:
def make_spectrogram(job: SpectrogramJob, store_audio=False, show_image=False, store_image=False):
local_path = f"/tmp/{job["id"]}"
s3 = boto3.client("s3")

Expand Down Expand Up @@ -80,15 +83,21 @@ def make_spectrogram(job: SpectrogramJob, store_audio=False, show_image=False, s
imshow(spectrogram)

# Store spectrogram image either as a file or in-memory
image_size = None
image_store = f"/tmp/{job["id"]}.png" if store_image else io.BytesIO()
matplotlib.image.imsave(image_store, spectrogram)
if not store_image:
if store_image:
image_size = os.path.getsize(image_store)
else:
image_store.seek(0)
image_size = sys.getsizeof(image_store)

if job["image_key"] is not None and job["image_bucket"] is not None:
image_content = open(image_store, "rb") if store_image else image_store
s3.put_object(Bucket=job["image_bucket"], Key=job["image_key"], Body=image_content)

return {"sample_rate": sample_rate, "image_size": image_size}

def get_audio_metadata(local_path):
result = check_output(['ffprobe',
'-hide_banner', '-loglevel', 'panic',
Expand Down
90 changes: 85 additions & 5 deletions server/lib/orcasite/radio/audio_image.ex
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
defmodule Orcasite.Radio.AudioImage do
require Ash.Resource.Change.Builtins
require Ash.Resource.Change.Builtins
require Ash.Resource.Change.Builtins

use Ash.Resource,
otp_app: :orcasite,
domain: Orcasite.Radio,
extensions: [AshGraphql.Resource],
extensions: [AshGraphql.Resource, AshUUID],
data_layer: AshPostgres.DataLayer

postgres do
table "audio_images"
repo Orcasite.Repo

custom_indexes do
index [:start_time]
index [:end_time]
Expand All @@ -21,10 +26,22 @@ defmodule Orcasite.Radio.AudioImage do
attributes do
uuid_primary_key :id
attribute :image_type, Orcasite.Types.ImageType, public?: true
attribute :status, Orcasite.Types.AudioImageStatus, default: :new, public?: true

attribute :start_time, :utc_datetime_usec, public?: true
attribute :end_time, :utc_datetime_usec, public?: true
attribute :status, Orcasite.Types.AudioImageStatus do
default :new
public? true
allow_nil? false
end

attribute :start_time, :utc_datetime_usec do
public? true
allow_nil? false
end

attribute :end_time, :utc_datetime_usec do
public? true
allow_nil? false
end

attribute :parameters, :map do
public? true
Expand All @@ -36,13 +53,14 @@ defmodule Orcasite.Radio.AudioImage do

attribute :bucket, :string, public?: true
attribute :bucket_region, :string, public?: true
attribute :object_path, :string
attribute :object_path, :string, public?: true

timestamps()
end

relationships do
belongs_to :feed, Orcasite.Radio.Feed do
allow_nil? false
public? true
end

Expand All @@ -58,6 +76,68 @@ defmodule Orcasite.Radio.AudioImage do

actions do
defaults [:read, :destroy, create: :*, update: :*]

create :for_feed_segment do
argument :feed_segment_id, :uuid, allow_nil?: false

argument :image_type, Orcasite.Types.ImageType do
default :spectrogram
end

change set_attribute(:image_type, arg(:image_type))
change set_attribute(:bucket, "dev-audio-viz")
change set_attribute(:bucket_region, "us-west-2")

change before_action(fn change, _context ->
feed_segment_id = Ash.Changeset.get_argument(change, :feed_segment_id)

Orcasite.Radio.FeedSegment
|> Ash.get(feed_segment_id, authorize?: false)
|> case do
{:ok, feed_segment} ->
change
|> Ash.Changeset.change_attributes(%{
start_time: feed_segment.start_time,
end_time: feed_segment.end_time
})
|> Ash.Changeset.manage_relationship(:feed_segments, [feed_segment],
type: :append
)
|> Ash.Changeset.manage_relationship(:feed, feed_segment.feed_id,
type: :append
)

error ->
error
end
end)

change after_action(
fn record, _context ->
feed = record |> Ash.load(:feed) |> Map.get(:feed)

record
|> Ash.Changeset.for_update(:update, %{
object_path: "/#{feed.node_name}/#{record.id}.png"
})
|> Ash.update(authorize?: false)
end,
prepend?: true
)

change after_action(fn record, _context ->
record
|> Ash.Changeset.for_update(:generate_spectrogram)
|> Ash.update(authorize?: false)
end)
end

update :generate_spectrogram do
change set_attribute(:status, :processing)
change fn change, _context ->
change
end
end
end

graphql do
Expand Down
2 changes: 1 addition & 1 deletion server/lib/orcasite/radio/audio_image_feed_segment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Orcasite.Radio.AudioImageFeedSegment do
use Ash.Resource,
otp_app: :orcasite,
domain: Orcasite.Radio,
extensions: [AshGraphql.Resource],
extensions: [AshGraphql.Resource, AshUUID],
data_layer: AshPostgres.DataLayer

postgres do
Expand Down
80 changes: 55 additions & 25 deletions server/lib/orcasite/radio/aws_client.ex
Original file line number Diff line number Diff line change
@@ -1,33 +1,58 @@
defmodule Orcasite.Radio.AwsClient do
alias Orcasite.Radio.{Feed, FeedStream}

@default_results %{count: 0, timestamps: []}

def generate_spectrogram() do
ExAws.Lambda.list_functions()
|> ExAws.request!()
|> Map.get("Functions")
|> Enum.find_value(fn %{"FunctionName" => name} ->
if String.contains?(name, "AudioVizFunction"), do: {:ok, name}
end)
@default_timestamp_results %{count: 0, timestamps: []}

def generate_spectrogram(%{
image_id: image_id,
audio_bucket: audio_bucket,
audio_key: audio_key,
image_bucket: image_bucket,
image_key: image_key
}) do

ExAws.S3.head_object(image_bucket, String.trim_leading(image_key, "/"))
|> ExAws.request()
|> case do
{:ok, name} ->
ExAws.Lambda.invoke(
name,
%{
"id" => "test_image",
"audio_bucket" => "streaming-orcasound-net",
"audio_key" => "rpi_sunset_bay/hls/1654758019/live006.ts",
"image_bucket" => "dev-archive-orcasound-net",
"image_key" => "spectrograms/live006_spect.png"
},
%{},
invocation_type: :request_response
)
|> ExAws.request()
{:ok, %{headers: headers}} ->
# Exists, skip
size =
headers
|> Enum.find(&(elem(&1, 0) == "Content-Length"))
|> elem(1)
|> String.to_integer()

{:ok, %{size: size}}

_ ->
nil
# Doesn't exist, make spectrogram
ExAws.Lambda.list_functions()
|> ExAws.request!()
|> Map.get("Functions")
|> Enum.find_value(fn %{"FunctionName" => name} ->
if String.contains?(name, "AudioVizFunction"), do: {:ok, name}
end)
|> case do
{:ok, name} ->
ExAws.Lambda.invoke(
name,
%{
"id" => image_id,
"audio_bucket" => audio_bucket,
"audio_key" => String.trim_leading(audio_key, "/"),
"image_bucket" => image_bucket,
"image_key" => String.trim_leading(image_key, "/")
},
%{},
invocation_type: :request_response
)
|> ExAws.request()

# TODO: Set sample rate, size

_ ->
nil
end
end
end

Expand Down Expand Up @@ -91,7 +116,12 @@ defmodule Orcasite.Radio.AwsClient do
end
end

def loop_request_timestamp(feed, callback \\ nil, token \\ nil, results \\ @default_results) do
def loop_request_timestamp(
feed,
callback \\ nil,
token \\ nil,
results \\ @default_timestamp_results
) do
request_timestamps(feed, token, callback)
|> case do
{:ok,
Expand Down
22 changes: 14 additions & 8 deletions server/lib/orcasite/radio/calculations/decode_uuid.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,21 @@ defmodule Orcasite.Radio.Calculations.DecodeUUID do
end

def decode(record) do
[_, encoded_id] =
record
|> Map.get(:id)
|> String.split("_")
case Ecto.UUID.cast(record.id) do
{:ok, uuid} ->
uuid

with {:ok, uuid} <- AshUUID.Encoder.decode(encoded_id) do
uuid
else
_ -> nil
_ ->
[_, encoded_id] =
record
|> Map.get(:id)
|> String.split("_")

with {:ok, uuid} <- AshUUID.Encoder.decode(encoded_id) do
uuid
else
_ -> nil
end
end
end
end
4 changes: 3 additions & 1 deletion server/lib/orcasite/radio/feed.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ defmodule Orcasite.Radio.Feed do
end

attributes do
uuid_attribute :id, prefix: "feed", public?: true
uuid_attribute :id, public?: true

attribute :name, :string, allow_nil?: false, public?: true
attribute :node_name, :string, allow_nil?: false, public?: true
Expand Down Expand Up @@ -67,6 +67,8 @@ defmodule Orcasite.Radio.Feed do
:string,
{Orcasite.Radio.Calculations.FeedImageUrl, object: "map.png"},
public?: true

# calculate :uuid, :string, Orcasite.Radio.Calculations.DecodeUUID
end

aggregates do
Expand Down
2 changes: 1 addition & 1 deletion server/lib/orcasite/types/audio_image_status.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
defmodule Orcasite.Types.AudioImageStatus do
use Ash.Type.Enum, values: [:new, :processing, :complete]
use Ash.Type.Enum, values: [:new, :processing, :complete, :failed]
end

0 comments on commit 33f31dc

Please sign in to comment.