Merge branch 'master' into fix/UppercaseRepoName

This commit is contained in:
Andre Wei 2024-10-21 13:17:24 +08:00 committed by GitHub
commit 5c1a21da24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 57 additions and 52 deletions

View File

@ -115,7 +115,7 @@ docker-platforms: ##@Cross_Compilation List supported platforms
.PHONY: docker-platforms .PHONY: docker-platforms
docker-build: ##@Cross_Compilation Cross-compile for any supported platform (check `make docker-platforms`) docker-build: ##@Cross_Compilation Cross-compile for any supported platform (check `make docker-platforms`)
docker build \ docker buildx build \
--platform $(PLATFORMS) \ --platform $(PLATFORMS) \
--build-arg GIT_TAG=${GIT_TAG} \ --build-arg GIT_TAG=${GIT_TAG} \
--build-arg GIT_SHA=${GIT_SHA} \ --build-arg GIT_SHA=${GIT_SHA} \
@ -127,7 +127,7 @@ docker-image: ##@Cross_Compilation Build Docker image, tagged as `deluan/navidro
@echo $(IMAGE_PLATFORMS) | grep -q "windows" && echo "ERROR: Windows is not supported for Docker builds" && exit 1 || true @echo $(IMAGE_PLATFORMS) | grep -q "windows" && echo "ERROR: Windows is not supported for Docker builds" && exit 1 || true
@echo $(IMAGE_PLATFORMS) | grep -q "darwin" && echo "ERROR: macOS is not supported for Docker builds" && exit 1 || true @echo $(IMAGE_PLATFORMS) | grep -q "darwin" && echo "ERROR: macOS is not supported for Docker builds" && exit 1 || true
@echo $(IMAGE_PLATFORMS) | grep -q "arm/v5" && echo "ERROR: Linux ARMv5 is not supported for Docker builds" && exit 1 || true @echo $(IMAGE_PLATFORMS) | grep -q "arm/v5" && echo "ERROR: Linux ARMv5 is not supported for Docker builds" && exit 1 || true
docker build \ docker buildx build \
--platform $(IMAGE_PLATFORMS) \ --platform $(IMAGE_PLATFORMS) \
--build-arg GIT_TAG=${GIT_TAG} \ --build-arg GIT_TAG=${GIT_TAG} \
--build-arg GIT_SHA=${GIT_SHA} \ --build-arg GIT_SHA=${GIT_SHA} \

View File

