Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MyMusicBoxApi/buildProd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go build -buildvcs=false -o ~/mymusicbox_production
2 changes: 1 addition & 1 deletion MyMusicBoxApi/database/playlistsong.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func (pdb *PostgresDb) FetchPlaylistSongs(ctx context.Context, playlistId int, l

query := `SELECT s.Id, s.Name, s.Path, s.ThumbnailPath, s.Duration, s.SourceId, s.UpdatedAt, s.CreatedAt FROM playlistsong ps
INNER JOIN song s ON s.id = ps.songid
WHERE ps.playlistid = $1 AND ps.position > $2`
WHERE ps.playlistid = $1 AND ps.position >= $2`

statement, err := pdb.connection.Prepare(query)
defer statement.Close()
Expand Down
13 changes: 12 additions & 1 deletion MyMusicBoxApi/database/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"database/sql"
"errors"
"fmt"
"musicboxapi/configuration"
"musicboxapi/logging"
"os"
"strings"
Expand All @@ -20,7 +21,13 @@ type PostgresDb struct {

func (pdb *PostgresDb) OpenConnection() (created bool) {

baseConnectionString := "user=postgres dbname=postgres password=%s host=127.0.0.1 port=5432 sslmode=disable"
baseConnectionString := ""

if configuration.Config.UseDevUrl {
baseConnectionString = "user=postgres dbname=postgres password=%s host=127.0.0.1 port=5433 sslmode=disable"
} else {
baseConnectionString = "user=postgres dbname=postgres password=%s host=127.0.0.1 port=5432 sslmode=disable"
}

connectionString := fmt.Sprintf(baseConnectionString, os.Getenv("POSTGRES_PASSWORD"))

Expand Down Expand Up @@ -68,6 +75,10 @@ func (pdb *PostgresDb) NonScalarQuery(query string, params ...any) (error error)

if err != nil {
logging.Error(fmt.Sprintf("[NonScalarQuery] Exec error: %s", err.Error()))
logging.Error(fmt.Sprintf("Query: %s", query))
for index := range params {
logging.Error(params[index])
}
return err
}

Expand Down
7 changes: 7 additions & 0 deletions MyMusicBoxApi/database/tasklog.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ func (pdb *PostgresDb) EndTaskLog(taskId int, nStatus int, data []byte) error {
return pdb.NonScalarQuery(query, nStatus, data, time.Now(), taskId)
}

func (pdb *PostgresDb) UpdateTaskLogError(params ...any) error {
query := `UPDATE TaskLog
SET Status = $1, OutputLog = $2, EndTime = $3
WHERE Id = $4`
return pdb.NonScalarQuery(query, params...)
}

func (pdb *PostgresDb) GetTaskLogs(ctx context.Context) ([]models.TaskLog, error) {
query := `SELECT Id, StartTime, EndTime, Status, OutputLog FROM TaskLog ORDER BY Id desc` // get the latest first

Expand Down
Binary file added MyMusicBoxApi/default/icon-128x128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion MyMusicBoxApi/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
)

require (
Expand All @@ -25,7 +26,7 @@ require (
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9
github.com/lrstanley/go-ytdlp v0.0.0-20250610000944-a2284ab714d8
github.com/lrstanley/go-ytdlp v1.1.1-0.20250710012800-43387ad4d4a9
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
Expand Down
22 changes: 22 additions & 0 deletions MyMusicBoxApi/http/tasklog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package http

import (
"fmt"
"musicboxapi/database"
"musicboxapi/models"

"github.com/gin-gonic/gin"
)

func FetchTaskLogs(ctx *gin.Context) {
db := database.PostgresDb{}
defer db.CloseConnection()

logs, err := db.GetTaskLogs(ctx.Request.Context())

if err != nil {
ctx.JSON(500, models.ErrorResponse(err.Error()))
}

ctx.JSON(200, models.OkResponse(logs, fmt.Sprintf("%d", len(logs))))
}
6 changes: 3 additions & 3 deletions MyMusicBoxApi/logging/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import (
)

func Info(a any) {
go log(fmt.Sprintf("\033[32m[API Info]\033[0m %s", a))
log(fmt.Sprintf("\033[32m[API Info]\033[0m %s", a))
}

func Warning(a any) {
go log(fmt.Sprintf("\033[33m[API Warning]\033[0m %s", a))
log(fmt.Sprintf("\033[33m[API Warning]\033[0m %s", a))
}

func Error(a any) {
go log(fmt.Sprintf("\033[31m[API Error]\033[0m %s", a))
log(fmt.Sprintf("\033[31m[API Error]\033[0m %s", a))
}

func log(text string) {
Expand Down
18 changes: 17 additions & 1 deletion MyMusicBoxApi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"musicboxapi/configuration"
"musicboxapi/http"
"musicboxapi/logging"
"os"

"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
Expand All @@ -14,7 +15,6 @@ import (

func main() {
// If yt-dlp isn't installed yet, download and cache it for further use.
// todo test....
go ytdlp.MustInstall(context.TODO(), nil)

configuration.LoadConfig()
Expand All @@ -32,13 +32,29 @@ func main() {
if configuration.Config.UseDevUrl {
engine.Use(cors.Default())
logging.Warning("CORS is enabled for all origins")
} else {

origin := os.Getenv("CORS_ORIGIN")

// Use Default cors
if len(origin) == 0 {
engine.Use(cors.Default())
logging.Warning("CORS is enabled for all origins")
} else {
strictCors := cors.New(cors.Config{
AllowAllOrigins: false,
AllowOrigins: []string{origin}, // move to env
})
engine.Use(strictCors)
}
}

apiv1Group := engine.Group(configuration.GetApiGroupUrl("v1"))
apiv1Group.GET("/songs", http.FetchSongs)
apiv1Group.GET("/playlist", http.FetchPlaylists)
apiv1Group.GET("/playlist/:playlistId", http.FetchPlaylistSongs)
apiv1Group.GET("/play/:sourceId", http.Play)
apiv1Group.GET("/tasklogs", http.FetchTaskLogs)

apiv1Group.POST("/playlist", http.InsertPlaylist)
apiv1Group.POST("/playlistsong/:playlistId/:songId", http.InsertPlaylistSong)
Expand Down
9 changes: 9 additions & 0 deletions MyMusicBoxApi/models/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package models

type YtdlpJsonResult struct {
Id string `json:"id"`
Title string `json:"title"`
Duration int `json:"duration"`
Playlist string `json:"playlist"`
PlaylistIndex int `json:"index"`
}
11 changes: 8 additions & 3 deletions MyMusicBoxApi/service/playlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ func downloadPlaylist(

playlistExists := false
playlistId := -1
downloadCounter := 0

for _, playlist := range existingPlaylists {
if playlist.Name == playlistNames[0] {
Expand All @@ -66,14 +65,21 @@ func downloadPlaylist(
_playlistId, _ := readLines(playlistIdFileName)

if !playlistExists {
playlistId, _ = db.InsertPlaylist(models.Playlist{
_newPlaylistId, err := db.InsertPlaylist(models.Playlist{
Name: playlistNames[0],
Description: "Custom playlist",
ThumbnailPath: fmt.Sprintf("%s.jpg", _playlistId[0]),
CreationDate: time.Now(),
IsPublic: true,
UpdatedAt: time.Now(),
})

if err != nil {
logging.Error(fmt.Sprintf("[Creating custom playlist error]: %s", err.Error()))
return
}

playlistId = _newPlaylistId
}

// Special case, thumbnail is written to root directory
Expand Down Expand Up @@ -112,7 +118,6 @@ func downloadPlaylist(
for id := range downloadCount {
name := names[id]
if canDownload(name) {
downloadCounter++
ytdlpInstance := defaultSettings.Clone()

result, err := ytdlpInstance.Run(context.Background(), fmt.Sprintf("https://www.youtube.com/watch?v=%s", ids[id]))
Expand Down
119 changes: 119 additions & 0 deletions MyMusicBoxApi/service/titleparser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package service

import (
"fmt"
"regexp"
"strings"
)

// V2 downloader using json

// testRun := ytdlp.New().
// DownloadArchive(archiveFileName).
// ForceIPv4().
// NoKeepVideo().
// SkipDownload().
// // ExtractAudio().
// // AudioQuality("0").
// // AudioFormat(fileExtension).
// // PostProcessorArgs("FFmpegExtractAudio:-b:a 160k").
// DownloadArchive(archiveFileName).
// // WriteThumbnail().
// // ConcurrentFragments(10).
// // ConvertThumbnails("jpg").
// // ForceIPv4().
// // Downloader("aria2c").
// // DownloaderArgs("aria2c:-x 16 -s 16 -j 16").
// // Output(storageFolderName+"/%(id)s.%(ext)s").
// ReplaceInMetadata("title", `["()]`, "").
// PrintToFile(`{"id": "%(id)s","title": "%(title)s", "playlist":"%(playlist_title)s", "duration": %(duration)s, "index":%(playlist_autonumber)s}`, idsFileName).
// Cookies("selenium/cookies_netscape")

// testRun.Run(context.Background(), downloadRequest.Url)

// rawJsons, _ := readLines(idsFileName)

// for index := range rawJsons {
// var jsonResult models.YtdlpJsonResult

// parseError := json.Unmarshal([]byte(FixJSONStringValues(rawJsons[index])), &jsonResult)

// if parseError != nil {
// logging.Error(parseError.Error())
// continue
// }

// logging.Info(fmt.Sprintf("%d %s %d %s", index, jsonResult.Title, jsonResult.PlaylistIndex, jsonResult.Playlist))
// }

// _dlp := ytdlp.New().
// DownloadArchive(archiveFileName).
// ForceIPv4().
// NoKeepVideo().
// SkipDownload().
// FlatPlaylist().
// WriteThumbnail().
// PrintToFile("%(id)s", idsFileName).
// PrintToFile("%(title)s", namesFileName).
// PrintToFile("%(duration)s", durationFileName).
// PrintToFile("%(playlist_title)s", playlistTitleFileName).
// PrintToFile("%(playlist_id)s", playlistIdFileName).
// Cookies("selenium/cookies_netscape")

// _dlp.Run(context.Background(), downloadRequest.Url)

// return

// FixJSONStringValues scans all JSON string values and escapes invalid characters.
func FixJSONStringValues(input string) string {
// Regex pattern to match string values like: "key": "value"
re := regexp.MustCompile(`"(?:[^"\\]|\\.)*?"\s*:\s*"(.*?)"`)

return strings.TrimSpace(re.ReplaceAllStringFunc(input, func(match string) string {
parts := strings.SplitN(match, ":", 2)
if len(parts) != 2 {
return match
}
key := strings.TrimSpace(parts[0])
rawValue := strings.TrimSpace(parts[1])

// Remove surrounding quotes
unquoted := strings.Trim(rawValue, `"`)

// Escape invalid characters inside the value
escaped := escapeUnsafeJSONCharacters(unquoted)

return fmt.Sprintf(`%s: "%s"`, key, escaped)
}))
}

// escapeUnsafeJSONCharacters escapes characters that would break JSON string parsing.
func escapeUnsafeJSONCharacters(s string) string {
var b strings.Builder
for _, r := range s {
switch r {
case '\\':
b.WriteString(`\\`)
case '"':
b.WriteString(`\"`)
case '\b':
b.WriteString(`\b`)
case '\f':
b.WriteString(`\f`)
case '\n':
b.WriteString(`\n`)
case '\r':
b.WriteString(`\r`)
case '\t':
b.WriteString(`\t`)
default:
if r < 0x20 {
// Escape control characters as unicode
fmt.Fprintf(&b, `\u%04x`, r)
} else {
b.WriteRune(r)
}
}
}
return b.String()
}
Loading