Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 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
43 changes: 43 additions & 0 deletions docs/dev/AUDIO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Audio Datatype

The `Audio` datatype is similar to `Scene` but stores audio-only media (i.e. Audiobooks, music, ASMR, etc).

## Scope
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Gykes / @WithoutPants : You can see a limited scope here. The database design pkg/sqlite/migrations/86_audio.up.sql shows the extent of the change.

If I get a thumbs up, then I will start work. If you want to increase/reduce scope, or to wait for any other PRs, let me know here

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should nail down where this is planning on going. XXX vs non XXX

I prefer the former as that's what this app is kind of aimed at. If we do XXX then I believe that having Performer and Artist in the audio metadata would be confusing and would conflict. I say we just roll with what we currently have and not add Artists.

Copy link
Copy Markdown
Contributor Author

@bob12224 bob12224 Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am okay with all suggested changes.

Still waiting for some confirmation that the scope will be accepted before investing time into this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Gykes if you feel comfortable giving the thumbs up on this, then I can go ahead.

For all of your suggestions, I placed a 👍 to indicate that I agree and will make the change once I start working.

I will be using this for SFW, but don't mind building it for NSFW. I will adopt all your suggestions (except the rating100, see PR comment to discuss)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the fact that the main dev has said it's okay in previous comments and casually in passing then I'm okay giving the thumbs up. I can test/review as you go or in chunks, whatever you are more comfortable doing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Gykes this will be a large PR, if you want to provide reviews as commits come, or wait till the end; whatever you think is best to ensure this gets merged. I am also open to excluding/deferring sections to reduce the PR size

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just give me a ping when you feel it's ready for a review/test


- This ticket adds backend support for Audio Only, future tickets can add the UI elements
- Audio metadata:
- Title
- Artists (string? like director)
- Date
- Studio
- Performers
- Tags
- Details
- Urls
- Rating
- Organized
- O History
- Play History
- Studio Code
- NICE TO HAVES
- Groups
- Audio File metadata:
- duration
- audio codec
- OPTIONAL (can be added now or later)
- channels (mono, stereo, 5.1, 7.1)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would tthis really be needed? Currently scenes have audio and we don't track this at all.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest looking at bitrate again, as this is tracked for scenes (the video portion).
And it might be better to add now, instead of planning to add later.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly I was referring to the channels. I think bitrate would be fine.

- bitrate
- sample rate


## TODO List

- [ ] `pkg/sqlite/migrations/86_audio.up.sql`
- Create a migration for the Audio type, very similar to Scene
- [ ] Duplicate much of `pkg/scene/*` into `pkg/audio/*`
- Exclude: markers, screenshot, preview, transcode, sprite
- [ ] Graphql
- [ ] Copy/modify `graphql/schema/types/scene.graphql` to `graphql/schema/types/audio.graphql`

### Last Steps
- [ ] Delete this file upon completion of the feature
2 changes: 2 additions & 0 deletions graphql/schema/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# TODO(audio): add findAudio, findAudios, audioCreate, audioUpdate, audioDestroy, audiosDestroy

