@@ -13,12 +13,12 @@ import (
13
13
"github.com/disintegration/imaging"
14
14
"github.com/jinzhu/gorm"
15
15
16
+ "go.senan.xyz/gonic/countrw"
16
17
"go.senan.xyz/gonic/server/ctrlsubsonic/params"
17
18
"go.senan.xyz/gonic/server/ctrlsubsonic/spec"
18
19
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
19
20
"go.senan.xyz/gonic/server/db"
20
- "go.senan.xyz/gonic/server/encode"
21
- "go.senan.xyz/gonic/server/mime"
21
+ "go.senan.xyz/gonic/server/transcode"
22
22
)
23
23
24
24
// "raw" handlers are ones that don't always return a spec response.
@@ -242,111 +242,76 @@ func (c *Controller) ServeStream(w http.ResponseWriter, r *http.Request) *spec.R
242
242
if err != nil {
243
243
return spec .NewError (10 , "please provide an `id` parameter" )
244
244
}
245
- var audioFile db.AudioFile
245
+ var file db.AudioFile
246
246
var audioPath string
247
247
switch id .Type {
248
248
case specid .Track :
249
249
track , err := streamGetTrack (c .DB , id .Value )
250
250
if err != nil {
251
251
return spec .NewError (70 , "track with id `%s` was not found" , id )
252
252
}
253
- audioFile = track
253
+ file = track
254
254
audioPath = path .Join (track .AbsPath ())
255
255
case specid .PodcastEpisode :
256
256
podcast , err := streamGetPodcast (c .DB , id .Value )
257
257
if err != nil {
258
258
return spec .NewError (70 , "podcast with id `%s` was not found" , id )
259
259
}
260
- audioFile = podcast
260
+ file = podcast
261
261
audioPath = path .Join (c .PodcastsPath , podcast .Path )
262
262
default :
263
263
return spec .NewError (70 , "media type of `%s` was not found" , id .Type )
264
264
}
265
265
266
266
user := r .Context ().Value (CtxUser ).(* db.User )
267
- if track , ok := audioFile .(* db.Track ); ok && track .Album != nil {
267
+ if track , ok := file .(* db.Track ); ok && track .Album != nil {
268
268
defer func () {
269
269
if err := streamUpdateStats (c .DB , user .ID , track .Album .ID , time .Now ()); err != nil {
270
- log .Printf ("error updating listen stats : %v" , err )
270
+ log .Printf ("error updating status : %v" , err )
271
271
}
272
272
}()
273
273
}
274
274
275
- pref , err := streamGetTransPref ( c . DB , user . ID , params .GetOr ( "c" , "" ))
276
- if err != nil {
277
- return spec . NewError ( 0 , "failed to get transcode stream preference: %v" , err )
275
+ if format , _ := params .Get ( "format" ); format == "raw" {
276
+ http . ServeFile ( w , r , audioPath )
277
+ return nil
278
278
}
279
279
280
- onInvalidProfile := func () error {
281
- log .Printf ("serving raw `%s`\n " , audioFile .AudioFilename ())
282
- w .Header ().Set ("Content-Type" , audioFile .MIME ())
280
+ pref , err := streamGetTransPref (c .DB , user .ID , params .GetOr ("c" , "" ))
281
+ if err != nil && ! errors .Is (err , gorm .ErrRecordNotFound ) {
282
+ return spec .NewError (0 , "couldn't find transcode preference: %v" , err )
283
+ }
284
+ if pref == nil {
283
285
http .ServeFile (w , r , audioPath )
284
286
return nil
285
287
}
286
- onCacheHit := func (profile encode.Profile , path string ) error {
287
- log .Printf ("serving transcode `%s`: cache [%s/%dk] hit!\n " ,
288
- audioFile .AudioFilename (), profile .Format , profile .Bitrate )
289
- cacheMime , _ := mime .FromExtension (profile .Format )
290
- w .Header ().Set ("Content-Type" , cacheMime )
291
288
292
- cacheFile , err := os .Stat (path )
293
- if err != nil {
294
- return fmt .Errorf ("failed to stat cache file `%s`: %w" , path , err )
295
- }
296
- contentLength := fmt .Sprintf ("%d" , cacheFile .Size ())
297
- w .Header ().Set ("Content-Length" , contentLength )
298
- http .ServeFile (w , r , path )
299
- return nil
289
+ profile , ok := transcode .UserProfiles [pref .Profile ]
290
+ if ! ok {
291
+ return spec .NewError (0 , "unknown transcode user profile %q" , pref .Profile )
300
292
}
301
- onCacheMiss := func (profile encode.Profile ) (io.Writer , error ) {
302
- log .Printf ("serving transcode `%s`: cache [%s/%dk] miss!\n " ,
303
- audioFile .AudioFilename (), profile .Format , profile .Bitrate )
304
- encodeMime , _ := mime .FromExtension (profile .Format )
305
- w .Header ().Set ("Content-Type" , encodeMime )
306
- return w , nil
307
- }
308
- encodeOptions := encode.Options {
309
- TrackPath : audioPath ,
310
- TrackBitrate : audioFile .AudioBitrate (),
311
- CachePath : c .CachePath ,
312
- ProfileName : pref .Profile ,
313
- PreferredBitrate : params .GetOrInt ("maxBitRate" , 0 ),
314
- OnInvalidProfile : onInvalidProfile ,
315
- OnCacheHit : onCacheHit ,
316
- OnCacheMiss : onCacheMiss ,
317
- }
318
- if err := encode .Encode (encodeOptions ); err != nil {
319
- log .Printf ("serving transcode `%s`: error: %v\n " , audioFile .AudioFilename (), err )
293
+ if max , _ := params .GetInt ("maxBitRate" ); max > 0 && int (profile .BitRate ()) > max {
294
+ profile = transcode .WithBitrate (profile , transcode .BitRate (max ))
320
295
}
321
- return nil
322
- }
323
296
324
- func (c * Controller ) ServeDownload (w http.ResponseWriter , r * http.Request ) * spec.Response {
325
- params := r .Context ().Value (CtxParams ).(params.Params )
326
- id , err := params .GetID ("id" )
327
- if err != nil {
328
- return spec .NewError (10 , "please provide an `id` parameter" )
297
+ log .Printf ("trancoding to %q with max bitrate %dk" , profile .MIME (), profile .BitRate ())
298
+
299
+ guessedSize := transcode .GuessExpectedSize (profile , time .Duration (file .AudioLength ())* time .Second )
300
+ w .Header ().Set ("Content-Type" , profile .MIME ())
301
+ w .Header ().Set ("Content-Length" , fmt .Sprintf ("%d" , guessedSize ))
302
+
303
+ cw := countrw .NewCountWriter (w )
304
+ if err := c .Transcoder .Transcode (r .Context (), profile , audioPath , cw ); err != nil {
305
+ return spec .NewError (0 , "error transcoding %v" , err )
329
306
}
330
- var filePath string
331
- var audioFile db.AudioFile
332
- switch id .Type {
333
- case specid .Track :
334
- track , _ := streamGetTrack (c .DB , id .Value )
335
- audioFile = track
336
- filePath = track .AbsPath ()
337
- if err != nil {
338
- return spec .NewError (70 , "track with id `%s` was not found" , id )
339
- }
340
- case specid .PodcastEpisode :
341
- podcast , err := streamGetPodcast (c .DB , id .Value )
342
- audioFile = podcast
343
- filePath = path .Join (c .PodcastsPath , podcast .Path )
344
- if err != nil {
345
- return spec .NewError (70 , "podcast with id `%s` was not found" , id )
346
- }
307
+
308
+ // pad out the response with 0 up until the Content-Length we promised
309
+ remains := int64 (guessedSize ) - int64 (cw .Count ())
310
+ padding := io .LimitReader (& countrw.NullReader {}, remains )
311
+ _ , _ = io .Copy (cw , padding )
312
+
313
+ if f , ok := w .(http.Flusher ); ok {
314
+ f .Flush ()
347
315
}
348
- log .Printf ("serving raw `%s`\n " , audioFile .AudioFilename ())
349
- w .Header ().Set ("Content-Type" , audioFile .MIME ())
350
- http .ServeFile (w , r , filePath )
351
316
return nil
352
317
}
0 commit comments