Compare commits

...

10 commits

Author SHA1 Message Date
ed7e1ddade Tag files 2023-11-12 22:10:31 -06:00
7a198a0273 🐛 Fail gracefully when none found 2023-11-11 18:51:38 -06:00
5881f3d538 Get tags from mb 2023-11-11 17:10:37 -06:00
739bed214c Add testify for simpler tests 2023-10-31 15:41:24 -05:00
0aa0ac284e 🛠 Added TernCall util 2023-10-29 11:58:00 -05:00
0a55d514fc 🚧 Saving tags for later writing 2023-10-29 11:14:44 -05:00
4990b09f67 🚚 Moving tracks to its own subpackage 2023-10-28 18:15:33 -05:00
04274dc788 📝 Add README 2023-10-01 13:41:41 -05:00
38202e58aa 📄 Add LICENSE 2023-10-01 13:34:44 -05:00
c87dc5e04f 🚧 More of fingerprinting 2023-09-24 22:34:30 -05:00
24 changed files with 598 additions and 49 deletions

View file

@ -0,0 +1,7 @@
© 2023 Dan Jones <danjones@goodevilgenius.org>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

23
README.md Normal file
View file

@ -0,0 +1,23 @@
# strip-beats
Imagine you have a folder full of music videos. You want to go through those videos, turn it into an audio file, tag them, and add it to your music collection.
This app aims to streamline that.
## Pre-requisites
- ffmpeg
- fpcalc (for fingerprinting audio so that we can identify it and automatically pull tags)
- mpv (for watching the video to ensure you're stipping out non-music elements, like an intro or outro)
- An account on acousticid.org (for querying the fingerprint to get data)
## Install
- Pull the repo
- `go install .`
Hopefully, I'll have binaries downloadable soon.
## Disclaimer
This does not work yet! Some features are implemented, but most are not yet. Have a look around at the code, but don't actually use this at all.

View file

@ -5,7 +5,7 @@ import (
"os"
"codeberg.org/danjones000/strip-beats/files"
"codeberg.org/danjones000/strip-beats/input/boolean"
"codeberg.org/danjones000/strip-beats/io/boolean"
"codeberg.org/danjones000/strip-beats/media"
"codeberg.org/danjones000/strip-beats/utils"
"github.com/rkoesters/xdg/trash"
@ -20,8 +20,10 @@ func PickNewFile() media.Probe {
}
func SetFile(path string) media.Probe {
resetTags()
f := media.ProbeFile(path)
file = &f
copyTagsFromFile()
return f
}
@ -70,6 +72,7 @@ func PickAgain() {
}
file = nil
tmpfile = nil
resetTags()
}
func Finish() {

16
app/convert.go Normal file
View file

@ -0,0 +1,16 @@
package app
import (
"fmt"
"codeberg.org/danjones000/strip-beats/media"
"codeberg.org/danjones000/strip-beats/utils"
)
func convert() {
in := utils.Tern(tmpfile == nil, file, tmpfile)
out, _ := media.ConvertAndTag(*in, tags)
fmt.Println(out)
quit()
}

View file

@ -2,6 +2,8 @@ package app
import (
"os"
p "path/filepath"
s "strings"
"codeberg.org/danjones000/strip-beats/media"
"codeberg.org/danjones000/strip-beats/utils"
@ -31,7 +33,10 @@ func validateNumber(input string, lastChar rune) bool {
}
func fadeFile() error {
tmp, err := os.CreateTemp("", "audio.*.mka")
base := p.Base(file.Format.Path)
ext := p.Ext(base)
base = s.TrimSuffix(base, ext)
tmp, err := os.CreateTemp("", base+".*.mka")
if err != nil {
return err
}

View file

@ -3,14 +3,14 @@ package app
import (
"fmt"
"codeberg.org/danjones000/strip-beats/input/list"
"codeberg.org/danjones000/strip-beats/io/list"
)
func (st AppStep) Title() string {
mustpick := "You need to pick a file"
switch st {
case Pick:
return "Pick a new show"
return "Pick a new file"
case Watch:
if file == nil {
return mustpick
@ -26,6 +26,11 @@ func (st AppStep) Title() string {
return mustpick
}
return fmt.Sprintf("Should we try to identify %s", file.ShortPath())
case Convert:
if file == nil {
return mustpick
}
return fmt.Sprintf("Convert %s?", file.ShortPath())
case Restart:
return "Forget current selection"
case Quit:
@ -54,6 +59,8 @@ func (st AppStep) Rune() rune {
return 'r'
case Print:
return 'a'
case Convert:
return 'c'
case Quit:
return 'q'
default:
@ -70,7 +77,7 @@ func mainMenu() AppStep {
if file == nil {
steps = []list.Option{Pick, Quit}
} else {
steps = []list.Option{Pick, Watch, Fade, Print, Quit}
steps = []list.Option{Pick, Watch, Fade, Print, Convert, Quit}
}
step := list.List("What would you like to do next?", steps, nil)

View file

@ -3,15 +3,48 @@ package app
import (
"fmt"
"codeberg.org/danjones000/strip-beats/io/boolean"
"codeberg.org/danjones000/strip-beats/io/list"
m "codeberg.org/danjones000/strip-beats/io/message"
"codeberg.org/danjones000/strip-beats/media"
"codeberg.org/danjones000/strip-beats/media/brainz"
"codeberg.org/danjones000/strip-beats/utils"
"github.com/akrennmair/slice"
)
type recOpt struct {
rec brainz.Recording
r rune
score float64
}
func (o recOpt) Title() string {
return o.rec.Title
}
func (o recOpt) Text() string {
return fmt.Sprintf(
"(Score %.2f) By %s - First Released %s - %s %s",
100*o.score,
o.rec.FirstArtist().Name,
o.rec.FirstReleaseDate,
o.rec.FirstGenre().Name,
utils.Tern(o.rec.Video, "(Video)", ""))
}
func (o recOpt) Rune() rune {
return o.r
}
func (o recOpt) Selected() func() {
return nil
}
func print() {
if file == nil {
PickFileWithConf()
}
fp, err := media.Fingerprint("/home/drj/MyFiles/Videos/WebShows/YouTube/Dolly_Parton_-_Topic/Just_Because_I_m_a_Woman.Dolly_Parton_-_Topic.Fmv-XQerVkM.webm")
fp, err := media.Fingerprint(file.Format.Path)
if err != nil {
panic(err)
}
@ -19,17 +52,136 @@ func print() {
if err != nil {
panic(err)
}
var recs []brainz.Recording
rec, ok := chooseRec(ids)
if !ok {
m.Message("Couldn't find a marching recording")
return
}
fmt.Println(rec.Title)
tags.Title = rec.Title
tags.MusicbrainzRecordingId = rec.Id
tags.Date = rec.FirstReleaseDate
tags.Genre = rec.FirstGenre().Name
tags.Artist = rec.FirstArtist().Name
tags.ArtistSort = rec.FirstArtist().SortName
tags.AcoustidId = rec.AcousticId
rel := findFirstRelease(rec)
if !boolean.Choose(fmt.Sprintf("Is %s album (%s %s) correct?", rel.Title, rel.Country, rel.Date)) {
rel = chooseRel(rec)
}
media := rel.Media[0]
track := media.Tracks[0]
full, err := brainz.GetReleaseWithMedia(rel.Id)
if err != nil {
panic(err)
}
albumArtist := full.ArtistCredit[0]
tags.MusicbrainzReleaseGroupId = full.ReleaseGroup.Id
tags.MusicbrainzAlbumId = rel.Id
tags.MusicbrainzAlbumArtistId = albumArtist.Artist.Id
tags.AlbumArtist = albumArtist.Name
tags.Album = rel.Title
if rel.Date != "" {
tags.Date = rel.Date
}
tags.AlbumArtistSort = albumArtist.Artist.SortName
tags.ReleaseCountry = rel.Country
if len(full.LabelInfo) > 0 {
tags.Label = full.LabelInfo[0].Label.Name
tags.MusicBrainzLabelId = full.LabelInfo[0].Label.Id
}
if len(rel.Genres) > 0 && rel.Genres[0].Name != "" {
tags.Genre = rel.Genres[0].Name
}
tags.Disc = media.Position
tags.DiscCount = len(full.Media)
tags.Track = track.Position
tags.TrackCount = media.TrackCount
}
func chooseRec(ids media.IdResults) (brainz.Recording, bool) {
var recs []list.Option
var rec brainz.Recording
var err error
i := 'a'
for _, res := range ids.Results {
for _, rec := range res.Recordings {
for _, rec = range res.Recordings {
err = brainz.FillRecording(&rec)
if err != nil {
panic(err)
}
recs = append(recs, rec)
rec.AcousticId = res.Id
recs = append(recs, recOpt{rec, i, res.Score})
if rec.Title != "" {
// Empty titles will be filtered out, so we don't need to increment them
i = i + 1
}
}
}
recs = slice.Filter(recs, func(opt list.Option) bool {
return opt.Title() != ""
})
fmt.Printf("%+v\n", recs)
if len(recs) < 1 {
return rec, false
}
return list.List("Which recording is the correct one?", recs, nil).(recOpt).rec, true
}
func findFirstRelease(rec brainz.Recording) brainz.Release {
var rel brainz.Release
for _, rel = range rec.Releases {
if rel.Date == rec.FirstReleaseDate {
return rel
}
}
if len(rec.Releases) > 0 {
return rec.Releases[0]
}
return brainz.Release{}
}
func chooseRel(rec brainz.Recording) brainz.Release {
var rels []list.Option
var rel brainz.Release
i := 'a'
for _, rel = range rec.Releases {
rels = append(rels, relOpt{rel, i})
if rel.Title != "" {
// Empty titles will be filtered out, so we don't need to increment them
i = i + 1
}
}
rels = slice.Filter(rels, func(opt list.Option) bool {
return opt.Title() != ""
})
return list.List("Which releases is the correct one?", rels, nil).(relOpt).rel
}
type relOpt struct {
rel brainz.Release
r rune
}
func (o relOpt) Title() string {
return o.rel.Title
}
func (o relOpt) Text() string {
return fmt.Sprintf("%s %s", o.rel.Country, o.rel.Date)
}
func (o relOpt) Rune() rune {
return o.r
}
func (o relOpt) Selected() func() {
return nil
}

View file

@ -4,7 +4,7 @@ import (
"fmt"
"os"
"codeberg.org/danjones000/strip-beats/input/boolean"
"codeberg.org/danjones000/strip-beats/io/boolean"
"codeberg.org/danjones000/strip-beats/media"
"codeberg.org/danjones000/strip-beats/media/brainz"
)
@ -16,6 +16,7 @@ const (
Watch
Fade
Print
Convert
Restart
Quit
)
@ -51,14 +52,17 @@ func testMb() {
}
func testPrint() {
SetFile("/home/drj/MyFiles/Videos/WebShows/YouTube/Dolly_Parton_-_Topic/Just_Because_I_m_a_Woman.Dolly_Parton_-_Topic.Fmv-XQerVkM.webm")
// SetFile("/home/drj/MyFiles/Videos/WebShows/YouTube/Dolly_Parton_-_Topic/Just_Because_I_m_a_Woman.Dolly_Parton_-_Topic.Fmv-XQerVkM.webm")
// SetFile("/home/drj/MyFiles/Videos/WebShows/YouTube/Whitney_Houston/I_Will_Always_Love_You_Ultimate_Collection_Edit.Whitney_Houston.rB7z_l8mBxw.mp4")
SetFile("/home/drj/MyFiles/Music/Original_Broadway_Cast_of_Hamilton/Hamilton/d02t12-We_Know.m4a")
// SetFile("/home/drj/MyFiles/Videos/WebShows/YouTube/KaceyMusgravesVEVO/Kacey_Musgraves_-_Biscuits-nGIUtLO_x8g.mp4")
// SetFile("/home/drj/MyFiles/Videos/WebShows/YouTube/Willie_Nelson_-_Topic/Too_Sick_To_Pray.Willie_Nelson_-_Topic.8QgBXo41j2E.webm")
print()
quit()
}
func Run(step AppStep) {
testPrint()
for step < Quit {
switch step {
case Pick:
@ -91,6 +95,9 @@ func Run(step AppStep) {
case Print:
print()
step = mainMenu()
case Convert:
convert()
step = mainMenu()
case Quit:
quit()
default:

21
app/tags.go Normal file
View file

@ -0,0 +1,21 @@
package app
import (
"errors"
t "codeberg.org/danjones000/strip-beats/media/tags"
)
var tags t.Tags
func resetTags() {
tags = t.Tags{}
}
func copyTagsFromFile() {
if file == nil {
panic(errors.New("Missing file"))
}
tags = file.FullTags()
}

View file

@ -1,5 +1,5 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
Copyright © 2023 Dan Jones <danjones@goodevilgenius.org>
*/
package cmd

View file

@ -12,7 +12,7 @@ import (
const (
AppName string = "strip-beats"
Version string = "0.1.0"
Version string = "0.1.1"
Url string = "https://codeberg.org/danjones000/strip-beats"
Email string = "danjones@goodevilgenius.org"
UserAgent string = AppName + "/" + Version + " (" + Url + "; " + Email + ")"

4
go.mod
View file

@ -13,21 +13,25 @@ require (
github.com/rivo/tview v0.0.0-20230826224341-9754ab44dc1c
github.com/rkoesters/xdg v0.0.1
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.7.0
github.com/u2takey/ffmpeg-go v0.5.0
golang.org/x/term v0.11.0
)
require (
github.com/aws/aws-sdk-go v1.38.20 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/gdamore/tcell/v2 v2.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/u2takey/go-utils v0.3.1 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/text v0.7.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

1
go.sum
View file

@ -117,6 +117,7 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

20
io/message/message.go Normal file
View file

@ -0,0 +1,20 @@
package message
import "github.com/rivo/tview"
func Message(text string) {
app := tview.NewApplication()
modal := tview.NewModal()
if text != "" {
modal.SetText(text)
}
modal.AddButtons([]string{"Ok"}).
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
app.Stop()
})
if err := app.SetRoot(modal, false).EnableMouse(true).Run(); err != nil {
panic(err)
}
}

View file

@ -1,6 +1,5 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
Copyright © 2023 Dan Jones <danjones@goodevilgenius.org>
*/
package main

View file

@ -12,6 +12,7 @@ import (
type Recording struct {
Id uuid.UUID
AcousticId uuid.UUID
Isrcs []string
FirstReleaseDate string `json:"first-release-date"`
Length int
@ -19,6 +20,45 @@ type Recording struct {
Video bool
Releases []Release
Genres []Genre
ArtistCredit []ArtistCredit `json:"artist-credit"`
}
func (r Recording) FirstArtist() Artist {
var a Artist
for _, ac := range r.ArtistCredit {
if ac.Artist.Name != "" {
return ac.Artist
}
}
for _, rel := range r.Releases {
for _, ac := range rel.ArtistCredit {
if ac.Artist.Name != "" {
return ac.Artist
}
}
}
if a.Name == "" {
a.Name = "Unknown Artist"
}
return a
}
func (r Recording) FirstGenre() Genre {
var g Genre
for _, g = range r.Genres {
if g.Name != "" {
return g
}
}
for _, rel := range r.Releases {
for _, g = range rel.Genres {
if g.Name != "" {
return g
}
}
}
return g
}
type Genre struct {
@ -27,21 +67,57 @@ type Genre struct {
}
type Release struct {
Id uuid.UUID
Country string
Date string
Media []Media
Status string
StatusId uuid.UUID `json:"status-id"`
ArtistCredit []ArtistCredit `json:"artist-credit"`
Title string
Genres []Genre
Id uuid.UUID
Asin string
Barcode string
Country string
Date string
Disambiguation string
Media []Media
Packaging string
PackagingId uuid.UUID `json:"packaging-id"`
Quality string
Status string
StatusId uuid.UUID `json:"status-id"`
ArtistCredit []ArtistCredit `json:"artist-credit"`
Title string
Genres []Genre
ReleaseGroup ReleaseGroup `json:"release-group"`
LabelInfo []LabelInfo `json:"label-info"`
// ReleaseEvents []ReleaseEvent `json:"release-events"`
}
type LabelInfo struct {
CatalogNumber string `json:"catalog-number"`
Label Label
}
type Label struct {
Id uuid.UUID
Name string
SortName string `json:"sort-name"`
Disambiguation string
TypeId string `json:"type-id"`
Type string
LabelCode int `json:"label-code"`
}
type ReleaseGroup struct {
Id uuid.UUID
Title string
ArtistCredit []ArtistCredit `json:"artist-credit"`
Disambiguation string
FirstReleaseDate string `json:"first-release-date"`
PrimaryType string `json:"primary-type"`
PrimaryTypeId uuid.UUID `json:"primary-type-id"`
SecondaryTypes []string `json:"secondary-types"`
SecondaryTypeIds []uuid.UUID `json:"secondary-type-ids"`
}
type ArtistCredit struct {
Name string
Artist Artist
Name string
Artist Artist
JoinPhrase string
}
type Artist struct {
@ -56,6 +132,7 @@ type Artist struct {
type Media struct {
FormatId uuid.UUID `json:"format-id"`
Position int
Title string
TrackOffset int `json:"track-offset"`
Format string
TrackCount int `json:"track-count"`
@ -70,6 +147,25 @@ type Track struct {
Length int
}
func GetReleaseWithMedia(id uuid.UUID) (Release, error) {
rel := Release{Id: id}
url := fmt.Sprintf("https://musicbrainz.org/ws/2/release/%s", id)
resp, err := h.GetWithQuery(url, u.Values{
"fmt": []string{"json"},
"inc": []string{"artist-credits+discids+labels+release-groups"}})
if err != nil {
return rel, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return rel, err
}
err = json.Unmarshal(body, &rel)
return rel, err
}
func GetRecording(id string) (Recording, error) {
u, err := uuid.Parse(id)
rec := Recording{Id: u}

View file

@ -3,12 +3,142 @@ package media
import (
"errors"
"fmt"
p "path/filepath"
"strconv"
s "strings"
"codeberg.org/danjones000/strip-beats/config"
t "codeberg.org/danjones000/strip-beats/media/tags"
"codeberg.org/danjones000/strip-beats/utils"
"github.com/google/uuid"
ffmpeg "github.com/u2takey/ffmpeg-go"
)
func ConvertAndTag(in Probe, tags t.Tags) (string, error) {
st := in.GetFirstAcceptableAudio()
if st == nil {
st = in.GetFirstAudio()
}
if st == nil {
return "", errors.New("Can't find an audio stream")
}
conf := config.GetConfig()
codec := utils.Tern(st.isAcceptableCodec(), "copy", conf.FfEncoder)
target := utils.Tern(st.isAcceptableCodec(), st.CodecName, conf.Codec)
base := p.Base(in.Format.Path)
ext := p.Ext(base)
base = s.TrimSuffix(base, ext)
ext = conf.CodecExt[target]
out := p.Join(conf.SavePath, base+"."+ext)
input := ffmpeg.Input(in.Format.Path).Get(strconv.Itoa(st.Index))
args := ffmpeg.KwArgs{"c:a": codec}
output := input.Output(out, args, getMetadataArgs(ext, tags)).GlobalArgs("-y")
return out, output.Run()
}
func getMetadataArgs(ext string, tags t.Tags) ffmpeg.KwArgs {
var meta []string
args := ffmpeg.KwArgs{}
switch ext {
case "opus":
fallthrough
case "flac":
fallthrough
case "ogg":
meta = append(meta, mapMetaKeys(getOggMeta(tags))...)
case "m4a":
fallthrough
case "mp4":
meta = append(meta, mapMetaKeys(getMp4Meta(tags))...)
case "mp3":
// @todo meta = append(meta, mapMetaKeys(getMp3Meta(tags))...)
}
meta = append(meta, "comment=Processed by "+config.UserAgent)
args["metadata"] = meta
return args
}
func mapMetaKeys(meta map[string]string) []string {
out := []string{}
for k, v := range meta {
if v != "" {
out = append(out, fmt.Sprintf("%s=%s", k, v))
}
}
return out
}
func getOggMeta(tags t.Tags) map[string]string {
meta := map[string]string{}
meta["TITLE"] = tags.Title
meta["ARTIST"] = tags.Artist
meta["ALBUM_ARTIST"] = tags.AlbumArtist
meta["ALBUMARTIST"] = tags.AlbumArtist
meta["ALBUM"] = tags.Album
meta["DATE"] = tags.Date
year, _, _ := s.Cut(tags.Date, "-")
meta["YEAR"] = year
meta["URL"] = tags.Url
meta["PURL"] = tags.Url
meta["URI"] = tags.Url
meta["ARTISTSORT"] = tags.ArtistSort
meta["ALBUMARTISTSORT"] = tags.AlbumArtistSort
meta["RELEASECOUNTRY"] = tags.ReleaseCountry
meta["LABEL"] = tags.Label
meta["GENRE"] = tags.Genre
meta["DISC"] = utils.Tern(tags.Disc > 0, strconv.Itoa(tags.Disc), "")
meta["DISCTOTAL"] = utils.Tern(tags.DiscCount > 0, strconv.Itoa(tags.DiscCount), "")
meta["TRACK"] = utils.Tern(tags.Track > 0, strconv.Itoa(tags.Track), "")
meta["TRACKTOTAL"] = utils.Tern(tags.TrackCount > 0, strconv.Itoa(tags.TrackCount), "")
if tags.AcoustidId != uuid.Nil {
meta["ACOUSTID_ID"] = tags.AcoustidId.String()
}
if tags.MusicbrainzReleaseGroupId != uuid.Nil {
meta["MUSICBRAINZ_RELEASEGROUPID"] = tags.MusicbrainzReleaseGroupId.String()
}
if tags.MusicbrainzAlbumId != uuid.Nil {
meta["MUSICBRAINZ_ALBUMID"] = tags.MusicbrainzAlbumId.String()
}
if tags.MusicbrainzAlbumArtistId != uuid.Nil {
meta["MUSICBRAINZ_ALBUMARTISTID"] = tags.MusicbrainzAlbumArtistId.String()
}
if tags.MusicbrainzRecordingId != uuid.Nil {
meta["MUSICBRAINZ_RECORDINGID"] = tags.MusicbrainzRecordingId.String()
}
if tags.MusicBrainzLabelId != uuid.Nil {
meta["MUSICBRAINZ_LABELID"] = tags.MusicBrainzLabelId.String()
}
return meta
}
func getMp4Meta(tags t.Tags) map[string]string {
meta := map[string]string{}
meta["title"] = tags.Title
meta["artist"] = tags.Artist
meta["album_artist"] = tags.AlbumArtist
meta["album"] = tags.Album
year, _, _ := s.Cut(tags.Date, "-")
meta["year"] = year
meta["description"] = tags.Url
meta["genre"] = tags.Genre
meta["network"] = tags.Label
if tags.Disc > 0 {
meta["disc"] = fmt.Sprintf("%d", tags.Disc) + utils.Tern(tags.DiscCount > 0, fmt.Sprintf("/%d", tags.DiscCount), "")
}
if tags.Track > 0 {
meta["track"] = fmt.Sprintf("%d", tags.Track) + utils.Tern(tags.TrackCount > 0, fmt.Sprintf("/%d", tags.TrackCount), "")
}
return meta
}
func TrimWithFade(in Probe, out string, start, stop, up, down float64) error {
// -ss (start) -t (end) -af afade=t=in:st=(start):d=(up),afade=t=out:st=(downstart):d=(down)
st := in.GetFirstAcceptableAudio()

View file

@ -12,6 +12,7 @@ import (
"codeberg.org/danjones000/strip-beats/config"
"codeberg.org/danjones000/strip-beats/media/brainz"
h "codeberg.org/danjones000/strip-beats/utils/http"
"github.com/google/uuid"
)
type FPrint struct {
@ -45,7 +46,7 @@ type IdResults struct {
}
type IdResult struct {
Id string
Id uuid.UUID
Score float64
Recordings []brainz.Recording
}

View file

@ -4,6 +4,7 @@ import (
"encoding/json"
"codeberg.org/danjones000/strip-beats/config"
"codeberg.org/danjones000/strip-beats/media/tags"
"codeberg.org/danjones000/strip-beats/utils"
"dario.cat/mergo"
"github.com/akrennmair/slice"
@ -99,7 +100,7 @@ type Stream struct {
StartTime float64 `json:"start_time,string"`
Duration float64 `json:",string"`
DurationTs int `json:"duration_ts"`
Tags Tags
Tags tags.Tags
}
func (st Stream) isWantedCodec() bool {
@ -124,26 +125,10 @@ type Format struct {
Size int `json:",string"`
BitRate int `json:"bit_rate,string"`
Score int `json:"probe_score"`
Tags Tags
Tags tags.Tags
}
type Tags struct {
MajorBrand string `json:"major_brand"`
Title string
Artist string
AlbumArtist string `json:"album_artist"`
Album string
Date string
Encoder string
Comment string
Description string
Composer string
Genre string
Disc string
Track string
}
func (pr Probe) FullTags() Tags {
func (pr Probe) FullTags() tags.Tags {
t := pr.Format.Tags
s := pr.WantedAudioStream()
if s != nil {

35
media/tags/tags.go Normal file
View file

@ -0,0 +1,35 @@
package tags
import (
"github.com/google/uuid"
)
type Tags struct {
Title string
Artist string
AlbumArtist string `json:"album_artist"`
Album string
Date string
Comment string
Description string
Synopsis string
Url string `json:"purl"`
AcoustidId uuid.UUID `json:"ACOUSTID_ID"`
MusicbrainzReleaseGroupId uuid.UUID `json:"MUSICBRAINZ_RELEASEGROUPID"`
MusicbrainzAlbumId uuid.UUID `json:"MUSICBRAINZ_ALBUMID"`
MusicbrainzAlbumArtistId uuid.UUID `json:"MUSICBRAINZ_ALBUMARTISTID"`
MusicbrainzRecordingId uuid.UUID
MusicBrainzLabelId uuid.UUID
ArtistSort string
AlbumArtistSort string
ReleaseCountry string
Label string
Composer string
Genre string
Disc int `json:"-"`
DiscCount int
Track int `json:"-"`
TrackCount int
TrackStr string `json:"track"`
DiscStr string `json:"disc"`
}

View file

@ -29,3 +29,17 @@ func HourMinSecToSeconds(time string) (float64, error) {
dur, _ := t.ParseDuration(f)
return dur.Seconds(), nil
}
func Tern[V any](choice bool, one, two V) V {
if choice {
return one
}
return two
}
func TernCall[V any](choice bool, one, two func() V) V {
if choice {
return one()
}
return two()
}

23
utils/utils_tern_test.go Normal file
View file

@ -0,0 +1,23 @@
package utils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestTern(t *testing.T) {
assert.Equal(t, Tern(true, 5, 2), 5)
assert.Equal(t, Tern(false, 5, 2), 2)
}
func TestTernCall(t *testing.T) {
five := func() int {
return 5
}
two := func() int {
return 2
}
assert.Equal(t, TernCall(true, five, two), 5)
assert.Equal(t, TernCall(false, five, two), 2)
}