@ -18,8 +18,6 @@ import (
type FFmpeg interface { type FFmpeg interface {
Transcode(ctx context.Context, command, path string, maxBitRate, offset int) (io.ReadCloser, error) Transcode(ctx context.Context, command, path string, maxBitRate, offset int) (io.ReadCloser, error)
ExtractImage(ctx context.Context, path string) (io.ReadCloser, error) ExtractImage(ctx context.Context, path string) (io.ReadCloser, error)
ConvertToWAV(ctx context.Context, path string) (io.ReadCloser, error)
ConvertToFLAC(ctx context.Context, path string) (io.ReadCloser, error)
Probe(ctx context.Context, files []string) (string, error) Probe(ctx context.Context, files []string) (string, error)
CmdPath() (string, error) CmdPath() (string, error)
IsAvailable() bool IsAvailable() bool
@ -33,8 +31,6 @@ func New() FFmpeg {
const ( const (
extractImageCmd = "ffmpeg -i %s -an -vcodec copy -f image2pipe -" extractImageCmd = "ffmpeg -i %s -an -vcodec copy -f image2pipe -"
probeCmd = "ffmpeg %s -f ffmetadata" probeCmd = "ffmpeg %s -f ffmetadata"
createWavCmd = "ffmpeg -i %s -c:a pcm_s16le -f wav -"
createFLACCmd = "ffmpeg -i %s -f flac -"
) )
type ffmpeg struct{} type ffmpeg struct{}
@ -55,16 +51,6 @@ func (e *ffmpeg) ExtractImage(ctx context.Context, path string) (io.ReadCloser,
return e.start(ctx, args) return e.start(ctx, args)
} }
func (e *ffmpeg) ConvertToWAV(ctx context.Context, path string) (io.ReadCloser, error) {
args := createFFmpegCommand(createWavCmd, path, 0, 0)
return e.start(ctx, args)
}
func (e *ffmpeg) ConvertToFLAC(ctx context.Context, path string) (io.ReadCloser, error) {
args := createFFmpegCommand(createFLACCmd, path, 0, 0)
return e.start(ctx, args)
}
func (e *ffmpeg) Probe(ctx context.Context, files []string) (string, error) { func (e *ffmpeg) Probe(ctx context.Context, files []string) (string, error) {
if _, err := ffmpegCmd(); err != nil { if _, err := ffmpegCmd(); err != nil {
return "", err return "", err
@ -153,31 +139,26 @@ func (j *ffCmd) wait() {
// Path will always be an absolute path // Path will always be an absolute path
func createFFmpegCommand(cmd, path string, maxBitRate, offset int) []string { func createFFmpegCommand(cmd, path string, maxBitRate, offset int) []string {
split := strings.Split(fixCmd(cmd), " ") var args []string
var parts []string for _, s := range fixCmd(cmd) {
for _, s := range split {
if strings.Contains(s, "%s") { if strings.Contains(s, "%s") {
s = strings.ReplaceAll(s, "%s", path) s = strings.ReplaceAll(s, "%s", path)
parts = append(parts, s) args = append(args, s)
if offset > 0 && !strings.Contains(cmd, "%t") { if offset > 0 && !strings.Contains(cmd, "%t") {
parts = append(parts, "-ss", strconv.Itoa(offset)) args = append(args, "-ss", strconv.Itoa(offset))
} }
} else { } else {
s = strings.ReplaceAll(s, "%t", strconv.Itoa(offset)) s = strings.ReplaceAll(s, "%t", strconv.Itoa(offset))
s = strings.ReplaceAll(s, "%b", strconv.Itoa(maxBitRate)) s = strings.ReplaceAll(s, "%b", strconv.Itoa(maxBitRate))
parts = append(parts, s) args = append(args, s)
} }
} }
return args
return parts
} }
func createProbeCommand(cmd string, inputs []string) []string { func createProbeCommand(cmd string, inputs []string) []string {
split := strings.Split(fixCmd(cmd), " ")
var args []string var args []string
for _, s := range fixCmd(cmd) {
for _, s := range split {
if s == "%s" { if s == "%s" {
for _, inp := range inputs { for _, inp := range inputs {
args = append(args, "-i", inp) args = append(args, "-i", inp)
@ -189,18 +170,15 @@ func createProbeCommand(cmd string, inputs []string) []string {
return args return args
} }
func fixCmd(cmd string) string { func fixCmd(cmd string) []string {
split := strings.Split(cmd, " ") split := strings.Fields(cmd)
var result []string
cmdPath, _ := ffmpegCmd() cmdPath, _ := ffmpegCmd()
for _, s := range split { for i, s := range split {
if s == "ffmpeg" || s == "ffmpeg.exe" { if s == "ffmpeg" || s == "ffmpeg.exe" {
result = append(result, cmdPath) split[i] = cmdPath
} else {
result = append(result, s)
} }
} }
return strings.Join(result, " ") return split
} }
func ffmpegCmd() (string, error) { func ffmpegCmd() (string, error) {
@ -223,6 +201,7 @@ func ffmpegCmd() (string, error) {
return ffmpegPath, ffmpegErr return ffmpegPath, ffmpegErr
} }
// These variables are accessible here for tests. Do not use them directly in production code. Use ffmpegCmd() instead.
var ( var (
ffOnce sync.Once ffOnce sync.Once
ffmpegPath string ffmpegPath string

View File

@ -27,6 +27,10 @@ var _ = Describe("ffmpeg", func() {
args := createFFmpegCommand("ffmpeg -i %s -b:a %bk mp3 -", "/music library/file.mp3", 123, 0) args := createFFmpegCommand("ffmpeg -i %s -b:a %bk mp3 -", "/music library/file.mp3", 123, 0)
Expect(args).To(Equal([]string{"ffmpeg", "-i", "/music library/file.mp3", "-b:a", "123k", "mp3", "-"})) Expect(args).To(Equal([]string{"ffmpeg", "-i", "/music library/file.mp3", "-b:a", "123k", "mp3", "-"}))
}) })
It("handles extra spaces in the command string", func() {
args := createFFmpegCommand("ffmpeg -i %s -b:a %bk mp3 -", "/music library/file.mp3", 123, 0)
Expect(args).To(Equal([]string{"ffmpeg", "-i", "/music library/file.mp3", "-b:a", "123k", "mp3", "-"}))
})
Context("when command has time offset param", func() { Context("when command has time offset param", func() {
It("creates a valid command line with offset", func() { It("creates a valid command line with offset", func() {
args := createFFmpegCommand("ffmpeg -i %s -b:a %bk -ss %t mp3 -", "/music library/file.mp3", 123, 456) args := createFFmpegCommand("ffmpeg -i %s -b:a %bk -ss %t mp3 -", "/music library/file.mp3", 123, 456)
@ -48,4 +52,17 @@ var _ = Describe("ffmpeg", func() {
Expect(args).To(Equal([]string{"ffmpeg", "-i", "/music library/one.mp3", "-i", "/music library/two.mp3", "-f", "ffmetadata"})) Expect(args).To(Equal([]string{"ffmpeg", "-i", "/music library/one.mp3", "-i", "/music library/two.mp3", "-f", "ffmetadata"}))
}) })
}) })
When("ffmpegPath is set", func() {
It("returns the correct ffmpeg path", func() {
ffmpegPath = "/usr/bin/ffmpeg"
args := createProbeCommand(probeCmd, []string{"one.mp3"})
Expect(args).To(Equal([]string{"/usr/bin/ffmpeg", "-i", "one.mp3", "-f", "ffmetadata"}))
})
It("returns the correct ffmpeg path with spaces", func() {
ffmpegPath = "/usr/bin/with spaces/ffmpeg.exe"
args := createProbeCommand(probeCmd, []string{"one.mp3"})
Expect(args).To(Equal([]string{"/usr/bin/with spaces/ffmpeg.exe", "-i", "one.mp3", "-f", "ffmetadata"}))
})
})
}) })

View File

@ -162,10 +162,10 @@ func (s *playlists) parseM3U(ctx context.Context, pls *model.Playlist, baseDir s
} }
existing := make(map[string]int, len(found)) existing := make(map[string]int, len(found))
for idx := range found { for idx := range found {
existing[found[idx].Path] = idx existing[strings.ToLower(found[idx].Path)] = idx
} }
for _, path := range filteredLines { for _, path := range filteredLines {
idx, ok := existing[path] idx, ok := existing[strings.ToLower(path)]
if ok { if ok {
mfs = append(mfs, found[idx]) mfs = append(mfs, found[idx])
} else { } else {

View File

@ -142,6 +142,20 @@ var _ = Describe("Playlists", func() {
Expect(pls.Tracks[1].Path).To(Equal("test1.mp3")) Expect(pls.Tracks[1].Path).To(Equal("test1.mp3"))
Expect(pls.Tracks[2].Path).To(Equal("test2.mp3")) Expect(pls.Tracks[2].Path).To(Equal("test2.mp3"))
}) })
It("is case-insensitive when comparing paths", func() {
repo.data = []string{
"tEsT1.Mp3",
}
m3u := strings.Join([]string{
"TeSt1.mP3",
}, "\n")
f := strings.NewReader(m3u)
pls, err := ps.ImportM3U(ctx, f)
Expect(err).ToNot(HaveOccurred())
Expect(pls.Tracks).To(HaveLen(1))
Expect(pls.Tracks[0].Path).To(Equal("tEsT1.Mp3"))
})
}) })
}) })

View File

@ -0,0 +1,9 @@
-- +goose Up
create index if not exists media_file_sort_title on media_file(coalesce(nullif(sort_title,''),order_title));
create index if not exists album_sort_name on album(coalesce(nullif(sort_album_name,''),order_album_name));
create index if not exists artist_sort_name on artist(coalesce(nullif(sort_artist_name,''),order_artist_name));
-- +goose Down
drop index if exists media_file_sort_title;
drop index if exists album_sort_name;
drop index if exists artist_sort_name;

View File

@ -37,20 +37,6 @@ func (ff *MockFFmpeg) ExtractImage(context.Context, string) (io.ReadCloser, erro
return ff, nil return ff, nil
} }
func (ff *MockFFmpeg) ConvertToFLAC(context.Context, string) (io.ReadCloser, error) {
if ff.Error != nil {
return nil, ff.Error
}
return ff, nil
}
func (ff *MockFFmpeg) ConvertToWAV(context.Context, string) (io.ReadCloser, error) {
if ff.Error != nil {
return nil, ff.Error
}
return ff, nil
}
func (ff *MockFFmpeg) Probe(context.Context, []string) (string, error) { func (ff *MockFFmpeg) Probe(context.Context, []string) (string, error) {
if ff.Error != nil { if ff.Error != nil {
return "", ff.Error return "", ff.Error