"The query root for this schema"
type Query {
# Filters
Expand Down
300 changes: 300 additions & 0 deletions graphql/schema/types/audio.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
# TODO(audio): update this file

type AudioFileType {
size: String
duration: Float
video_codec: String
audio_codec: String
width: Int
height: Int
framerate: Float
bitrate: Int
}

type AudioPathsType {
screenshot: String # Resolver
preview: String # Resolver
stream: String # Resolver
webp: String # Resolver
vtt: String # Resolver
sprite: String # Resolver
funscript: String # Resolver
interactive_heatmap: String # Resolver
caption: String # Resolver
}

type AudioMovie {
movie: Movie!
audio_index: Int
}

type AudioGroup {
group: Group!
audio_index: Int
}

type VideoCaption {
language_code: String!
caption_type: String!
}

type Audio {
id: ID!
title: String
code: String
details: String
director: String
url: String @deprecated(reason: "Use urls")
urls: [String!]!
date: String
# rating expressed as 1-100
rating100: Int
organized: Boolean!
o_counter: Int
interactive: Boolean!
interactive_speed: Int
captions: [VideoCaption!]
created_at: Time!
updated_at: Time!
"The last time play count was updated"
last_played_at: Time
"The time index a audio was left at"
resume_time: Float
"The total time a audio has spent playing"
play_duration: Float
"The number ot times a audio has been played"
play_count: Int

"Times a audio was played"
play_history: [Time!]!
"Times the o counter was incremented"
o_history: [Time!]!

files: [VideoFile!]!
paths: AudioPathsType! # Resolver
audio_markers: [AudioMarker!]!
galleries: [Gallery!]!
studio: Studio
groups: [AudioGroup!]!
movies: [AudioMovie!]! @deprecated(reason: "Use groups")
tags: [Tag!]!
performers: [Performer!]!
stash_ids: [StashID!]!

custom_fields: Map!

"Return valid stream paths"
audioStreams: [AudioStreamEndpoint!]!
}

input AudioMovieInput {
movie_id: ID!
audio_index: Int
}

input AudioGroupInput {
group_id: ID!
audio_index: Int
}

input AudioCreateInput {
title: String
code: String
details: String
director: String
url: String @deprecated(reason: "Use urls")
urls: [String!]
date: String
# rating expressed as 1-100
rating100: Int
organized: Boolean
studio_id: ID
gallery_ids: [ID!]
performer_ids: [ID!]
groups: [AudioGroupInput!]
movies: [AudioMovieInput!] @deprecated(reason: "Use groups")
tag_ids: [ID!]
"This should be a URL or a base64 encoded data URL"
cover_image: String
stash_ids: [StashIDInput!]

"""
The first id will be assigned as primary.
Files will be reassigned from existing audios if applicable.
Files must not already be primary for another audio.
"""
file_ids: [ID!]

custom_fields: Map
}

input AudioUpdateInput {
clientMutationId: String
id: ID!
title: String
code: String
details: String
director: String
url: String @deprecated(reason: "Use urls")
urls: [String!]
date: String
# rating expressed as 1-100
rating100: Int
o_counter: Int
@deprecated(reason: "Unsupported - Use audioIncrementO/audioDecrementO")
organized: Boolean
studio_id: ID
gallery_ids: [ID!]
performer_ids: [ID!]
groups: [AudioGroupInput!]
movies: [AudioMovieInput!] @deprecated(reason: "Use groups")
tag_ids: [ID!]
"This should be a URL or a base64 encoded data URL"
cover_image: String
stash_ids: [StashIDInput!]

"The time index a audio was left at"
resume_time: Float
"The total time a audio has spent playing"
play_duration: Float
"The number ot times a audio has been played"
play_count: Int
@deprecated(
reason: "Unsupported - Use audioIncrementPlayCount/audioDecrementPlayCount"
)

primary_file_id: ID

custom_fields: CustomFieldsInput
}

enum BulkUpdateIdMode {
SET
ADD
REMOVE
}

input BulkUpdateIds {
ids: [ID!]
mode: BulkUpdateIdMode!
}

input BulkAudioUpdateInput {
clientMutationId: String
ids: [ID!]
title: String
code: String
details: String
director: String
url: String @deprecated(reason: "Use urls")
urls: BulkUpdateStrings
date: String
# rating expressed as 1-100
rating100: Int
organized: Boolean
studio_id: ID
gallery_ids: BulkUpdateIds
performer_ids: BulkUpdateIds
tag_ids: BulkUpdateIds
group_ids: BulkUpdateIds
movie_ids: BulkUpdateIds @deprecated(reason: "Use group_ids")

custom_fields: CustomFieldsInput
}

input AudioDestroyInput {
id: ID!
delete_file: Boolean
delete_generated: Boolean
"If true, delete the file entry from the database if the file is not assigned to any other objects"
destroy_file_entry: Boolean
}

input AudiosDestroyInput {
ids: [ID!]!
delete_file: Boolean
delete_generated: Boolean
"If true, delete the file entry from the database if the file is not assigned to any other objects"
destroy_file_entry: Boolean
}

type FindAudiosResultType {
count: Int!
"Total duration in seconds"
duration: Float!
"Total file size in bytes"
filesize: Float!
audios: [Audio!]!
}

input AudioParserInput {
ignoreWords: [String!]
whitespaceCharacters: String
capitalizeTitle: Boolean
ignoreOrganized: Boolean
}

type AudioMovieID {
movie_id: ID!
audio_index: String
}

type AudioParserResult {
audio: Audio!
title: String
code: String
details: String
director: String
url: String
date: String
# rating expressed as 1-5
rating: Int @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100
rating100: Int
studio_id: ID
gallery_ids: [ID!]
performer_ids: [ID!]
movies: [AudioMovieID!]
tag_ids: [ID!]
}

type AudioParserResultType {
count: Int!
results: [AudioParserResult!]!
}

input AudioHashInput {
checksum: String
oshash: String
}

type AudioStreamEndpoint {
url: String!
mime_type: String
label: String
}

input AssignAudioFileInput {
audio_id: ID!
file_id: ID!
}

input AudioMergeInput {
"""
If destination audio has no files, then the primary file of the
first source audio will be assigned as primary
"""
source: [ID!]!
destination: ID!
# values defined here will override values in the destination
values: AudioUpdateInput

# if true, the source history will be combined with the destination
play_history: Boolean
o_history: Boolean
}

type HistoryMutationResult {
count: Int!
history: [Time!]!
}
2 changes: 2 additions & 0 deletions graphql/schema/types/filters.graphql
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# TODO(audio): add AudioFilterType

enum SortDirectionEnum {
ASC
DESC
Expand Down
Loading
Loading