add OS transcoding extension

This commit is contained in:
jeffvli
2026-03-09 21:50:03 -07:00
parent 7982c0e1bd
commit a30b1ec90b
10 changed files with 529 additions and 71 deletions
+81
View File
@@ -11,6 +11,80 @@ const userParameters = z.object({
username: z.string(),
});
const transcodeDecisionParameters = z.object({
mediaId: z.string(),
mediaType: z.enum(['song', 'podcast']),
});
const getTranscodeStreamParameters = z.object({
mediaId: z.string(),
mediaType: z.enum(['song', 'podcast']),
offset: z.number().optional(),
transcodeParams: z.string(),
});
const codecProfileLimitation = z.object({
comparison: z.string(),
name: z.string(),
required: z.boolean().optional(),
values: z.array(z.string()),
});
const directPlayProfile = z.object({
audioCodecs: z.array(z.string()),
containers: z.array(z.string()),
maxAudioChannels: z.number().optional(),
protocols: z.array(z.string()),
});
const transcodingProfile = z.object({
audioCodec: z.string(),
container: z.string(),
maxAudioChannels: z.number().optional(),
protocol: z.string(),
});
const codecProfile = z.object({
limitations: z.array(codecProfileLimitation).optional(),
name: z.string(),
type: z.string(),
});
const transcodeDecisionRequestBody = z.object({
codecProfiles: z.array(codecProfile).optional(),
directPlayProfiles: z.array(directPlayProfile).optional(),
maxAudioBitrate: z.number().optional(),
maxTranscodingAudioBitrate: z.number().optional(),
name: z.string(),
platform: z.string(),
transcodingProfiles: z.array(transcodingProfile).optional(),
});
const streamDetails = z.object({
audioBitdepth: z.number().optional(),
audioBitrate: z.number().optional(),
audioChannels: z.number().optional(),
audioProfile: z.string().optional(),
audioSamplerate: z.number().optional(),
codec: z.string().optional(),
container: z.string().optional(),
protocol: z.string().optional(),
});
const transcodeDecision = z.object({
canDirectPlay: z.boolean(),
canTranscode: z.boolean(),
errorReason: z.string().optional(),
sourceStream: streamDetails.optional(),
transcodeParams: z.string().optional(),
transcodeReason: z.array(z.string()).optional(),
transcodeStream: streamDetails.optional(),
});
const getTranscodeDecision = z.object({
transcodeDecision,
});
const user = z.object({
user: z.object({
adminRole: z.boolean(),
@@ -382,6 +456,7 @@ export enum SubsonicExtensions {
INDEX_BASED_QUEUE = 'indexBasedQueue',
SONG_LYRICS = 'songLyrics',
TRANSCODE_OFFSET = 'transcodeOffset',
TRANSCODING = 'transcoding',
}
const updatePlaylistParameters = z.object({
@@ -718,6 +793,9 @@ const getInternetRadioStations = z.object({
});
export const ssType = {
_body: {
getTranscodeDecision: transcodeDecisionRequestBody,
},
_parameters: {
albumInfo: albumInfoParameters,
albumList: albumListParameters,
@@ -741,6 +819,8 @@ export const ssType = {
getSong: getSongParameters,
getSongsByGenre: getSongsByGenreParameters,
getStarred: getStarredParameters,
getTranscodeDecision: transcodeDecisionParameters,
getTranscodeStream: getTranscodeStreamParameters,
randomSongList: randomSongListParameters,
removeFavorite: removeFavoriteParameters,
savePlayQueueByIndex: savePlayQueueByIndexParameters,
@@ -786,6 +866,7 @@ export const ssType = {
getSong,
getSongsByGenre,
getStarred,
getTranscodeDecision,
internetRadioStation,
musicFolderList,
ping,
+56 -11
View File
@@ -410,16 +410,18 @@ export type Song = {
userRating: null | number;
};
type ApiContext = {
pathReplace?: string;
pathReplaceWith?: string;
};
type BaseEndpointArgs = {
apiClientProps: {
server?: null | ServerListItemWithCredential;
serverId: string;
signal?: AbortSignal;
};
context?: {
pathReplace?: string;
pathReplaceWith?: string;
};
context?: ApiContext;
};
type GenreListSortMap = {
@@ -1416,11 +1418,10 @@ export type ControllerEndpoint = {
getSongDetail: (args: SongDetailArgs) => Promise<SongDetailResponse>;
getSongList: (args: SongListArgs) => Promise<SongListResponse>;
getSongListCount: (args: SongListCountArgs) => Promise<number>;
getStreamUrl: (args: StreamArgs) => string;
getStreamUrl: (args: StreamArgs) => Promise<string>;
getStructuredLyrics?: (args: StructuredLyricsArgs) => Promise<StructuredLyric[]>;
getTagList?: (args: TagListArgs) => Promise<TagListResponse>;
getTopSongs: (args: TopSongListArgs) => Promise<TopSongListResponse>;
// getArtistInfo?: (args: any) => void;
getUserInfo: (args: UserInfoArgs) => Promise<UserInfoResponse>;
getUserList?: (args: UserListArgs) => Promise<UserListResponse>;
movePlaylistItem?: (args: MoveItemArgs) => Promise<void>;
@@ -1563,7 +1564,7 @@ export type InternalControllerEndpoint = {
getSongDetail: (args: ReplaceApiClientProps<SongDetailArgs>) => Promise<SongDetailResponse>;
getSongList: (args: ReplaceApiClientProps<SongListArgs>) => Promise<SongListResponse>;
getSongListCount: (args: ReplaceApiClientProps<SongListCountArgs>) => Promise<number>;
getStreamUrl: (args: ReplaceApiClientProps<StreamArgs>) => string;
getStreamUrl: (args: ReplaceApiClientProps<StreamArgs>) => Promise<string>;
getStructuredLyrics?: (
args: ReplaceApiClientProps<StructuredLyricsArgs>,
) => Promise<StructuredLyric[]>;
@@ -1667,6 +1668,9 @@ export type StreamQuery = {
bitrate?: number;
format?: string;
id: string;
mediaType?: 'podcast' | 'song';
offset?: number;
skipAutoTranscode?: boolean;
transcode: boolean;
};
@@ -1711,6 +1715,50 @@ export type TagListResponse = {
tags?: Tag[];
};
export type TranscodeDecisionArgs = BaseEndpointArgs & {
body?: TranscodeDecisionRequestBody;
query: TranscodeDecisionQuery;
};
export type TranscodeDecisionQuery = {
id: string;
type: 'song';
};
export type TranscodeDecisionRequestBody = {
codecProfiles?: Array<{
limitations?: Array<{
comparison: string;
name: string;
required?: boolean;
values: string[];
}>;
name: string;
type: string;
}>;
directPlayProfiles?: Array<{
audioCodecs: string[];
containers: string[];
maxAudioChannels?: number;
protocols: string[];
}>;
maxAudioBitrate?: number;
maxTranscodingAudioBitrate?: number;
name: string;
platform: string;
transcodingProfiles?: Array<{
audioCodec: string;
container: string;
maxAudioChannels?: number;
protocol: string;
}>;
};
export type TranscodeDecisionResponse = {
decision: 'direct' | 'transcode';
transcodeParams?: string;
};
export type UserInfoArgs = BaseEndpointArgs & { query: UserInfoQuery };
export type UserInfoQuery = {
@@ -1730,8 +1778,5 @@ type BaseEndpointArgsWithServer = {
serverId: string;
signal?: AbortSignal;
};
context?: {
pathReplace?: string;
pathReplaceWith?: string;
};
context?: ApiContext;
};
+1
View File
@@ -7,6 +7,7 @@ export enum ServerFeature {
LYRICS_SINGLE_STRUCTURED = 'lyricsSingleStructured',
MUSIC_FOLDER_MULTISELECT = 'musicFolderMultiselect',
OS_FORM_POST = 'osFormPost',
OS_TRANSCODE_DECISION = 'osTranscodeDecision',
PLAYLISTS_SMART = 'playlistsSmart',
PUBLIC_PLAYLIST = 'publicPlaylist',
SERVER_PLAY_QUEUE = 'serverPlayQueue',