diff --git a/front/public/translations/en.json b/front/public/translations/en.json index 78e33aa9..6fe0a950 100644 --- a/front/public/translations/en.json +++ b/front/public/translations/en.json @@ -311,6 +311,7 @@ "video": "Video", "audio": "Audio", "subtitles": "Subtitles", + "channels": "{{channels}} Channels", "forced": "Forced", "hearing-impaired": "CC", "default": "Default", diff --git a/front/src/models/video-info.ts b/front/src/models/video-info.ts index 506599bf..80e0466d 100644 --- a/front/src/models/video-info.ts +++ b/front/src/models/video-info.ts @@ -35,6 +35,7 @@ export const AudioTrack = z.object({ language: z.string().nullable(), codec: z.string(), mimeCodec: z.string().nullable(), + channels: z.int(), bitrate: z.number(), isDefault: z.boolean(), }); diff --git a/front/src/ui/info/index.tsx b/front/src/ui/info/index.tsx index 8bd7377a..a78c6a2f 100644 --- a/front/src/ui/info/index.tsx +++ b/front/src/ui/info/index.tsx @@ -94,6 +94,7 @@ export const Info = () => { ? t("mediainfo.default") : undefined, x.codec, + t("mediainfo.channels", { channels: x.channels }), ] .filter((x) => x) .join(" - ")} diff --git a/transcoder/migrations/000003_add_audio_channels.down.sql b/transcoder/migrations/000003_add_audio_channels.down.sql new file mode 100644 index 00000000..2f5955f7 --- /dev/null +++ b/transcoder/migrations/000003_add_audio_channels.down.sql @@ -0,0 +1,5 @@ +begin; + +alter table gocoder.audios drop column channels; + +commit; diff --git a/transcoder/migrations/000003_add_audio_channels.up.sql b/transcoder/migrations/000003_add_audio_channels.up.sql new file mode 100644 index 00000000..f0d5140f --- /dev/null +++ b/transcoder/migrations/000003_add_audio_channels.up.sql @@ -0,0 +1,6 @@ +begin; + +delete from gocoder.audios; +alter table gocoder.audios add column channels int not null default 2; + +commit; diff --git a/transcoder/src/codec.go b/transcoder/src/codec.go index acfe8839..9c98c110 100644 --- a/transcoder/src/codec.go +++ b/transcoder/src/codec.go @@ -100,11 +100,11 @@ func GetMimeCodec(stream *ffprobe.Stream) *string { return &ret case "ac3": - ret := "mp4a.a5" + ret := "ac-3" return &ret case "eac3": - ret := "mp4a.a6" + ret := "ec-3" return &ret case "flac": diff --git a/transcoder/src/filestream.go b/transcoder/src/filestream.go index 9f9637fb..2efd7830 100644 --- a/transcoder/src/filestream.go +++ b/transcoder/src/filestream.go @@ -77,52 +77,12 @@ func (fs *FileStream) Destroy() { func (fs *FileStream) GetMaster(client string) string { master := "#EXTM3U\n" - for _, audio := range fs.Info.Audios { - for _, quality := range AudioQualities { - master += "#EXT-X-MEDIA:TYPE=AUDIO," - master += fmt.Sprintf("GROUP-ID=\"audio-%s\",", quality) - if audio.Language != nil { - master += fmt.Sprintf("LANGUAGE=\"%s\",", *audio.Language) - } - if audio.Title != nil { - master += fmt.Sprintf("NAME=\"%s\",", *audio.Title) - } else if audio.Language != nil { - master += fmt.Sprintf("NAME=\"%s\",", *audio.Language) - } else { - master += fmt.Sprintf("NAME=\"Audio %d\",", audio.Index) - } - if audio.IsDefault { - master += "DEFAULT=YES," - } - master += "CHANNELS=\"2\"," - master += fmt.Sprintf("URI=\"audio/%d/%s/index.m3u8?clientId=%s\"\n", audio.Index, quality, client) - } - master += "#EXT-X-MEDIA:TYPE=AUDIO," - master += fmt.Sprintf("GROUP-ID=\"audio-%s\",", AOriginal) - if audio.Language != nil { - master += fmt.Sprintf("LANGUAGE=\"%s\",", *audio.Language) - } - if audio.Title != nil { - master += fmt.Sprintf("NAME=\"%s\",", *audio.Title) - } else if audio.Language != nil { - master += fmt.Sprintf("NAME=\"%s\",", *audio.Language) - } else { - master += fmt.Sprintf("NAME=\"Audio %d\",", audio.Index) - } - if audio.IsDefault { - master += "DEFAULT=YES," - } - master += "CHANNELS=\"2\"," // TODO - master += fmt.Sprintf("URI=\"audio/%d/%s/index.m3u8?clientId=%s\"\n", audio.Index, AOriginal, client) - } - // codec is the prefix + the level, the level is not part of the codec we want to compare for the same_codec check bellow transcode_prefix := "avc1.6400" transcode_codec := transcode_prefix + "28" transcode_audio_codec := "mp4a.40.2" var def_video *Video - var def_audio *Audio for _, video := range fs.Info.Videos { if video.IsDefault { def_video = &video @@ -132,10 +92,53 @@ func (fs *FileStream) GetMaster(client string) string { if def_video == nil && len(fs.Info.Videos) > 0 { def_video = &fs.Info.Videos[0] } - if len(fs.Info.Audios) > 0 { + + var def_audio *Audio + for _, audio := range fs.Info.Audios { + if audio.IsDefault { + def_audio = &audio + break + } + } + if def_audio == nil && len(fs.Info.Audios) > 0 { def_audio = &fs.Info.Audios[0] } + if def_audio != nil { + aqualities := utils.Filter(AudioQualities, func(quality AudioQuality) bool { + return quality.Bitrate() < def_audio.Bitrate + }) + aqualities = append(aqualities, AOriginal) + + for _, audio := range fs.Info.Audios { + for _, quality := range slices.Backward(aqualities) { + master += "#EXT-X-MEDIA:TYPE=AUDIO," + master += fmt.Sprintf("GROUP-ID=\"a-%s\",", quality) + if audio.Language != nil { + master += fmt.Sprintf("LANGUAGE=\"%s\",", *audio.Language) + } + if audio.Title != nil { + master += fmt.Sprintf("NAME=\"%s\",", *audio.Title) + } else if audio.Language != nil { + master += fmt.Sprintf("NAME=\"%s\",", *audio.Language) + } else { + master += fmt.Sprintf("NAME=\"Audio %d\",", audio.Index) + } + if audio == *def_audio { + master += "DEFAULT=YES," + } + if quality == AOriginal { + master += fmt.Sprintf("CHANNELS=\"%d\",", audio.Channels) + } else { + master += "CHANNELS=\"2\"," + } + master += fmt.Sprintf("URI=\"audio/%d/%s/index.m3u8?clientId=%s\"\n", audio.Index, quality, client) + } + master += "\n" + } + master += "\n" + } + if def_video != nil { qualities := utils.Filter(VideoQualities, func(quality VideoQuality) bool { return quality.Height() < def_video.Height @@ -148,8 +151,8 @@ func (fs *FileStream) GetMaster(client string) string { } qualities = append(qualities, Original) - for _, quality := range qualities { - for _, video := range fs.Info.Videos { + for _, video := range fs.Info.Videos { + for _, quality := range slices.Backward(qualities) { master += "#EXT-X-MEDIA:TYPE=VIDEO," master += fmt.Sprintf("GROUP-ID=\"%s\",", quality) if video.Language != nil { @@ -168,6 +171,7 @@ func (fs *FileStream) GetMaster(client string) string { master += fmt.Sprintf("URI=\"%d/%s/index.m3u8?clientId=%s\"\n", video.Index, quality, client) } } + master += "\n" } master += "\n" @@ -178,23 +182,35 @@ func (fs *FileStream) GetMaster(client string) string { if def_audio != nil && (def_audio.MimeCodec == nil || *def_audio.MimeCodec != transcode_audio_codec) { audios = append(audios, matchAudioQuality(def_video.Quality())) } - for _, audio_quality := range audios { + for _, aquality := range audios { // original & noresize streams bitrate := float64(def_video.Bitrate) master += "#EXT-X-STREAM-INF:" master += fmt.Sprintf("AVERAGE-BANDWIDTH=%d,", int(math.Min(bitrate*0.8, float64(def_video.Quality().AverageBitrate())))) master += fmt.Sprintf("BANDWIDTH=%d,", int(math.Min(bitrate, float64(def_video.Quality().MaxBitrate())))) master += fmt.Sprintf("RESOLUTION=%dx%d,", def_video.Width, def_video.Height) - var audio_codec = transcode_audio_codec - if def_audio != nil && audio_quality == AOriginal { - audio_codec = *def_audio.MimeCodec + + codecs := make([]string, 0) + if quality == Original { + if def_video.MimeCodec != nil { + codecs = append(codecs, *def_video.MimeCodec) + } + } else { + codecs = append(codecs, transcode_codec) } - if quality != Original { - master += fmt.Sprintf("CODECS=\"%s\",", strings.Join([]string{transcode_codec, audio_codec}, ",")) - } else if def_video.MimeCodec != nil { - master += fmt.Sprintf("CODECS=\"%s\",", strings.Join([]string{*def_video.MimeCodec, audio_codec}, ",")) + if aquality == AOriginal { + if def_audio != nil && def_audio.MimeCodec != nil { + codecs = append(codecs, *def_audio.MimeCodec) + } + } else { + codecs = append(codecs, transcode_audio_codec) + } + if len(codecs) > 0 { + master += fmt.Sprintf("CODECS=\"%s\",", strings.Join(codecs, ",")) + } + if def_audio != nil { + master += fmt.Sprintf("AUDIO=\"a-%s\",", string(aquality)) } - master += fmt.Sprintf("AUDIO=\"audio-%s\",", string(audio_quality)) master += "CLOSED-CAPTIONS=NONE\n" master += fmt.Sprintf("%d/%s/index.m3u8?clientId=%s\n", def_video.Index, quality, client) } @@ -206,7 +222,9 @@ func (fs *FileStream) GetMaster(client string) string { master += fmt.Sprintf("BANDWIDTH=%d,", quality.MaxBitrate()) master += fmt.Sprintf("RESOLUTION=%dx%d,", int(aspectRatio*float32(quality.Height())+0.5), quality.Height()) master += fmt.Sprintf("CODECS=\"%s\",", strings.Join([]string{transcode_codec, transcode_audio_codec}, ",")) - master += fmt.Sprintf("AUDIO=\"audio-%s\",", string(matchAudioQuality(quality))) + if def_audio != nil { + master += fmt.Sprintf("AUDIO=\"a-%s\",", string(matchAudioQuality(quality))) + } master += "CLOSED-CAPTIONS=NONE\n" master += fmt.Sprintf("%d/%s/index.m3u8?clientId=%s\n", def_video.Index, quality, client) } diff --git a/transcoder/src/info.go b/transcoder/src/info.go index 3860aa63..e6761b97 100644 --- a/transcoder/src/info.go +++ b/transcoder/src/info.go @@ -17,7 +17,7 @@ import ( "gopkg.in/vansante/go-ffprobe.v2" ) -const InfoVersion = 3 +const InfoVersion = 4 type Versions struct { Info int32 `json:"info" db:"ver_info"` @@ -98,6 +98,8 @@ type Audio struct { Codec string `json:"codec" db:"codec"` /// The codec of this stream (defined as the RFC 6381). MimeCodec *string `json:"mimeCodec" db:"mime_codec"` + /// The number of channels that stream has. + Channels int `json:"channels" db:"channels"` /// The average bitrate of the audio in bytes/s Bitrate uint32 `json:"bitrate" db:"bitrate"` /// Is this stream the default one of it's type? @@ -280,6 +282,7 @@ func RetriveMediaInfo(path string, sha string) (*MediaInfo, error) { Language: NullIfUnd(lang.String()), Codec: stream.CodecName, MimeCodec: GetMimeCodec(stream), + Channels: stream.Channels, Bitrate: ParseUint(cmp.Or(stream.BitRate, mi.Format.BitRate)), IsDefault: stream.Disposition.Default != 0, } diff --git a/transcoder/src/metadata.go b/transcoder/src/metadata.go index df53eee3..4a79c912 100644 --- a/transcoder/src/metadata.go +++ b/transcoder/src/metadata.go @@ -318,8 +318,8 @@ func (s *MetadataService) storeFreshMetadata(ctx context.Context, path string, s tx.Exec( ctx, ` - insert into gocoder.audios(sha, idx, title, language, codec, mime_codec, is_default, bitrate) - values ($1, $2, $3, $4, $5, $6, $7, $8) + insert into gocoder.audios(sha, idx, title, language, codec, mime_codec, channels, is_default, bitrate) + values ($1, $2, $3, $4, $5, $6, $7, $8, $9) on conflict (sha, idx) do update set sha = excluded.sha, idx = excluded.idx, @@ -327,10 +327,11 @@ func (s *MetadataService) storeFreshMetadata(ctx context.Context, path string, s language = excluded.language, codec = excluded.codec, mime_codec = excluded.mime_codec, + channels = excluded.channels, is_default = excluded.is_default, bitrate = excluded.bitrate `, - ret.Sha, a.Index, a.Title, a.Language, a.Codec, a.MimeCodec, a.IsDefault, a.Bitrate, + ret.Sha, a.Index, a.Title, a.Language, a.Codec, a.MimeCodec, a.Channels, a.IsDefault, a.Bitrate, ) } for _, s := range ret.Subtitles {