From 004ebea7d0281c49e3c7549f42eca12b75f83434 Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 9 Feb 2026 20:16:44 -0500 Subject: [PATCH] fix: small issues Updated mock AAC transcoding command to use the new default (ipod with fragmented MP4) matching the migration, ensuring tests exercise the same buildDynamicArgs code path as production. Improved archiver test mock to match on the whole StreamRequest struct instead of decomposing fields, making it resilient to future field additions. Added named constants for JWT claim keys in the transcode token and wrapped ParseTranscodeParams errors with ErrTokenInvalid for consistency. Documented the IsLossless BitDepth fallback heuristic as temporary until Codec column is populated. Signed-off-by: Deluan --- core/archiver_test.go | 10 +++---- core/transcode/transcode.go | 48 ++++++++++++++++++++-------------- model/mediafile.go | 4 ++- tests/mock_transcoding_repo.go | 2 +- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/core/archiver_test.go b/core/archiver_test.go index b0cf13ae1..4ded2eba4 100644 --- a/core/archiver_test.go +++ b/core/archiver_test.go @@ -44,7 +44,7 @@ var _ = Describe("Archiver", func() { }}).Return(mfs, nil) ds.On("MediaFile", mock.Anything).Return(mfRepo) - ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128, 0, 0, 0, 0).Return(io.NopCloser(strings.NewReader("test")), nil).Times(3) + ms.On("DoStream", mock.Anything, mock.Anything, core.StreamRequest{Format: "mp3", BitRate: 128}).Return(io.NopCloser(strings.NewReader("test")), nil).Times(3) out := new(bytes.Buffer) err := arch.ZipAlbum(context.Background(), "1", "mp3", 128, out) @@ -73,7 +73,7 @@ var _ = Describe("Archiver", func() { }}).Return(mfs, nil) ds.On("MediaFile", mock.Anything).Return(mfRepo) - ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128, 0, 0, 0, 0).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2) + ms.On("DoStream", mock.Anything, mock.Anything, core.StreamRequest{Format: "mp3", BitRate: 128}).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2) out := new(bytes.Buffer) err := arch.ZipArtist(context.Background(), "1", "mp3", 128, out) @@ -104,7 +104,7 @@ var _ = Describe("Archiver", func() { } sh.On("Load", mock.Anything, "1").Return(share, nil) - ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128, 0, 0, 0, 0).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2) + ms.On("DoStream", mock.Anything, mock.Anything, core.StreamRequest{Format: "mp3", BitRate: 128}).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2) out := new(bytes.Buffer) err := arch.ZipShare(context.Background(), "1", out) @@ -136,7 +136,7 @@ var _ = Describe("Archiver", func() { plRepo := &mockPlaylistRepository{} plRepo.On("GetWithTracks", "1", true, false).Return(pls, nil) ds.On("Playlist", mock.Anything).Return(plRepo) - ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128, 0, 0, 0, 0).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2) + ms.On("DoStream", mock.Anything, mock.Anything, core.StreamRequest{Format: "mp3", BitRate: 128}).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2) out := new(bytes.Buffer) err := arch.ZipPlaylist(context.Background(), "1", "mp3", 128, out) @@ -218,7 +218,7 @@ type mockMediaStreamer struct { } func (m *mockMediaStreamer) DoStream(ctx context.Context, mf *model.MediaFile, req core.StreamRequest) (*core.Stream, error) { - args := m.Called(ctx, mf, req.Format, req.BitRate, req.SampleRate, req.BitDepth, req.Channels, req.Offset) + args := m.Called(ctx, mf, req) if args.Error(1) != nil { return nil, args.Error(1) } diff --git a/core/transcode/transcode.go b/core/transcode/transcode.go index d9bc41018..fe246d89d 100644 --- a/core/transcode/transcode.go +++ b/core/transcode/transcode.go @@ -15,6 +15,16 @@ import ( const ( tokenTTL = 12 * time.Hour defaultBitrate = 256 // kbps + + // JWT claim keys for transcode params tokens + claimMediaID = "mid" // Media file ID + claimDirectPlay = "dp" // Direct play flag (bool) + claimUpdatedAt = "ua" // Source file updated-at (Unix seconds) + claimFormat = "fmt" // Target transcoding format + claimBitrate = "br" // Target bitrate (kbps) + claimChannels = "ch" // Target channels + claimSampleRate = "sr" // Target sample rate (Hz) + claimBitDepth = "bd" // Target bit depth ) func NewDecider(ds model.DataStore) Decider { @@ -299,21 +309,21 @@ func (s *deciderService) applyCodecLimitations(ctx context.Context, sourceBitrat func (s *deciderService) CreateTranscodeParams(decision *Decision) (string, error) { exp := time.Now().Add(tokenTTL) claims := map[string]any{ - "mid": decision.MediaID, - "dp": decision.CanDirectPlay, - "ua": decision.SourceUpdatedAt.Truncate(time.Second).Unix(), + claimMediaID: decision.MediaID, + claimDirectPlay: decision.CanDirectPlay, + claimUpdatedAt: decision.SourceUpdatedAt.Truncate(time.Second).Unix(), } if decision.CanTranscode && decision.TargetFormat != "" { - claims["fmt"] = decision.TargetFormat - claims["br"] = decision.TargetBitrate + claims[claimFormat] = decision.TargetFormat + claims[claimBitrate] = decision.TargetBitrate if decision.TargetChannels > 0 { - claims["ch"] = decision.TargetChannels + claims[claimChannels] = decision.TargetChannels } if decision.TargetSampleRate > 0 { - claims["sr"] = decision.TargetSampleRate + claims[claimSampleRate] = decision.TargetSampleRate } if decision.TargetBitDepth > 0 { - claims["bd"] = decision.TargetBitDepth + claims[claimBitDepth] = decision.TargetBitDepth } } return auth.CreateExpiringPublicToken(exp, claims) @@ -328,38 +338,38 @@ func (s *deciderService) ParseTranscodeParams(token string) (*Params, error) { params := &Params{} // Required claims - mid, ok := claims["mid"].(string) + mid, ok := claims[claimMediaID].(string) if !ok || mid == "" { - return nil, fmt.Errorf("invalid transcode token: missing media ID") + return nil, fmt.Errorf("%w: invalid transcode token: missing media ID", ErrTokenInvalid) } params.MediaID = mid - dp, ok := claims["dp"].(bool) + dp, ok := claims[claimDirectPlay].(bool) if !ok { - return nil, fmt.Errorf("invalid transcode token: missing direct play flag") + return nil, fmt.Errorf("%w: invalid transcode token: missing direct play flag", ErrTokenInvalid) } params.DirectPlay = dp // Optional claims (legitimately absent for direct-play tokens) - if f, ok := claims["fmt"].(string); ok { + if f, ok := claims[claimFormat].(string); ok { params.TargetFormat = f } - if br, ok := claims["br"].(float64); ok { + if br, ok := claims[claimBitrate].(float64); ok { params.TargetBitrate = int(br) } - if ch, ok := claims["ch"].(float64); ok { + if ch, ok := claims[claimChannels].(float64); ok { params.TargetChannels = int(ch) } - if sr, ok := claims["sr"].(float64); ok { + if sr, ok := claims[claimSampleRate].(float64); ok { params.TargetSampleRate = int(sr) } - if bd, ok := claims["bd"].(float64); ok { + if bd, ok := claims[claimBitDepth].(float64); ok { params.TargetBitDepth = int(bd) } - ua, ok := claims["ua"].(float64) + ua, ok := claims[claimUpdatedAt].(float64) if !ok { - return nil, fmt.Errorf("invalid transcode token: missing source timestamp") + return nil, fmt.Errorf("%w: invalid transcode token: missing source timestamp", ErrTokenInvalid) } params.SourceUpdatedAt = time.Unix(int64(ua), 0) diff --git a/model/mediafile.go b/model/mediafile.go index e92109a06..908540a0f 100644 --- a/model/mediafile.go +++ b/model/mediafile.go @@ -239,7 +239,9 @@ func (mf MediaFile) IsLossless() bool { if slices.Contains(confmime.LosslessFormats, mf.Suffix) { return true } - // Fallback heuristic: if BitDepth is set, it's likely lossless + // Fallback heuristic: if BitDepth is set, it's likely lossless. + // This may produce false positives for lossy formats that report bit depth, + // but it becomes irrelevant once the Codec column is populated after a full rescan. return mf.BitDepth > 0 } diff --git a/tests/mock_transcoding_repo.go b/tests/mock_transcoding_repo.go index ff0aba86b..796e84111 100644 --- a/tests/mock_transcoding_repo.go +++ b/tests/mock_transcoding_repo.go @@ -21,7 +21,7 @@ func (m *MockTranscodingRepo) FindByFormat(format string) (*model.Transcoding, e case "flac": return &model.Transcoding{ID: "flac1", TargetFormat: "flac", DefaultBitRate: 0, Command: "ffmpeg -i %s -ss %t -map 0:a:0 -v 0 -c:a flac -f flac -"}, nil case "aac": - return &model.Transcoding{ID: "aac1", TargetFormat: "aac", DefaultBitRate: 256, Command: "ffmpeg -i %s -ss %t -map 0:a:0 -b:a %bk -v 0 -c:a aac -f adts -"}, nil + return &model.Transcoding{ID: "aac1", TargetFormat: "aac", DefaultBitRate: 256, Command: "ffmpeg -i %s -ss %t -map 0:a:0 -b:a %bk -v 0 -c:a aac -f ipod -movflags frag_keyframe+empty_moov -"}, nil default: return nil, model.ErrNotFound }