mycode:

package main

import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	"gopkg.in/yaml.v2"
	"errors"
	"fmt"
	"github.com/beevik/etree"
	"io"
	"io/ioutil"
	"math"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/abema/go-mp4"
	"github.com/grafov/m3u8"
	"bufio"
)

const (
	defaultId   = "0"
	prefetchKey = "skd://itunes.apple.com/P000000000/s1/e1"
)

var (
	forbiddenNames = regexp.MustCompile(`[/\\<>:"|?*]`)
)
type Config struct {
    MediaUserToken string `yaml:"media-user-token"`
    SaveLrcFile    bool `yaml:"save-lrc-file"`   
    EmbedLrc       bool `yaml:"embed-lrc"`
    EmbedCover     bool `yaml:"embed-cover"`
    CoverSize      string `yaml:"cover-size"`
    CoverFormat      string `yaml:"cover-format"`
    AlacSaveFolder      string `yaml:"alac-save-folder"`
    AtmosSaveFolder      string `yaml:"atmos-save-folder"`
}

var config Config
var oktrackNum int = 0
var trackTotalnum int = 0

type SampleInfo struct {
	data      []byte
	duration  uint32
	descIndex uint32
}

type SongInfo struct {
	r         io.ReadSeeker
	alacParam *Alac
	samples   []SampleInfo
}

func loadConfig() error {
    // 读取config.yaml文件内容  
    data, err := ioutil.ReadFile("config.yaml")
    if err != nil {
        return err
    }
    // 将yaml解析到config变量中
    err = yaml.Unmarshal(data, &config)
    if err != nil {
        return err
    }
    return nil
}

func (s *SongInfo) Duration() (ret uint64) {
	for i := range s.samples {
		ret += uint64(s.samples[i].duration)
	}
	return
}

func (*Alac) GetType() mp4.BoxType {
	return BoxTypeAlac()
}

func fileExists(path string) (bool, error) {
	f, err := os.Stat(path)
	if err == nil {
		return !f.IsDir(), nil
	} else if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func writeM4a(w *mp4.Writer, info *SongInfo, meta *AutoGenerated, data []byte, trackNum, trackTotal int) error {
	index := trackNum - 1
	{ // ftyp
		box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeFtyp()})
		if err != nil {
			return err
		}
		_, err = mp4.Marshal(w, &mp4.Ftyp{
			MajorBrand:   [4]byte{'M', '4', 'A', ' '},
			MinorVersion: 0,
			CompatibleBrands: []mp4.CompatibleBrandElem{
				{CompatibleBrand: [4]byte{'M', '4', 'A', ' '}},
				{CompatibleBrand: [4]byte{'m', 'p', '4', '2'}},
				{CompatibleBrand: mp4.BrandISOM()},
				{CompatibleBrand: [4]byte{0, 0, 0, 0}},
			},
		}, box.Context)
		if err != nil {
			return err
		}
		_, err = w.EndBox()
		if err != nil {
			return err
		}
	}

	const chunkSize uint32 = 5
	duration := info.Duration()
	numSamples := uint32(len(info.samples))
	var stco *mp4.BoxInfo

	{ // moov
		_, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMoov()})
		if err != nil {
			return err
		}
		box, err := mp4.ExtractBox(info.r, nil, mp4.BoxPath{mp4.BoxTypeMoov()})
		if err != nil {
			return err
		}
		moovOri := box[0]

		{ // mvhd
			_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMvhd()})
			if err != nil {
				return err
			}

			oriBox, err := mp4.ExtractBoxWithPayload(info.r, moovOri, mp4.BoxPath{mp4.BoxTypeMvhd()})
			if err != nil {
				return err
			}
			mvhd := oriBox[0].Payload.(*mp4.Mvhd)
			if mvhd.Version == 0 {
				mvhd.DurationV0 = uint32(duration)
			} else {
				mvhd.DurationV1 = duration
			}

			_, err = mp4.Marshal(w, mvhd, oriBox[0].Info.Context)
			if err != nil {
				return err
			}

			_, err = w.EndBox()
			if err != nil {
				return err
			}
		}

		{ // trak
			_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeTrak()})
			if err != nil {
				return err
			}

			box, err := mp4.ExtractBox(info.r, moovOri, mp4.BoxPath{mp4.BoxTypeTrak()})
			if err != nil {
				return err
			}
			trakOri := box[0]

			{ // tkhd
				_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeTkhd()})
				if err != nil {
					return err
				}

				oriBox, err := mp4.ExtractBoxWithPayload(info.r, trakOri, mp4.BoxPath{mp4.BoxTypeTkhd()})
				if err != nil {
					return err
				}
				tkhd := oriBox[0].Payload.(*mp4.Tkhd)
				if tkhd.Version == 0 {
					tkhd.DurationV0 = uint32(duration)
				} else {
					tkhd.DurationV1 = duration
				}
				tkhd.SetFlags(0x7)

				_, err = mp4.Marshal(w, tkhd, oriBox[0].Info.Context)
				if err != nil {
					return err
				}

				_, err = w.EndBox()
				if err != nil {
					return err
				}
			}

			{ // mdia
				_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMdia()})
				if err != nil {
					return err
				}

				box, err := mp4.ExtractBox(info.r, trakOri, mp4.BoxPath{mp4.BoxTypeMdia()})
				if err != nil {
					return err
				}
				mdiaOri := box[0]

				{ // mdhd
					_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMdhd()})
					if err != nil {
						return err
					}

					oriBox, err := mp4.ExtractBoxWithPayload(info.r, mdiaOri, mp4.BoxPath{mp4.BoxTypeMdhd()})
					if err != nil {
						return err
					}
					mdhd := oriBox[0].Payload.(*mp4.Mdhd)
					if mdhd.Version == 0 {
						mdhd.DurationV0 = uint32(duration)
					} else {
						mdhd.DurationV1 = duration
					}

					_, err = mp4.Marshal(w, mdhd, oriBox[0].Info.Context)
					if err != nil {
						return err
					}

					_, err = w.EndBox()
					if err != nil {
						return err
					}
				}

				{ // hdlr
					oriBox, err := mp4.ExtractBox(info.r, mdiaOri, mp4.BoxPath{mp4.BoxTypeHdlr()})
					if err != nil {
						return err
					}

					err = w.CopyBox(info.r, oriBox[0])
					if err != nil {
						return err
					}
				}

				{ // minf
					_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMinf()})
					if err != nil {
						return err
					}

					box, err := mp4.ExtractBox(info.r, mdiaOri, mp4.BoxPath{mp4.BoxTypeMinf()})
					if err != nil {
						return err
					}
					minfOri := box[0]

					{ // smhd, dinf
						boxes, err := mp4.ExtractBoxes(info.r, minfOri, []mp4.BoxPath{
							{mp4.BoxTypeSmhd()},
							{mp4.BoxTypeDinf()},
						})
						if err != nil {
							return err
						}

						for _, b := range boxes {
							err = w.CopyBox(info.r, b)
							if err != nil {
								return err
							}
						}
					}

					{ // stbl
						_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeStbl()})
						if err != nil {
							return err
						}

						{ // stsd
							box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeStsd()})
							if err != nil {
								return err
							}
							_, err = mp4.Marshal(w, &mp4.Stsd{EntryCount: 1}, box.Context)
							if err != nil {
								return err
							}

							{ // alac
								_, err = w.StartBox(&mp4.BoxInfo{Type: BoxTypeAlac()})
								if err != nil {
									return err
								}

								_, err = w.Write([]byte{
									0, 0, 0, 0, 0, 0, 0, 1,
									0, 0, 0, 0, 0, 0, 0, 0})
								if err != nil {
									return err
								}

								err = binary.Write(w, binary.BigEndian, uint16(info.alacParam.NumChannels))
								if err != nil {
									return err
								}

								err = binary.Write(w, binary.BigEndian, uint16(info.alacParam.BitDepth))
								if err != nil {
									return err
								}

								_, err = w.Write([]byte{0, 0})
								if err != nil {
									return err
								}

								err = binary.Write(w, binary.BigEndian, info.alacParam.SampleRate)
								if err != nil {
									return err
								}

								_, err = w.Write([]byte{0, 0})
								if err != nil {
									return err
								}

								box, err := w.StartBox(&mp4.BoxInfo{Type: BoxTypeAlac()})
								if err != nil {
									return err
								}

								_, err = mp4.Marshal(w, info.alacParam, box.Context)
								if err != nil {
									return err
								}

								_, err = w.EndBox()
								if err != nil {
									return err
								}

								_, err = w.EndBox()
								if err != nil {
									return err
								}
							}

							_, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						{ // stts
							box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeStts()})
							if err != nil {
								return err
							}

							var stts mp4.Stts
							for _, sample := range info.samples {
								if len(stts.Entries) != 0 {
									last := &stts.Entries[len(stts.Entries)-1]
									if last.SampleDelta == sample.duration {
										last.SampleCount++
										continue
									}
								}
								stts.Entries = append(stts.Entries, mp4.SttsEntry{
									SampleCount: 1,
									SampleDelta: sample.duration,
								})
							}
							stts.EntryCount = uint32(len(stts.Entries))

							_, err = mp4.Marshal(w, &stts, box.Context)
							if err != nil {
								return err
							}

							_, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						{ // stsc
							box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeStsc()})
							if err != nil {
								return err
							}

							if numSamples%chunkSize == 0 {
								_, err = mp4.Marshal(w, &mp4.Stsc{
									EntryCount: 1,
									Entries: []mp4.StscEntry{
										{
											FirstChunk:             1,
											SamplesPerChunk:        chunkSize,
											SampleDescriptionIndex: 1,
										},
									},
								}, box.Context)
							} else {
								_, err = mp4.Marshal(w, &mp4.Stsc{
									EntryCount: 2,
									Entries: []mp4.StscEntry{
										{
											FirstChunk:             1,
											SamplesPerChunk:        chunkSize,
											SampleDescriptionIndex: 1,
										}, {
											FirstChunk:             numSamples/chunkSize + 1,
											SamplesPerChunk:        numSamples % chunkSize,
											SampleDescriptionIndex: 1,
										},
									},
								}, box.Context)
							}

							_, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						{ // stsz
							box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeStsz()})
							if err != nil {
								return err
							}

							stsz := mp4.Stsz{SampleCount: numSamples}
							for _, sample := range info.samples {
								stsz.EntrySize = append(stsz.EntrySize, uint32(len(sample.data)))
							}

							_, err = mp4.Marshal(w, &stsz, box.Context)
							if err != nil {
								return err
							}

							_, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						{ // stco
							box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeStco()})
							if err != nil {
								return err
							}

							l := (numSamples + chunkSize - 1) / chunkSize
							_, err = mp4.Marshal(w, &mp4.Stco{
								EntryCount:  l,
								ChunkOffset: make([]uint32, l),
							}, box.Context)

							stco, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						_, err = w.EndBox()
						if err != nil {
							return err
						}
					}

					_, err = w.EndBox()
					if err != nil {
						return err
					}
				}

				_, err = w.EndBox()
				if err != nil {
					return err
				}
			}

			_, err = w.EndBox()
			if err != nil {
				return err
			}
		}

		{ // udta
			ctx := mp4.Context{UnderUdta: true}
			_, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeUdta(), Context: ctx})
			if err != nil {
				return err
			}

			{ // meta
				ctx.UnderIlstMeta = true

				_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMeta(), Context: ctx})
				if err != nil {
					return err
				}

				_, err = mp4.Marshal(w, &mp4.Meta{}, ctx)
				if err != nil {
					return err
				}

				{ // hdlr
					_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeHdlr(), Context: ctx})
					if err != nil {
						return err
					}

					_, err = mp4.Marshal(w, &mp4.Hdlr{
						HandlerType: [4]byte{'m', 'd', 'i', 'r'},
						Reserved:    [3]uint32{0x6170706c, 0, 0},
					}, ctx)
					if err != nil {
						return err
					}

					_, err = w.EndBox()
					if err != nil {
						return err
					}
				}

				{ // ilst
					ctx.UnderIlst = true

					_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeIlst(), Context: ctx})
					if err != nil {
						return err
					}

					marshalData := func(val interface{}) error {
						_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeData()})
						if err != nil {
							return err
						}

						var boxData mp4.Data
						switch v := val.(type) {
						case string:
							boxData.DataType = mp4.DataTypeStringUTF8
							boxData.Data = []byte(v)
						case uint8:
							boxData.DataType = mp4.DataTypeSignedIntBigEndian
							boxData.Data = []byte{v}
						case uint32:
							boxData.DataType = mp4.DataTypeSignedIntBigEndian
							boxData.Data = make([]byte, 4)
							binary.BigEndian.PutUint32(boxData.Data, v)
						case []byte:
							boxData.DataType = mp4.DataTypeBinary
							boxData.Data = v
						default:
							panic("unsupported value")
						}

						_, err = mp4.Marshal(w, &boxData, ctx)
						if err != nil {
							return err
						}

						_, err = w.EndBox()
						return err
					}

					addMeta := func(tag mp4.BoxType, val interface{}) error {
						_, err = w.StartBox(&mp4.BoxInfo{Type: tag})
						if err != nil {
							return err
						}

						err = marshalData(val)
						if err != nil {
							return err
						}

						_, err = w.EndBox()
						return err
					}

					addExtendedMeta := func(name string, val interface{}) error {
						ctx.UnderIlstFreeMeta = true

						_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxType{'-', '-', '-', '-'}, Context: ctx})
						if err != nil {
							return err
						}

						{
							_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxType{'m', 'e', 'a', 'n'}, Context: ctx})
							if err != nil {
								return err
							}

							_, err = w.Write([]byte{0, 0, 0, 0})
							if err != nil {
								return err
							}

							_, err = io.WriteString(w, "com.apple.iTunes")
							if err != nil {
								return err
							}

							_, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						{
							_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxType{'n', 'a', 'm', 'e'}, Context: ctx})
							if err != nil {
								return err
							}

							_, err = w.Write([]byte{0, 0, 0, 0})
							if err != nil {
								return err
							}

							_, err = io.WriteString(w, name)
							if err != nil {
								return err
							}

							_, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						err = marshalData(val)
						if err != nil {
							return err
						}

						ctx.UnderIlstFreeMeta = false

						_, err = w.EndBox()
						return err
					}

					err = addMeta(mp4.BoxType{'\251', 'n', 'a', 'm'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.Name)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'\251', 'a', 'l', 'b'}, meta.Data[0].Attributes.Name)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'\251', 'A', 'R', 'T'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.ArtistName)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'\251', 'w', 'r', 't'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.ComposerName)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'\251', 'd', 'a', 'y'}, strings.Split(meta.Data[0].Attributes.ReleaseDate, "-")[0])
					if err != nil {
						return err
					}

					// cnID, err := strconv.ParseUint(meta.Data[0].Relationships.Tracks.Data[index].ID, 10, 32)
					// if err != nil {
					// 	return err
					// }

					// err = addMeta(mp4.BoxType{'c', 'n', 'I', 'D'}, uint32(cnID))
					// if err != nil {
					// 	return err
					// }

					err = addExtendedMeta("ISRC", meta.Data[0].Relationships.Tracks.Data[index].Attributes.Isrc)
					if err != nil {
						return err
					}

					if len(meta.Data[0].Relationships.Tracks.Data[index].Attributes.GenreNames) > 0 {
						err = addMeta(mp4.BoxType{'\251', 'g', 'e', 'n'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.GenreNames[0])
						if err != nil {
							return err
						}
					}

					if len(meta.Data) > 0 {
						album := meta.Data[0]

						err = addMeta(mp4.BoxType{'a', 'A', 'R', 'T'}, album.Attributes.ArtistName)
						if err != nil {
							return err
						}

						err = addMeta(mp4.BoxType{'c', 'p', 'r', 't'}, album.Attributes.Copyright)
						if err != nil {
							return err
						}

						var isCpil uint8
						if album.Attributes.IsCompilation {
							isCpil = 1
						}
						err = addMeta(mp4.BoxType{'c', 'p', 'i', 'l'}, isCpil)
						if err != nil {
							return err
						}

						err = addExtendedMeta("LABEL", album.Attributes.RecordLabel)
						if err != nil {
							return err
						}

						err = addExtendedMeta("UPC", album.Attributes.Upc)
						if err != nil {
							return err
						}

						// plID, err := strconv.ParseUint(album.ID, 10, 32)
						// if err != nil {
						// 	return err
						// }

						// err = addMeta(mp4.BoxType{'p', 'l', 'I', 'D'}, uint32(plID))
						// if err != nil {
						// 	return err
						// }
					}

					// if len(meta.Data[0].Relationships.Artists.Data) > 0 {
					// 	atID, err := strconv.ParseUint(meta.Data[0].Relationships.Artists.Data[index].ID, 10, 32)
					// 	if err != nil {
					// 		return err
					// 	}

					// 	err = addMeta(mp4.BoxType{'a', 't', 'I', 'D'}, uint32(atID))
					// 	if err != nil {
					// 		return err
					// 	}
					// }

					trkn := make([]byte, 8)
					binary.BigEndian.PutUint32(trkn, uint32(trackNum))
					binary.BigEndian.PutUint16(trkn[4:], uint16(trackTotal))
					err = addMeta(mp4.BoxType{'t', 'r', 'k', 'n'}, trkn)
					if err != nil {
						return err
					}

					// disk := make([]byte, 8)
					// binary.BigEndian.PutUint32(disk, uint32(meta.Attributes.DiscNumber))
					// err = addMeta(mp4.BoxType{'d', 'i', 's', 'k'}, disk)
					// if err != nil {
					// 	return err
					// }

					ctx.UnderIlst = false

					_, err = w.EndBox()
					if err != nil {
						return err
					}
				}

				ctx.UnderIlstMeta = false
				_, err = w.EndBox()
				if err != nil {
					return err
				}
			}

			ctx.UnderUdta = false
			_, err = w.EndBox()
			if err != nil {
				return err
			}
		}

		_, err = w.EndBox()
		if err != nil {
			return err
		}
	}

	{
		box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMdat()})
		if err != nil {
			return err
		}

		_, err = mp4.Marshal(w, &mp4.Mdat{Data: data}, box.Context)
		if err != nil {
			return err
		}

		mdat, err := w.EndBox()

		var realStco mp4.Stco

		offset := mdat.Offset + mdat.HeaderSize
		for i := uint32(0); i < numSamples; i++ {
			if i%chunkSize == 0 {
				realStco.EntryCount++
				realStco.ChunkOffset = append(realStco.ChunkOffset, uint32(offset))
			}
			offset += uint64(len(info.samples[i].data))
		}

		_, err = stco.SeekToPayload(w)
		if err != nil {
			return err
		}
		_, err = mp4.Marshal(w, &realStco, box.Context)
		if err != nil {
			return err
		}
	}

	return nil
}

func decryptSong(info *SongInfo, keys []string, manifest *AutoGenerated, filename string, trackNum, trackTotal int) error {
	// fmt.Printf("%d-bit / %d Hz\n", info.bitDepth, info.bitRate)
	conn, err := net.Dial("tcp", "127.0.0.1:10020")
	if err != nil {
		return err
	}
	defer conn.Close()
	var decrypted []byte
	var lastIndex uint32 = math.MaxUint8

	fmt.Println("Decrypt start.")
	for _, sp := range info.samples {
		if lastIndex != sp.descIndex {
			if len(decrypted) != 0 {
				_, err := conn.Write([]byte{0, 0, 0, 0})
				if err != nil {
					return err
				}
			}
			keyUri := keys[sp.descIndex]
			id := manifest.Data[0].Relationships.Tracks.Data[trackNum-1].ID
			if keyUri == prefetchKey {
				id = defaultId
			}

			_, err := conn.Write([]byte{byte(len(id))})
			if err != nil {
				return err
			}
			_, err = io.WriteString(conn, id)
			if err != nil {
				return err
			}

			_, err = conn.Write([]byte{byte(len(keyUri))})
			if err != nil {
				return err
			}
			_, err = io.WriteString(conn, keyUri)
			if err != nil {
				return err
			}
		}
		lastIndex = sp.descIndex

		err := binary.Write(conn, binary.LittleEndian, uint32(len(sp.data)))
		if err != nil {
			return err
		}
		fmt.Printf("Sample size: %d\n", len(sp.data))

		_, err = conn.Write(sp.data)
		if err != nil {
			return err
		}

		de := make([]byte, len(sp.data))
		_, err = io.ReadFull(conn, de)
		if err != nil {
			return err
		}

		decrypted = append(decrypted, de...)
	}
	_, _ = conn.Write([]byte{0, 0, 0, 0, 0})

	fmt.Println("Decrypt finished.")

	file, err := os.Create(filename)
	if err != nil {
		panic(err)
	}
	defer file.Close()

	_, err = file.Write(decrypted)
	if err != nil {
		panic(err)
	}

	return nil
	// return writeM4a(mp4.NewWriter(create), info, manifest, decrypted, trackNum, trackTotal)
}

func decryptALAConlySong(info *SongInfo, keys []string, manifest *AutoGenerated, filename string, trackNum, trackTotal int) error {
	//fmt.Printf("%d-bit / %d Hz\n", info.bitDepth, info.bitRate)
	conn, err := net.Dial("tcp", "127.0.0.1:10020")
	if err != nil {
		return err
	}
	defer conn.Close()
	var decrypted []byte
	var lastIndex uint32 = math.MaxUint8

	fmt.Println("Decrypt start.")
	for _, sp := range info.samples {
		if lastIndex != sp.descIndex {
			if len(decrypted) != 0 {
				_, err := conn.Write([]byte{0, 0, 0, 0})
				if err != nil {
					return err
				}
			}
			keyUri := keys[sp.descIndex]
			id := manifest.Data[0].Relationships.Tracks.Data[trackNum-1].ID
			if keyUri == prefetchKey {
				id = defaultId
			}

			_, err := conn.Write([]byte{byte(len(id))})
			if err != nil {
				return err
			}
			_, err = io.WriteString(conn, id)
			if err != nil {
				return err
			}

			_, err = conn.Write([]byte{byte(len(keyUri))})
			if err != nil {
				return err
			}
			_, err = io.WriteString(conn, keyUri)
			if err != nil {
				return err
			}
		}
		lastIndex = sp.descIndex

		err := binary.Write(conn, binary.LittleEndian, uint32(len(sp.data)))
		if err != nil {
			return err
		}

		_, err = conn.Write(sp.data)
		if err != nil {
			return err
		}

		de := make([]byte, len(sp.data))
		_, err = io.ReadFull(conn, de)
		if err != nil {
			return err
		}

		decrypted = append(decrypted, de...)
	}
	_, _ = conn.Write([]byte{0, 0, 0, 0, 0})

	fmt.Println("Decrypt finished.")

	create, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer create.Close()

	if info == nil || info.alacParam == nil{
  		return errors.New("nil pointer dereference detected")
	}

	return writeM4a(mp4.NewWriter(create), info, manifest, decrypted, trackNum, trackTotal)
}

func checkUrl(url string) (string, string) {
	pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/album|\/album\/.+))\/(?:id)?(\d[^\D]+)(?:$|\?)`)
	matches := pat.FindAllStringSubmatch(url, -1)
	if matches == nil {
		return "", ""
	} else {
		return matches[0][1], matches[0][2]
	}
}

func checkUrlPlaylist(url string) (string, string) {
	pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/playlist|\/playlist\/.+))\/(?:id)?(pl\.[\w-]+)(?:$|\?)`)
	matches := pat.FindAllStringSubmatch(url, -1)

	if matches == nil {
		return "", ""
	} else {
		return matches[0][1], matches[0][2]
	}
}

func getMeta(albumId string, token string, storefront string) (*AutoGenerated, error) {
	var mtype string
	var page int
	if strings.Contains(albumId, "pl.") {
		mtype = "playlists"
	} else {
		mtype = "albums"
	}
	req, err := http.NewRequest("GET", fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/%s/%s", storefront, mtype, albumId), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
	req.Header.Set("Origin", "https://music.apple.com")
	query := url.Values{}
	query.Set("omit[resource]", "autos")
	query.Set("include", "tracks,artists,record-labels")
	query.Set("include[songs]", "artists")
	query.Set("fields[artists]", "name")
	query.Set("fields[albums:albums]", "artistName,artwork,name,releaseDate,url")
	query.Set("fields[record-labels]", "name")
	// query.Set("l", "en-gb")
	req.URL.RawQuery = query.Encode()
	do, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer do.Body.Close()
	if do.StatusCode != http.StatusOK {
		return nil, errors.New(do.Status)
	}
	obj := new(AutoGenerated)
	err = json.NewDecoder(do.Body).Decode(&obj)
	if err != nil {
		return nil, err
	}
	if strings.Contains(albumId, "pl.") {
		obj.Data[0].Attributes.ArtistName = "Apple Music"
		if len(obj.Data[0].Relationships.Tracks.Next) > 0 {
			page = 0
			for {
				page = page + 100
				pageStr := strconv.Itoa(page)
				req, err := http.NewRequest("GET", fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/%s/%s/tracks?offset=%s", storefront, mtype, albumId, pageStr), nil)
				if err != nil {
					return nil, err
				}
				req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
				req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
				req.Header.Set("Origin", "https://music.apple.com")
				do, err := http.DefaultClient.Do(req)
				if err != nil {
					return nil, err
				}
				defer do.Body.Close()
				if do.StatusCode != http.StatusOK {
					return nil, errors.New(do.Status)
				}
				obj2 := new(AutoGeneratedTrack)
				err = json.NewDecoder(do.Body).Decode(&obj2)
				if err != nil {
					return nil, err
				}
				for _, value := range obj2.Data {
					obj.Data[0].Relationships.Tracks.Data = append(obj.Data[0].Relationships.Tracks.Data, value)
				}
				if len(obj2.Next) == 0 {
					break
				}
			}
		}
	}
	return obj, nil
}

func getSongLyrics(songId string, storefront string, token string, userToken string) (string, error) {
	req, err := http.NewRequest("GET",
		fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/songs/%s/lyrics", storefront, songId), nil)
	if err != nil {
		return "", err
	}
	req.Header.Set("Origin", "https://music.apple.com")
	req.Header.Set("Referer", "https://music.apple.com/")
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	cookie := http.Cookie{Name: "media-user-token", Value: userToken}
	req.AddCookie(&cookie)
	do, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer do.Body.Close()
	obj := new(SongLyrics)
	err = json.NewDecoder(do.Body).Decode(&obj)
	if obj.Data != nil {
		return obj.Data[0].Attributes.Ttml, nil
	} else {
		return "", errors.New("failed to get lyrics")
	}
}

func writeCover(sanAlbumFolder, url string) error {
	covPath := filepath.Join(sanAlbumFolder, "cover." + config.CoverFormat)
	exists, err := fileExists(covPath)
	if err != nil {
		fmt.Println("Failed to check if cover exists.")
		return err
	}
	if exists {
		return nil
	}
	if config.CoverFormat == "png" {
		re := regexp.MustCompile(`\{w\}x\{h\}`)
		parts := re.Split(url, 2)
		url = parts[0] + "{w}x{h}" + strings.Replace(parts[1], ".jpg", ".png", 1)
	}
	url = strings.Replace(url, "{w}x{h}", config.CoverSize, 1)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return err
	}
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
	do, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer do.Body.Close()
	if do.StatusCode != http.StatusOK {
		errors.New(do.Status)
	}
	f, err := os.Create(covPath)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = io.Copy(f, do.Body)
	if err != nil {
		return err
	}
	return nil
}

func writeLyrics(sanAlbumFolder, filename string, lrc string) error {
	lyricspath := filepath.Join(sanAlbumFolder, filename)
	f, err := os.Create(lyricspath)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = f.WriteString(lrc)
	if err != nil {
		return err
	}
	return nil
}

func rip(albumId string, token string, storefront string, userToken string, albumURL string) error {
	meta, err := getMeta(albumId, token, storefront)
	if err != nil {
		fmt.Println("Failed to get album metadata.\n")
		return err
	}
	singerFoldername := fmt.Sprintf("%s", meta.Data[0].Attributes.ArtistName)
	if strings.HasSuffix(singerFoldername, ".") {
		singerFoldername = strings.ReplaceAll(singerFoldername, ".", "")
	}
	singerFoldername = strings.TrimSpace(singerFoldername)
	singerFolder := filepath.Join(config.AtmosSaveFolder, forbiddenNames.ReplaceAllString(singerFoldername, "_"))
	albumFolder := fmt.Sprintf("%s [Dolby Atmos]", meta.Data[0].Attributes.Name)
	if strings.HasSuffix(albumFolder, ".") {
		albumFolder = strings.ReplaceAll(albumFolder, ".", "")
	}
	albumFolder = strings.TrimSpace(albumFolder)
	sanAlbumFolder := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(albumFolder, "_"))
	os.MkdirAll(sanAlbumFolder, os.ModePerm)
	fmt.Println(singerFoldername)
	fmt.Println(albumFolder)
	// New functionality: write the album URL to album_link.txt
    if err := writeAlbumURL(sanAlbumFolder, albumURL); err != nil { 
        fmt.Println("Error saving album URL:", err)
        // Decide if you want to return the error and halt or just log it and continue
    }
	err = writeCover(sanAlbumFolder, meta.Data[0].Attributes.Artwork.URL)
	if err != nil {
		fmt.Println("Failed to write cover.")
	}
	trackTotal := len(meta.Data[0].Relationships.Tracks.Data)
	for trackNum, track := range meta.Data[0].Relationships.Tracks.Data {
		trackNum++
		trackTotalnum += 1
		fmt.Printf("Track %d of %d:\n", trackNum, trackTotal)
		manifest, err := getInfoFromAdam(track.ID, token, storefront)
		if err != nil {
			fmt.Println("Failed to get manifest.\n", err)
			continue
		}
		if manifest.Attributes.ExtendedAssetUrls.EnhancedHls == "" {
			fmt.Println("Unavailable in ALAC.")
			continue
		}

		trackUrl, keys, codecSuffix, selectedCodec, err := extractMedia(manifest.Attributes.ExtendedAssetUrls.EnhancedHls)
		if err != nil {
			fmt.Println("Failed to extract info from manifest.\n", err)
			continue
		}

		// selectedVariant := strings.ReplaceAll(codecSuffix, ".m3u8", "")
		// New lines to extract the desired codec part and construct filenames 
		newCodecSuffix := strings.SplitN(codecSuffix, "_", 3)[2]  // This will extract the desired codec suffix part
		fmt.Println("newCodecSuffix: ", newCodecSuffix)
		filename := fmt.Sprintf("%02d. %s [%s].ec3", trackNum, forbiddenNames.ReplaceAllString(track.Attributes.Name, ""), newCodecSuffix)
		m4afilename := fmt.Sprintf("%02d. %s [%s].m4a", trackNum, forbiddenNames.ReplaceAllString(track.Attributes.Name, ""), newCodecSuffix)
		lrcFilename := fmt.Sprintf("%02d. %s.lrc", trackNum, forbiddenNames.ReplaceAllString(track.Attributes.Name, "_"))
		trackPath := filepath.Join(sanAlbumFolder, filename)
		m4atrackPath := filepath.Join(sanAlbumFolder, m4afilename)
		// m4atrackPath := strings.Replace(trackPath, "."+strings.Split(selectedCodec, ".")[0], ".m4a", 1) // Generate m4a path
		exists, err := fileExists(trackPath)
		m4aexists, errs := fileExists(m4atrackPath)
		if err != nil {
			fmt.Println("Failed to check if track exists.")
		}
		if errs != nil {
			fmt.Println("Failed to check if m4atrack exists.")
		}
		if exists || m4aexists {
			fmt.Println("Track or M4atrack already exists locally.")
			oktrackNum += 1
			continue
		}
		var lrc string = ""
		if userToken != "your-media-user-token" && (config.EmbedLrc || config.SaveLrcFile) {
			ttml, err := getSongLyrics(track.ID, storefront, token, userToken)
			if err != nil {
				fmt.Println("Failed to get lyrics")
			} else {
				lrc, err = conventTTMLToLRC(ttml)
				if err != nil {
					fmt.Printf("Failed to parse lyrics: %s \n", err)
				} else {
					if config.SaveLrcFile {
						err := writeLyrics(sanAlbumFolder, lrcFilename, lrc)
						if err != nil {
							fmt.Printf("Failed to write lyrics")
						}
						if !config.EmbedLrc {
							lrc = ""
						}
					}
				}
			}
		}
		info, err := extractSong(trackUrl)
		if err != nil {
			fmt.Println("Failed to extract track.", err)
			continue
		}
		samplesOk := true
		for samplesOk {
			for _, i := range info.samples {
				if int(i.descIndex) >= len(keys) {
					fmt.Println("Decryption size mismatch.")
					samplesOk = false
				}
			}
			break
		}
		if !samplesOk {
			continue
		}
		// Check if the codec is ec-3 for further processing
		if selectedCodec == "ec-3" {
			// Decrypt the song and encapsulate/decrypt
			err = decryptSong(info, keys, meta, trackPath, trackNum, trackTotal)
			if err != nil {
				fmt.Println("Failed to decrypt track.\n", err)
				continue
			}

			index := trackNum - 1
			
			tags := []string{
				"tool=",
				fmt.Sprintf("lyrics=%s", lrc),
				fmt.Sprintf("album=%s", meta.Data[0].Attributes.Name),
				fmt.Sprintf("title=%s", meta.Data[0].Relationships.Tracks.Data[index].Attributes.Name),
				fmt.Sprintf("artist=%s", meta.Data[0].Relationships.Tracks.Data[index].Attributes.ArtistName),
				fmt.Sprintf("genre=%s", meta.Data[0].Relationships.Tracks.Data[index].Attributes.GenreNames[0]),
				fmt.Sprintf("created=%s", meta.Data[0].Attributes.ReleaseDate),
				fmt.Sprintf("album_artist=%s", meta.Data[0].Attributes.ArtistName),
				fmt.Sprintf("composer=%s", meta.Data[0].Relationships.Tracks.Data[index].Attributes.ComposerName),
				fmt.Sprintf("copyright=%s", meta.Data[0].Attributes.Copyright),
				fmt.Sprintf("ISRC=%s", meta.Data[0].Relationships.Tracks.Data[index].Attributes.Isrc),
				fmt.Sprintf("UPC=%s", meta.Data[0].Attributes.Upc),
				fmt.Sprintf("track=%d/%d", trackNum, trackTotal),
			}
			if config.EmbedCover {
				tags = append(tags, fmt.Sprintf("cover=%s/cover.%s", sanAlbumFolder, config.CoverFormat))
			}
			
			// tags := generateTags(meta, track, lrc, sanAlbumFolder, trackNum, trackTotal) // helper function for tags
			tagsString := strings.Join(tags, ":")
			cmd := exec.Command("MP4Box", "-add", trackPath, "-name", fmt.Sprintf("1=%s", meta.Data[0].Relationships.Tracks.Data[index].Attributes.Name), "-itags", tagsString, "-brand", "mp42", "-ab", "dby1", m4atrackPath)
			fmt.Printf("Encapsulating %s into %s\n", filepath.Base(trackPath), filepath.Base(m4atrackPath))
			if err := cmd.Run(); err != nil {
				fmt.Printf("Error encapsulating file: %v\n", err)
				continue
			}
			fmt.Printf("Deleting original EC3 file: %s\n", filepath.Base(trackPath))
			if err := os.Remove(trackPath); err != nil {
				fmt.Printf("Error deleting file: %v\n", err)
				continue
			}
			fmt.Printf("Successfully processed and deleted %s\n", filepath.Base(trackPath))
		} else {
			// If codec is not the ec-3:
			resp, err := http.Get(trackUrl)
			if err != nil {
				fmt.Println("Failed to download track.")
				continue
			}
			defer resp.Body.Close()
			
			f, err := os.Create(m4atrackPath)
			if err != nil {
				fmt.Printf("Error creating file %s: %v\n", m4atrackPath, err)
				continue
			}
			defer f.Close()
			
			_, err = io.Copy(f, resp.Body)
			if err != nil {
				fmt.Printf("Error saving file %s: %v\n", m4atrackPath, err)
				continue
			}
			fmt.Println("Successfully downloaded and saved track as m4a.")

			err = decryptALAConlySong(info, keys, meta, trackPath, trackNum, trackTotal)
			if err != nil {
				fmt.Println("Failed to decrypt track.\n", err)
				continue
			}

			tags := []string{
				fmt.Sprintf("lyrics=%s", lrc),
			}
			if config.EmbedCover {
				tags = append(tags, fmt.Sprintf("cover=%s/cover.%s", sanAlbumFolder, config.CoverFormat))
			}
			tagsString := strings.Join(tags, ":")
			cmd := exec.Command("MP4Box", "-itags", tagsString, trackPath)
			if err := cmd.Run(); err != nil {
				fmt.Printf("Embed failed: %v\n", err)
				continue
			}
		oktrackNum += 1
		}
	}
	return err
}

func writeAlbumURL(sanAlbumFolder string, url string) error {
    albumLinkFile := filepath.Join(sanAlbumFolder, "apple_music_link.txt")
    err := ioutil.WriteFile(albumLinkFile, []byte(url), 0644)
    if err != nil {
        fmt.Println("Failed to write album URL to file")
        return err
    }
    fmt.Printf("Apple Music URL saved to %s\n", albumLinkFile)
    return nil
}

func main() {
	err := loadConfig()
	if err != nil {
		fmt.Printf("load config failed: %v", err)
		return
	}
	token, err := getToken()
	if err != nil {
		fmt.Println("Failed to get token.")
		return
	}
	albumTotal := len(os.Args[1:])
	for albumNum, url := range os.Args[1:] {
		fmt.Printf("Album %d of %d:\n", albumNum+1, albumTotal)
		var storefront, albumId string
		if strings.Contains(url, "/playlist/") {
			storefront, albumId = checkUrlPlaylist(url)
		} else {
			storefront, albumId = checkUrl(url)
		}
		if albumId == "" {
			fmt.Printf("Invalid URL: %s\n", url)
			continue
		}
		err := rip(albumId, token, storefront, config.MediaUserToken, url)
		if err != nil {
			fmt.Println("Album failed.")
			fmt.Println(err)
		}
	}
	fmt.Printf("=======  Completed %d/%d ###### %d errors!! =======\n", oktrackNum, trackTotalnum, trackTotalnum-oktrackNum)
}

func conventTTMLToLRC(ttml string) (string, error) {
	parsedTTML := etree.NewDocument()
	err := parsedTTML.ReadFromString(ttml)
	if err != nil {
		return "", err
	}
	var lrcLines []string
	for _, item := range parsedTTML.FindElement("tt").FindElement("body").ChildElements() {
		for _, lyric := range item.ChildElements() {
			var h, m, s, ms int
			if lyric.SelectAttr("begin") == nil {
				return "", errors.New("no synchronised lyrics")
			}
			if strings.Contains(lyric.SelectAttr("begin").Value, ":") {
				_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d:%d:%d.%d", &h, &m, &s, &ms)
				if err != nil {
					_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d:%d.%d", &m, &s, &ms)
					h = 0
				}
			} else {
				_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d.%d", &s, &ms)
				h, m = 0, 0
			}
			if err != nil {
				return "", err
			}
			var text string
			if lyric.SelectAttr("text") == nil {
				var textTmp []string
				for _, span := range lyric.Child {
					if _, ok := span.(*etree.CharData); ok {
						textTmp = append(textTmp, span.(*etree.CharData).Data)
					} else {
						textTmp = append(textTmp, span.(*etree.Element).Text())
					}
				}
				text = strings.Join(textTmp, "")
			} else {
				text = lyric.SelectAttr("text").Value
			}
			m += h * 60
			ms = ms / 10
			lrcLines = append(lrcLines, fmt.Sprintf("[%02d:%02d.%02d]%s", m, s, ms, text))
		}
	}
	return strings.Join(lrcLines, "\n"), nil
}

func extractMedia(b string) (string, []string, string, string, error) {
	masterUrl, err := url.Parse(b)
	if err != nil {
		return "", nil, "", "", err
	}
	resp, err := http.Get(b)
	if err != nil {
		return "", nil, "", "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return "", nil, "", "", errors.New(resp.Status)
	}
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", nil, "", "", err
	}
	masterString := string(body)
	from, listType, err := m3u8.DecodeFrom(strings.NewReader(masterString), true)
	if err != nil || listType != m3u8.MASTER {
		return "", nil, "", "", errors.New("m3u8 not of master type")
	}
	master := from.(*m3u8.MasterPlaylist)
	var selectedVariant *m3u8.Variant
	variants := master.Variants

	// Display available codecs to the user
	fmt.Println("Available audio streams:")
	for i, variant := range variants {
		fmt.Printf("[%d] Codec: %s, Average Bandwidth: %d, URI: %s\n", i, variant.Codecs, variant.AverageBandwidth, variant.URI)
	}

	reader := bufio.NewReader(os.Stdin)
	fmt.Print("Enter the number of the audio stream you want to download: ")
	input, err := reader.ReadString('\n')
	if err != nil {
		return "", nil, "", "", err
	}
	input = strings.TrimSpace(input)
	selectedIndex, err := strconv.Atoi(input)
	if err != nil {
		return "", nil, "", "", err
	}
	if selectedIndex < 0 || selectedIndex >= len(variants) {
		return "", nil, "", "", errors.New("Invalid selection")
	}
	selectedVariant = variants[selectedIndex]

	selectedCodec := selectedVariant.Codecs
	streamUrl, err := masterUrl.Parse(selectedVariant.URI)
	if err != nil {
		return "", nil, "", "", err
	}

	var keys []string
	keys = append(keys, prefetchKey)
	streamUrl.Path = strings.TrimSuffix(streamUrl.Path, ".m3u8") + "_m.mp4"
	regex := regexp.MustCompile(`(skd?://[^"]*)`)
	matches := regex.FindAllStringSubmatch(masterString, -1)
	for _, match := range matches {
		if strings.HasSuffix(match[1], "c24") || strings.HasSuffix(match[1], "c6") {
			keys = append(keys, match[1])
		}
	}

	// Generate the suffix based on the selected codec
	// codecSuffix := selectedVariant.Codecs
	// segments := strings.Split(selectedVariant.URI, "")
	// var codecSuffix string
	// if len(segments) > 0 && strings.HasSuffix(selectedVariant.Codecs, ".m3u8") {
	// if len(segments) > 0 {
		// codecSuffix = strings.TrimSuffix(segments[len(segments)-1], ".m3u8")
	// }
	codecSuffix := strings.TrimSuffix(filepath.Base(selectedVariant.URI), ".m3u8")
	fmt.Println("codecSuffix:", codecSuffix)
	// codecSuffix = strings.SplitN(codecSuffix, "", 3)[2]

	return streamUrl.String(), keys, codecSuffix, selectedCodec, nil
}

func extractSong(url string) (*SongInfo, error) {
	fmt.Println("Downloading...")
	track, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer track.Body.Close()
	if track.StatusCode != http.StatusOK {
		return nil, errors.New(track.Status)
	}
	rawSong, err := ioutil.ReadAll(track.Body)
	if err != nil {
		return nil, err
	}
	fmt.Println("Downloaded.")

	f := bytes.NewReader(rawSong)

	trex, err := mp4.ExtractBoxWithPayload(f, nil, []mp4.BoxType{
		mp4.BoxTypeMoov(),
		mp4.BoxTypeMvex(),
		mp4.BoxTypeTrex(),
	})
	if err != nil || len(trex) != 1 {
		return nil, err
	}
	trexPay := trex[0].Payload.(*mp4.Trex)

	stbl, err := mp4.ExtractBox(f, nil, []mp4.BoxType{
		mp4.BoxTypeMoov(),
		mp4.BoxTypeTrak(),
		mp4.BoxTypeMdia(),
		mp4.BoxTypeMinf(),
		mp4.BoxTypeStbl(),
	})
	if err != nil || len(stbl) != 1 {
		return nil, err
	}

	// enca, err := mp4.ExtractBoxWithPayload(f, stbl[0], []mp4.BoxType{
	// 	mp4.BoxTypeStsd(),
	// 	mp4.BoxTypeEnca(),
	// })
	// if err != nil {
	// 	return nil, err
	// }

	// aalac, err := mp4.ExtractBoxWithPayload(f, &enca[0].Info,
	// 	[]mp4.BoxType{mp4.StrToBoxType("dec3")})
	// if err != nil || len(aalac) != 1 {
	// 	return nil, err
	// }

	extracted := &SongInfo{
		r: f,
		// alacParam: aalac[0].Payload.(*Alac),
	}

	moofs, err := mp4.ExtractBox(f, nil, []mp4.BoxType{
		mp4.BoxTypeMoof(),
	})
	if err != nil || len(moofs) <= 0 {
		return nil, err
	}

	mdats, err := mp4.ExtractBoxWithPayload(f, nil, []mp4.BoxType{
		mp4.BoxTypeMdat(),
	})
	if err != nil || len(mdats) != len(moofs) {
		return nil, err
	}

	fmt.Println("Extracting samples.")
	for i, moof := range moofs {
		tfhd, err := mp4.ExtractBoxWithPayload(f, moof, []mp4.BoxType{
			mp4.BoxTypeTraf(),
			mp4.BoxTypeTfhd(),
		})
		if err != nil || len(tfhd) != 1 {
			fmt.Printf("Error extracting tfhd for moof %d: %v\n", i, err)
			return nil, err
		}
		tfhdPay := tfhd[0].Payload.(*mp4.Tfhd)
		index := tfhdPay.SampleDescriptionIndex
		if index != 0 {
			index--
		}

		truns, err := mp4.ExtractBoxWithPayload(f, moof, []mp4.BoxType{
			mp4.BoxTypeTraf(),
			mp4.BoxTypeTrun(),
		})
		if err != nil || len(truns) <= 0 {
			fmt.Printf("Error extracting trun for moof %d: %v\n", i, err)
			return nil, err
		}

		mdat := mdats[i].Payload.(*mp4.Mdat).Data
		for _, t := range truns {
			for _, en := range t.Payload.(*mp4.Trun).Entries {
				info := SampleInfo{descIndex: index}

				switch {
				case t.Payload.CheckFlag(0x200):
					info.data = mdat[:en.SampleSize]
					mdat = mdat[en.SampleSize:]
				case tfhdPay.CheckFlag(0x10):
					info.data = mdat[:tfhdPay.DefaultSampleSize]
					mdat = mdat[tfhdPay.DefaultSampleSize:]
				default:
					info.data = mdat[:trexPay.DefaultSampleSize]
					mdat = mdat[trexPay.DefaultSampleSize:]
				}

				switch {
				case t.Payload.CheckFlag(0x100):
					info.duration = en.SampleDuration
				case tfhdPay.CheckFlag(0x8):
					info.duration = tfhdPay.DefaultSampleDuration
				default:
					info.duration = trexPay.DefaultSampleDuration
				}

				extracted.samples = append(extracted.samples, info)
			}
		}
		if len(mdat) != 0 {
			fmt.Println("Offset mismatch detected.")
			return nil, errors.New("offset mismatch")
		}
	}
	fmt.Println("Samples extracted successfully.")
	return extracted, nil
}

func init() {
	mp4.AddBoxDef((*Alac)(nil))
}

func BoxTypeAlac() mp4.BoxType { return mp4.StrToBoxType("alac") }

type Alac struct {
	mp4.FullBox `mp4:"extend"`

	FrameLength       uint32 `mp4:"size=32"`
	CompatibleVersion uint8  `mp4:"size=8"`
	BitDepth          uint8  `mp4:"size=8"`
	Pb                uint8  `mp4:"size=8"`
	Mb                uint8  `mp4:"size=8"`
	Kb                uint8  `mp4:"size=8"`
	NumChannels       uint8  `mp4:"size=8"`
	MaxRun            uint16 `mp4:"size=16"`
	MaxFrameBytes     uint32 `mp4:"size=32"`
	AvgBitRate        uint32 `mp4:"size=32"`
	SampleRate        uint32 `mp4:"size=32"`
}

func getInfoFromAdam(adamId string, token string, storefront string) (*SongData, error) {
	request, err := http.NewRequest("GET", fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/songs/%s", storefront, adamId), nil)
	if err != nil {
		return nil, err
	}
	query := url.Values{}
	query.Set("extend", "extendedAssetUrls")
	query.Set("include", "albums")
	request.URL.RawQuery = query.Encode()

	request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	request.Header.Set("User-Agent", "iTunes/12.11.3 (Windows; Microsoft Windows 10 x64 Professional Edition (Build 19041); x64) AppleWebKit/7611.1022.4001.1 (dt:2)")
	request.Header.Set("Origin", "https://music.apple.com")

	do, err := http.DefaultClient.Do(request)
	if err != nil {
		return nil, err
	}
	defer do.Body.Close()
	if do.StatusCode != http.StatusOK {
		return nil, errors.New(do.Status)
	}

	obj := new(ApiResult)
	err = json.NewDecoder(do.Body).Decode(&obj)
	if err != nil {
		return nil, err
	}

	for _, d := range obj.Data {
		if d.ID == adamId {
			return &d, nil
		}
	}
	return nil, nil
}

func getToken() (string, error) {
	req, err := http.NewRequest("GET", "https://beta.music.apple.com", nil)
	if err != nil {
		return "", err
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	regex := regexp.MustCompile(`/assets/index-legacy-[^/]+\.js`)
	indexJsUri := regex.FindString(string(body))

	req, err = http.NewRequest("GET", "https://beta.music.apple.com"+indexJsUri, nil)
	if err != nil {
		return "", err
	}

	resp, err = http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err = io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	regex = regexp.MustCompile(`eyJh([^"]*)`)
	token := regex.FindString(string(body))

	return token, nil
}

type ApiResult struct {
	Data []SongData `json:"data"`
}

type SongAttributes struct {
	ArtistName        string   `json:"artistName"`
	DiscNumber        int      `json:"discNumber"`
	GenreNames        []string `json:"genreNames"`
	ExtendedAssetUrls struct {
		EnhancedHls string `json:"enhancedHls"`
	} `json:"extendedAssetUrls"`
	IsMasteredForItunes bool   `json:"isMasteredForItunes"`
	ReleaseDate         string `json:"releaseDate"`
	Name                string `json:"name"`
	Isrc                string `json:"isrc"`
	AlbumName           string `json:"albumName"`
	TrackNumber         int    `json:"trackNumber"`
	ComposerName        string `json:"composerName"`
}

type AlbumAttributes struct {
	ArtistName          string   `json:"artistName"`
	IsSingle            bool     `json:"isSingle"`
	IsComplete          bool     `json:"isComplete"`
	GenreNames          []string `json:"genreNames"`
	TrackCount          int      `json:"trackCount"`
	IsMasteredForItunes bool     `json:"isMasteredForItunes"`
	ReleaseDate         string   `json:"releaseDate"`
	Name                string   `json:"name"`
	RecordLabel         string   `json:"recordLabel"`
	Upc                 string   `json:"upc"`
	Copyright           string   `json:"copyright"`
	IsCompilation       bool     `json:"isCompilation"`
}

type SongData struct {
	ID            string         `json:"id"`
	Attributes    SongAttributes `json:"attributes"`
	Relationships struct {
		Albums struct {
			Data []struct {
				ID         string          `json:"id"`
				Type       string          `json:"type"`
				Href       string          `json:"href"`
				Attributes AlbumAttributes `json:"attributes"`
			} `json:"data"`
		} `json:"albums"`
		Artists struct {
			Href string `json:"href"`
			Data []struct {
				ID   string `json:"id"`
				Type string `json:"type"`
				Href string `json:"href"`
			} `json:"data"`
		} `json:"artists"`
	} `json:"relationships"`
}

type SongResult struct {
	Artwork struct {
		Width                int    `json:"width"`
		URL                  string `json:"url"`
		Height               int    `json:"height"`
		TextColor3           string `json:"textColor3"`
		TextColor2           string `json:"textColor2"`
		TextColor4           string `json:"textColor4"`
		HasAlpha             bool   `json:"hasAlpha"`
		TextColor1           string `json:"textColor1"`
		BgColor              string `json:"bgColor"`
		HasP3                bool   `json:"hasP3"`
		SupportsLayeredImage bool   `json:"supportsLayeredImage"`
	} `json:"artwork"`
	ArtistName             string   `json:"artistName"`
	CollectionID           string   `json:"collectionId"`
	DiscNumber             int      `json:"discNumber"`
	GenreNames             []string `json:"genreNames"`
	ID                     string   `json:"id"`
	DurationInMillis       int      `json:"durationInMillis"`
	ReleaseDate            string   `json:"releaseDate"`
	ContentRatingsBySystem struct {
	} `json:"contentRatingsBySystem"`
	Name     string `json:"name"`
	Composer struct {
		Name string `json:"name"`
		URL  string `json:"url"`
	} `json:"composer"`
	EditorialArtwork struct {
	} `json:"editorialArtwork"`
	CollectionName string `json:"collectionName"`
	AssetUrls      struct {
		Plus             string `json:"plus"`
		Lightweight      string `json:"lightweight"`
		SuperLightweight string `json:"superLightweight"`
		LightweightPlus  string `json:"lightweightPlus"`
		EnhancedHls      string `json:"enhancedHls"`
	} `json:"assetUrls"`
	AudioTraits []string `json:"audioTraits"`
	Kind        string   `json:"kind"`
	Copyright   string   `json:"copyright"`
	ArtistID    string   `json:"artistId"`
	Genres      []struct {
		GenreID   string `json:"genreId"`
		Name      string `json:"name"`
		URL       string `json:"url"`
		MediaType string `json:"mediaType"`
	} `json:"genres"`
	TrackNumber int    `json:"trackNumber"`
	AudioLocale string `json:"audioLocale"`
	Offers      []struct {
		ActionText struct {
			Short       string `json:"short"`
			Medium      string `json:"medium"`
			Long        string `json:"long"`
			Downloaded  string `json:"downloaded"`
			Downloading string `json:"downloading"`
		} `json:"actionText"`
		Type           string  `json:"type"`
		PriceFormatted string  `json:"priceFormatted"`
		Price          float64 `json:"price"`
		BuyParams      string  `json:"buyParams"`
		Variant        string  `json:"variant,omitempty"`
		Assets         []struct {
			Flavor  string `json:"flavor"`
			Preview struct {
				Duration int    `json:"duration"`
				URL      string `json:"url"`
			} `json:"preview"`
			Size     int `json:"size"`
			Duration int `json:"duration"`
		} `json:"assets"`
	} `json:"offers"`
}
type iTunesLookup struct {
	Results map[string]SongResult `json:"results"`
}

type Meta struct {
	Context     string `json:"@context"`
	Type        string `json:"@type"`
	Name        string `json:"name"`
	Description string `json:"description"`
	Tracks      []struct {
		Type  string `json:"@type"`
		Name  string `json:"name"`
		Audio struct {
			Type string `json:"@type"`
		} `json:"audio"`
		Offers struct {
			Type     string `json:"@type"`
			Category string `json:"category"`
			Price    int    `json:"price"`
		} `json:"offers"`
		Duration string `json:"duration"`
	} `json:"tracks"`
	Citation    []interface{} `json:"citation"`
	WorkExample []struct {
		Type  string `json:"@type"`
		Name  string `json:"name"`
		URL   string `json:"url"`
		Audio struct {
			Type string `json:"@type"`
		} `json:"audio"`
		Offers struct {
			Type     string `json:"@type"`
			Category string `json:"category"`
			Price    int    `json:"price"`
		} `json:"offers"`
		Duration string `json:"duration"`
	} `json:"workExample"`
	Genre         []string  `json:"genre"`
	DatePublished time.Time `json:"datePublished"`
	ByArtist      struct {
		Type string `json:"@type"`
		URL  string `json:"url"`
		Name string `json:"name"`
	} `json:"byArtist"`
}

type AutoGenerated struct {
	Data []struct {
		ID         string `json:"id"`
		Type       string `json:"type"`
		Href       string `json:"href"`
		Attributes struct {
			Artwork struct {
				Width      int    `json:"width"`
				Height     int    `json:"height"`
				URL        string `json:"url"`
				BgColor    string `json:"bgColor"`
				TextColor1 string `json:"textColor1"`
				TextColor2 string `json:"textColor2"`
				TextColor3 string `json:"textColor3"`
				TextColor4 string `json:"textColor4"`
			} `json:"artwork"`
			ArtistName          string   `json:"artistName"`
			IsSingle            bool     `json:"isSingle"`
			URL                 string   `json:"url"`
			IsComplete          bool     `json:"isComplete"`
			GenreNames          []string `json:"genreNames"`
			TrackCount          int      `json:"trackCount"`
			IsMasteredForItunes bool     `json:"isMasteredForItunes"`
			ReleaseDate         string   `json:"releaseDate"`
			Name                string   `json:"name"`
			RecordLabel         string   `json:"recordLabel"`
			Upc                 string   `json:"upc"`
			AudioTraits         []string `json:"audioTraits"`
			Copyright           string   `json:"copyright"`
			PlayParams          struct {
				ID   string `json:"id"`
				Kind string `json:"kind"`
			} `json:"playParams"`
			IsCompilation bool `json:"isCompilation"`
		} `json:"attributes"`
		Relationships struct {
			RecordLabels struct {
				Href string        `json:"href"`
				Data []interface{} `json:"data"`
			} `json:"record-labels"`
			Artists struct {
				Href string `json:"href"`
				Data []struct {
					ID         string `json:"id"`
					Type       string `json:"type"`
					Href       string `json:"href"`
					Attributes struct {
						Name string `json:"name"`
					} `json:"attributes"`
				} `json:"data"`
			} `json:"artists"`
			Tracks struct {
				Href string `json:"href"`
				Next string `json:"next"`
				Data []struct {
					ID         string `json:"id"`
					Type       string `json:"type"`
					Href       string `json:"href"`
					Attributes struct {
						Previews []struct {
							URL string `json:"url"`
						} `json:"previews"`
						Artwork struct {
							Width      int    `json:"width"`
							Height     int    `json:"height"`
							URL        string `json:"url"`
							BgColor    string `json:"bgColor"`
							TextColor1 string `json:"textColor1"`
							TextColor2 string `json:"textColor2"`
							TextColor3 string `json:"textColor3"`
							TextColor4 string `json:"textColor4"`
						} `json:"artwork"`
						ArtistName          string   `json:"artistName"`
						URL                 string   `json:"url"`
						DiscNumber          int      `json:"discNumber"`
						GenreNames          []string `json:"genreNames"`
						HasTimeSyncedLyrics bool     `json:"hasTimeSyncedLyrics"`
						IsMasteredForItunes bool     `json:"isMasteredForItunes"`
						DurationInMillis    int      `json:"durationInMillis"`
						ReleaseDate         string   `json:"releaseDate"`
						Name                string   `json:"name"`
						Isrc                string   `json:"isrc"`
						AudioTraits         []string `json:"audioTraits"`
						HasLyrics           bool     `json:"hasLyrics"`
						AlbumName           string   `json:"albumName"`
						PlayParams          struct {
							ID   string `json:"id"`
							Kind string `json:"kind"`
						} `json:"playParams"`
						TrackNumber  int    `json:"trackNumber"`
						AudioLocale  string `json:"audioLocale"`
						ComposerName string `json:"composerName"`
					} `json:"attributes"`
					Relationships struct {
						Artists struct {
							Href string `json:"href"`
							Data []struct {
								ID         string `json:"id"`
								Type       string `json:"type"`
								Href       string `json:"href"`
								Attributes struct {
									Name string `json:"name"`
								} `json:"attributes"`
							} `json:"data"`
						} `json:"artists"`
					} `json:"relationships"`
				} `json:"data"`
			} `json:"tracks"`
		} `json:"relationships"`
	} `json:"data"`
}

type AutoGeneratedTrack struct {
	Href string `json:"href"`
	Next string `json:"next"`
	Data []struct {
		ID         string `json:"id"`
		Type       string `json:"type"`
		Href       string `json:"href"`
		Attributes struct {
			Previews []struct {
				URL string `json:"url"`
			} `json:"previews"`
			Artwork struct {
				Width      int    `json:"width"`
				Height     int    `json:"height"`
				URL        string `json:"url"`
				BgColor    string `json:"bgColor"`
				TextColor1 string `json:"textColor1"`
				TextColor2 string `json:"textColor2"`
				TextColor3 string `json:"textColor3"`
				TextColor4 string `json:"textColor4"`
			} `json:"artwork"`
			ArtistName          string   `json:"artistName"`
			URL                 string   `json:"url"`
			DiscNumber          int      `json:"discNumber"`
			GenreNames          []string `json:"genreNames"`
			HasTimeSyncedLyrics bool     `json:"hasTimeSyncedLyrics"`
			IsMasteredForItunes bool     `json:"isMasteredForItunes"`
			DurationInMillis    int      `json:"durationInMillis"`
			ReleaseDate         string   `json:"releaseDate"`
			Name                string   `json:"name"`
			Isrc                string   `json:"isrc"`
			AudioTraits         []string `json:"audioTraits"`
			HasLyrics           bool     `json:"hasLyrics"`
			AlbumName           string   `json:"albumName"`
			PlayParams          struct {
				ID   string `json:"id"`
				Kind string `json:"kind"`
			} `json:"playParams"`
			TrackNumber  int    `json:"trackNumber"`
			AudioLocale  string `json:"audioLocale"`
			ComposerName string `json:"composerName"`
		} `json:"attributes"`
		Relationships struct {
			Artists struct {
				Href string `json:"href"`
				Data []struct {
					ID         string `json:"id"`
					Type       string `json:"type"`
					Href       string `json:"href"`
					Attributes struct {
						Name string `json:"name"`
					} `json:"attributes"`
				} `json:"data"`
			} `json:"artists"`
		} `json:"relationships"`
	} `json:"data"`
}

type SongLyrics struct {
	Data []struct {
		Id         string `json:"id"`
		Type       string `json:"type"`
		Attributes struct {
			Ttml       string `json:"ttml"`
			PlayParams struct {
				Id          string `json:"id"`
				Kind        string `json:"kind"`
				CatalogId   string `json:"catalogId"`
				DisplayType int    `json:"displayType"`
			} `json:"playParams"`
		} `json:"attributes"`
	} `json:"data"`
}

(myenv) D:\ALAC features(enabled)\apple-music-alac-atmos-downloader-main>go run checkingatmsandothercodec.go https://music.apple.com/us/album/happiness-begins/1461478261
Album 1 of 1:
Jonas Brothers
Happiness Begins [Dolby Atmos]
Apple Music URL saved to AM-DL-Atmos downloads\Jonas Brothers\Happiness Begins [Dolby Atmos]\apple_music_link.txt
Track 1 of 14:
Available audio streams:
[0] Codec: mp4a.40.2, Average Bandwidth: 254249, URI: P856765898_A1461478389_audio_en_gr256_mp4a-40-2_dm.m3u8
[1] Codec: mp4a.40.2, Average Bandwidth: 254549, URI: P856765898_A1461478389_audio_en_gr256_mp4a-40-2_bm.m3u8
[2] Codec: mp4a.40.2, Average Bandwidth: 253930, URI: P856765898_A1461478389_audio_en_gr256_mp4a-40-2.m3u8
[3] Codec: ec-3, Average Bandwidth: 770164, URI: P856765898_A1461478389_audio_en_gr2768_mp4a-A6.m3u8
[4] Codec: ec-3, Average Bandwidth: 450164, URI: P856765898_A1461478389_audio_en_gr2448_mp4a-A6.m3u8
[5] Codec: alac, Average Bandwidth: 1653738, URI: P856765898_A1461478389_audio_en_gr2116_alac.m3u8
[6] Codec: mp4a.40.2, Average Bandwidth: 129194, URI: P856765898_A1461478389_audio_en_gr128_mp4a-40-2_dm.m3u8
[7] Codec: mp4a.40.2, Average Bandwidth: 128819, URI: P856765898_A1461478389_audio_en_gr128_mp4a-40-2.m3u8
[8] Codec: mp4a.40.2, Average Bandwidth: 129754, URI: P856765898_A1461478389_audio_en_gr128_mp4a-40-2_bm.m3u8
[9] Codec: mp4a.40.5, Average Bandwidth: 69196, URI: P856765898_A1461478389_audio_en_gr64_mp4a-40-2_bm.m3u8
[10] Codec: mp4a.40.5, Average Bandwidth: 69520, URI: P856765898_A1461478389_audio_en_gr64_mp4a-40-2_dm.m3u8
[11] Codec: mp4a.40.5, Average Bandwidth: 69418, URI: P856765898_A1461478389_audio_en_gr64_mp4a-40-2.m3u8
Enter the number of the audio stream you want to download: 5
codecSuffix: P856765898_A1461478389_audio_en_gr2116_alac
newCodecSuffix:  audio_en_gr2116_alac
Downloading...
Downloaded.
Extracting samples.
Samples extracted successfully.
Successfully downloaded and saved track as m4a.
Decrypt start.
Decrypt finished.
Failed to decrypt track.
 nil pointer dereference detected

ALAC decryption working code:

package main

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"encoding/json"
	"gopkg.in/yaml.v2"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/abema/go-mp4"
	"github.com/beevik/etree"
	"github.com/grafov/m3u8"
)

const (
	defaultId   = "0"
	prefetchKey = "skd://itunes.apple.com/P000000000/s1/e1"
)

var (
	forbiddenNames = regexp.MustCompile(`[/\\<>:"|?*]`)
)
type Config struct {
    MediaUserToken string `yaml:"media-user-token"`
    SaveLrcFile    bool `yaml:"save-lrc-file"`   
    EmbedLrc       bool `yaml:"embed-lrc"`
    EmbedCover     bool `yaml:"embed-cover"`
    CoverSize      string `yaml:"cover-size"`
    CoverFormat      string `yaml:"cover-format"`
    AlacSaveFolder      string `yaml:"alac-save-folder"`
    AtmosSaveFolder      string `yaml:"atmos-save-folder"`
	ForceApi       bool `yaml:"force-api"`
	Check      string `yaml:"check"`
}

var config Config
var txtpath string
var oktrackNum int = 0
var trackTotalnum int = 0

type SampleInfo struct {
	data      []byte
	duration  uint32
	descIndex uint32
}

type SongInfo struct {
	r         io.ReadSeeker
	alacParam *Alac
	samples   []SampleInfo
}
func loadConfig() error {
    // 读取config.yaml文件内容  
    data, err := ioutil.ReadFile("config.yaml")
    if err != nil {
        return err
    }
    // 将yaml解析到config变量中
    err = yaml.Unmarshal(data, &config)
    if err != nil {
        return err
    }
    return nil
}

func (s *SongInfo) Duration() (ret uint64) {
	for i := range s.samples {
		ret += uint64(s.samples[i].duration)
	}
	return
}

func (*Alac) GetType() mp4.BoxType {
	return BoxTypeAlac()
}

func fileExists(path string) (bool, error) {
	f, err := os.Stat(path)
	if err == nil {
		return !f.IsDir(), nil
	} else if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func writeM4a(w *mp4.Writer, info *SongInfo, meta *AutoGenerated, data []byte, trackNum, trackTotal int) error {
	index := trackNum - 1
	{ // ftyp
		box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeFtyp()})
		if err != nil {
			return err
		}
		_, err = mp4.Marshal(w, &mp4.Ftyp{
			MajorBrand:   [4]byte{'M', '4', 'A', ' '},
			MinorVersion: 0,
			CompatibleBrands: []mp4.CompatibleBrandElem{
				{CompatibleBrand: [4]byte{'M', '4', 'A', ' '}},
				{CompatibleBrand: [4]byte{'m', 'p', '4', '2'}},
				{CompatibleBrand: mp4.BrandISOM()},
				{CompatibleBrand: [4]byte{0, 0, 0, 0}},
			},
		}, box.Context)
		if err != nil {
			return err
		}
		_, err = w.EndBox()
		if err != nil {
			return err
		}
	}

	const chunkSize uint32 = 5
	duration := info.Duration()
	numSamples := uint32(len(info.samples))
	var stco *mp4.BoxInfo

	{ // moov
		_, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMoov()})
		if err != nil {
			return err
		}
		box, err := mp4.ExtractBox(info.r, nil, mp4.BoxPath{mp4.BoxTypeMoov()})
		if err != nil {
			return err
		}
		moovOri := box[0]

		{ // mvhd
			_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMvhd()})
			if err != nil {
				return err
			}

			oriBox, err := mp4.ExtractBoxWithPayload(info.r, moovOri, mp4.BoxPath{mp4.BoxTypeMvhd()})
			if err != nil {
				return err
			}
			mvhd := oriBox[0].Payload.(*mp4.Mvhd)
			if mvhd.Version == 0 {
				mvhd.DurationV0 = uint32(duration)
			} else {
				mvhd.DurationV1 = duration
			}

			_, err = mp4.Marshal(w, mvhd, oriBox[0].Info.Context)
			if err != nil {
				return err
			}

			_, err = w.EndBox()
			if err != nil {
				return err
			}
		}

		{ // trak
			_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeTrak()})
			if err != nil {
				return err
			}

			box, err := mp4.ExtractBox(info.r, moovOri, mp4.BoxPath{mp4.BoxTypeTrak()})
			if err != nil {
				return err
			}
			trakOri := box[0]

			{ // tkhd
				_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeTkhd()})
				if err != nil {
					return err
				}

				oriBox, err := mp4.ExtractBoxWithPayload(info.r, trakOri, mp4.BoxPath{mp4.BoxTypeTkhd()})
				if err != nil {
					return err
				}
				tkhd := oriBox[0].Payload.(*mp4.Tkhd)
				if tkhd.Version == 0 {
					tkhd.DurationV0 = uint32(duration)
				} else {
					tkhd.DurationV1 = duration
				}
				tkhd.SetFlags(0x7)

				_, err = mp4.Marshal(w, tkhd, oriBox[0].Info.Context)
				if err != nil {
					return err
				}

				_, err = w.EndBox()
				if err != nil {
					return err
				}
			}

			{ // mdia
				_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMdia()})
				if err != nil {
					return err
				}

				box, err := mp4.ExtractBox(info.r, trakOri, mp4.BoxPath{mp4.BoxTypeMdia()})
				if err != nil {
					return err
				}
				mdiaOri := box[0]

				{ // mdhd
					_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMdhd()})
					if err != nil {
						return err
					}

					oriBox, err := mp4.ExtractBoxWithPayload(info.r, mdiaOri, mp4.BoxPath{mp4.BoxTypeMdhd()})
					if err != nil {
						return err
					}
					mdhd := oriBox[0].Payload.(*mp4.Mdhd)
					if mdhd.Version == 0 {
						mdhd.DurationV0 = uint32(duration)
					} else {
						mdhd.DurationV1 = duration
					}

					_, err = mp4.Marshal(w, mdhd, oriBox[0].Info.Context)
					if err != nil {
						return err
					}

					_, err = w.EndBox()
					if err != nil {
						return err
					}
				}

				{ // hdlr
					oriBox, err := mp4.ExtractBox(info.r, mdiaOri, mp4.BoxPath{mp4.BoxTypeHdlr()})
					if err != nil {
						return err
					}

					err = w.CopyBox(info.r, oriBox[0])
					if err != nil {
						return err
					}
				}

				{ // minf
					_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMinf()})
					if err != nil {
						return err
					}

					box, err := mp4.ExtractBox(info.r, mdiaOri, mp4.BoxPath{mp4.BoxTypeMinf()})
					if err != nil {
						return err
					}
					minfOri := box[0]

					{ // smhd, dinf
						boxes, err := mp4.ExtractBoxes(info.r, minfOri, []mp4.BoxPath{
							{mp4.BoxTypeSmhd()},
							{mp4.BoxTypeDinf()},
						})
						if err != nil {
							return err
						}

						for _, b := range boxes {
							err = w.CopyBox(info.r, b)
							if err != nil {
								return err
							}
						}
					}

					{ // stbl
						_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeStbl()})
						if err != nil {
							return err
						}

						{ // stsd
							box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeStsd()})
							if err != nil {
								return err
							}
							_, err = mp4.Marshal(w, &mp4.Stsd{EntryCount: 1}, box.Context)
							if err != nil {
								return err
							}

							{ // alac
								_, err = w.StartBox(&mp4.BoxInfo{Type: BoxTypeAlac()})
								if err != nil {
									return err
								}

								_, err = w.Write([]byte{
									0, 0, 0, 0, 0, 0, 0, 1,
									0, 0, 0, 0, 0, 0, 0, 0})
								if err != nil {
									return err
								}

								err = binary.Write(w, binary.BigEndian, uint16(info.alacParam.NumChannels))
								if err != nil {
									return err
								}

								err = binary.Write(w, binary.BigEndian, uint16(info.alacParam.BitDepth))
								if err != nil {
									return err
								}

								_, err = w.Write([]byte{0, 0})
								if err != nil {
									return err
								}

								err = binary.Write(w, binary.BigEndian, info.alacParam.SampleRate)
								if err != nil {
									return err
								}

								_, err = w.Write([]byte{0, 0})
								if err != nil {
									return err
								}

								box, err := w.StartBox(&mp4.BoxInfo{Type: BoxTypeAlac()})
								if err != nil {
									return err
								}

								_, err = mp4.Marshal(w, info.alacParam, box.Context)
								if err != nil {
									return err
								}

								_, err = w.EndBox()
								if err != nil {
									return err
								}

								_, err = w.EndBox()
								if err != nil {
									return err
								}
							}

							_, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						{ // stts
							box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeStts()})
							if err != nil {
								return err
							}

							var stts mp4.Stts
							for _, sample := range info.samples {
								if len(stts.Entries) != 0 {
									last := &stts.Entries[len(stts.Entries)-1]
									if last.SampleDelta == sample.duration {
										last.SampleCount++
										continue
									}
								}
								stts.Entries = append(stts.Entries, mp4.SttsEntry{
									SampleCount: 1,
									SampleDelta: sample.duration,
								})
							}
							stts.EntryCount = uint32(len(stts.Entries))

							_, err = mp4.Marshal(w, &stts, box.Context)
							if err != nil {
								return err
							}

							_, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						{ // stsc
							box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeStsc()})
							if err != nil {
								return err
							}

							if numSamples%chunkSize == 0 {
								_, err = mp4.Marshal(w, &mp4.Stsc{
									EntryCount: 1,
									Entries: []mp4.StscEntry{
										{
											FirstChunk:             1,
											SamplesPerChunk:        chunkSize,
											SampleDescriptionIndex: 1,
										},
									},
								}, box.Context)
							} else {
								_, err = mp4.Marshal(w, &mp4.Stsc{
									EntryCount: 2,
									Entries: []mp4.StscEntry{
										{
											FirstChunk:             1,
											SamplesPerChunk:        chunkSize,
											SampleDescriptionIndex: 1,
										}, {
											FirstChunk:             numSamples/chunkSize + 1,
											SamplesPerChunk:        numSamples % chunkSize,
											SampleDescriptionIndex: 1,
										},
									},
								}, box.Context)
							}

							_, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						{ // stsz
							box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeStsz()})
							if err != nil {
								return err
							}

							stsz := mp4.Stsz{SampleCount: numSamples}
							for _, sample := range info.samples {
								stsz.EntrySize = append(stsz.EntrySize, uint32(len(sample.data)))
							}

							_, err = mp4.Marshal(w, &stsz, box.Context)
							if err != nil {
								return err
							}

							_, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						{ // stco
							box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeStco()})
							if err != nil {
								return err
							}

							l := (numSamples + chunkSize - 1) / chunkSize
							_, err = mp4.Marshal(w, &mp4.Stco{
								EntryCount:  l,
								ChunkOffset: make([]uint32, l),
							}, box.Context)

							stco, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						_, err = w.EndBox()
						if err != nil {
							return err
						}
					}

					_, err = w.EndBox()
					if err != nil {
						return err
					}
				}

				_, err = w.EndBox()
				if err != nil {
					return err
				}
			}

			_, err = w.EndBox()
			if err != nil {
				return err
			}
		}

		{ // udta
			ctx := mp4.Context{UnderUdta: true}
			_, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeUdta(), Context: ctx})
			if err != nil {
				return err
			}

			{ // meta
				ctx.UnderIlstMeta = true

				_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMeta(), Context: ctx})
				if err != nil {
					return err
				}

				_, err = mp4.Marshal(w, &mp4.Meta{}, ctx)
				if err != nil {
					return err
				}

				{ // hdlr
					_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeHdlr(), Context: ctx})
					if err != nil {
						return err
					}

					_, err = mp4.Marshal(w, &mp4.Hdlr{
						HandlerType: [4]byte{'m', 'd', 'i', 'r'},
						Reserved:    [3]uint32{0x6170706c, 0, 0},
					}, ctx)
					if err != nil {
						return err
					}

					_, err = w.EndBox()
					if err != nil {
						return err
					}
				}

				{ // ilst
					ctx.UnderIlst = true

					_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeIlst(), Context: ctx})
					if err != nil {
						return err
					}

					marshalData := func(val interface{}) error {
						_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeData()})
						if err != nil {
							return err
						}

						var boxData mp4.Data
						switch v := val.(type) {
						case string:
							boxData.DataType = mp4.DataTypeStringUTF8
							boxData.Data = []byte(v)
						case uint8:
							boxData.DataType = mp4.DataTypeSignedIntBigEndian
							boxData.Data = []byte{v}
						case uint32:
							boxData.DataType = mp4.DataTypeSignedIntBigEndian
							boxData.Data = make([]byte, 4)
							binary.BigEndian.PutUint32(boxData.Data, v)
						case []byte:
							boxData.DataType = mp4.DataTypeBinary
							boxData.Data = v
						default:
							panic("unsupported value")
						}

						_, err = mp4.Marshal(w, &boxData, ctx)
						if err != nil {
							return err
						}

						_, err = w.EndBox()
						return err
					}

					addMeta := func(tag mp4.BoxType, val interface{}) error {
						_, err = w.StartBox(&mp4.BoxInfo{Type: tag})
						if err != nil {
							return err
						}

						err = marshalData(val)
						if err != nil {
							return err
						}

						_, err = w.EndBox()
						return err
					}

					addExtendedMeta := func(name string, val interface{}) error {
						ctx.UnderIlstFreeMeta = true

						_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxType{'-', '-', '-', '-'}, Context: ctx})
						if err != nil {
							return err
						}

						{
							_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxType{'m', 'e', 'a', 'n'}, Context: ctx})
							if err != nil {
								return err
							}

							_, err = w.Write([]byte{0, 0, 0, 0})
							if err != nil {
								return err
							}

							_, err = io.WriteString(w, "com.apple.iTunes")
							if err != nil {
								return err
							}

							_, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						{
							_, err = w.StartBox(&mp4.BoxInfo{Type: mp4.BoxType{'n', 'a', 'm', 'e'}, Context: ctx})
							if err != nil {
								return err
							}

							_, err = w.Write([]byte{0, 0, 0, 0})
							if err != nil {
								return err
							}

							_, err = io.WriteString(w, name)
							if err != nil {
								return err
							}

							_, err = w.EndBox()
							if err != nil {
								return err
							}
						}

						err = marshalData(val)
						if err != nil {
							return err
						}

						ctx.UnderIlstFreeMeta = false

						_, err = w.EndBox()
						return err
					}

					err = addMeta(mp4.BoxType{'\251', 'n', 'a', 'm'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.Name)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'s', 'o', 'n', 'm'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.Name)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'\251', 'a', 'l', 'b'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.AlbumName)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'s', 'o', 'a', 'l'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.AlbumName)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'\251', 'A', 'R', 'T'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.ArtistName)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'s', 'o', 'a', 'r'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.ArtistName)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'\251', 'p', 'r', 'f'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.ArtistName)
					if err != nil {
						return err
					}

					err = addExtendedMeta("PERFORMER", meta.Data[0].Relationships.Tracks.Data[index].Attributes.ArtistName)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'\251', 'w', 'r', 't'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.ComposerName)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'s', 'o', 'c', 'o'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.ComposerName)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'\251', 'd', 'a', 'y'}, meta.Data[0].Attributes.ReleaseDate)
					if err != nil {
						return err
					}

					cnID, err := strconv.ParseUint(meta.Data[0].Relationships.Tracks.Data[index].ID, 10, 32)
					if err != nil {
						return err
					}

					err = addMeta(mp4.BoxType{'c', 'n', 'I', 'D'}, uint32(cnID))
					if err != nil {
						return err
					}

					err = addExtendedMeta("ISRC", meta.Data[0].Relationships.Tracks.Data[index].Attributes.Isrc)
					if err != nil {
						return err
					}

					if len(meta.Data[0].Relationships.Tracks.Data[index].Attributes.GenreNames) > 0 {
						err = addMeta(mp4.BoxType{'\251', 'g', 'e', 'n'}, meta.Data[0].Relationships.Tracks.Data[index].Attributes.GenreNames[0])
						if err != nil {
							return err
						}
					}

					if len(meta.Data) > 0 {
						album := meta.Data[0]

						err = addMeta(mp4.BoxType{'a', 'A', 'R', 'T'}, album.Attributes.ArtistName)
						if err != nil {
							return err
						}

						err = addMeta(mp4.BoxType{'s', 'o', 'a', 'a'}, album.Attributes.ArtistName)
						if err != nil {
							return err
						}

						err = addMeta(mp4.BoxType{'c', 'p', 'r', 't'}, album.Attributes.Copyright)
						if err != nil {
							return err
						}

						var isCpil uint8
						if album.Attributes.IsCompilation {
							isCpil = 1
						}
						err = addMeta(mp4.BoxType{'c', 'p', 'i', 'l'}, isCpil)
						if err != nil {
							return err
						}

						err = addMeta(mp4.BoxType{'\251', 'p', 'u', 'b'}, album.Attributes.RecordLabel)
						if err != nil {
							return err
						}

						err = addExtendedMeta("LABEL", album.Attributes.RecordLabel)
						if err != nil {
							return err
						}

						err = addExtendedMeta("UPC", album.Attributes.Upc)
						if err != nil {
							return err
						}
						
						if !strings.Contains(meta.Data[0].ID, "pl."){
							plID, err := strconv.ParseUint(meta.Data[0].ID, 10, 32)
							if err != nil {
								return err
							}
	
							err = addMeta(mp4.BoxType{'p', 'l', 'I', 'D'}, uint32(plID))
							if err != nil {
								return err
							}
						}
					}

					if len(meta.Data[0].Relationships.Tracks.Data[index].Relationships.Artists.Data[0].ID) > 0 {
						atID, err := strconv.ParseUint(meta.Data[0].Relationships.Tracks.Data[index].Relationships.Artists.Data[0].ID, 10, 32)
						if err != nil {
							return err
						}

						err = addMeta(mp4.BoxType{'a', 't', 'I', 'D'}, uint32(atID))
						if err != nil {
							return err
						}
					}

					trkn := make([]byte, 8)
					binary.BigEndian.PutUint32(trkn, uint32(meta.Data[0].Relationships.Tracks.Data[index].Attributes.TrackNumber))
					binary.BigEndian.PutUint16(trkn[4:], uint16(trackTotal))
					err = addMeta(mp4.BoxType{'t', 'r', 'k', 'n'}, trkn)
					if err != nil {
						return err
					}
					if meta.Data[0].Relationships.Tracks.Data[trackTotal - 1].Attributes.DiscNumber > 1{
						disk := make([]byte, 8)
						binary.BigEndian.PutUint32(disk, uint32(meta.Data[0].Relationships.Tracks.Data[index].Attributes.DiscNumber))
						binary.BigEndian.PutUint16(disk[4:], uint16(meta.Data[0].Relationships.Tracks.Data[trackTotal - 1].Attributes.DiscNumber))
						err = addMeta(mp4.BoxType{'d', 'i', 's', 'k'}, disk)
						if err != nil {
							return err
						}
					}

					ctx.UnderIlst = false

					_, err = w.EndBox()
					if err != nil {
						return err
					}
				}

				ctx.UnderIlstMeta = false
				_, err = w.EndBox()
				if err != nil {
					return err
				}
			}

			ctx.UnderUdta = false
			_, err = w.EndBox()
			if err != nil {
				return err
			}
		}

		_, err = w.EndBox()
		if err != nil {
			return err
		}
	}

	{
		box, err := w.StartBox(&mp4.BoxInfo{Type: mp4.BoxTypeMdat()})
		if err != nil {
			return err
		}

		_, err = mp4.Marshal(w, &mp4.Mdat{Data: data}, box.Context)
		if err != nil {
			return err
		}

		mdat, err := w.EndBox()

		var realStco mp4.Stco

		offset := mdat.Offset + mdat.HeaderSize
		for i := uint32(0); i < numSamples; i++ {
			if i%chunkSize == 0 {
				realStco.EntryCount++
				realStco.ChunkOffset = append(realStco.ChunkOffset, uint32(offset))
			}
			offset += uint64(len(info.samples[i].data))
		}

		_, err = stco.SeekToPayload(w)
		if err != nil {
			return err
		}
		_, err = mp4.Marshal(w, &realStco, box.Context)
		if err != nil {
			return err
		}
	}

	return nil
}

func decryptSong(info *SongInfo, keys []string, manifest *AutoGenerated, filename string, trackNum, trackTotal int) error {
	//fmt.Printf("%d-bit / %d Hz\n", info.bitDepth, info.bitRate)
	conn, err := net.Dial("tcp", "127.0.0.1:10020")
	if err != nil {
		return err
	}
	defer conn.Close()
	var decrypted []byte
	var lastIndex uint32 = math.MaxUint8

	fmt.Println("Decrypt start.")
	for _, sp := range info.samples {
		if lastIndex != sp.descIndex {
			if len(decrypted) != 0 {
				_, err := conn.Write([]byte{0, 0, 0, 0})
				if err != nil {
					return err
				}
			}
			keyUri := keys[sp.descIndex]
			id := manifest.Data[0].Relationships.Tracks.Data[trackNum-1].ID
			if keyUri == prefetchKey {
				id = defaultId
			}

			_, err := conn.Write([]byte{byte(len(id))})
			if err != nil {
				return err
			}
			_, err = io.WriteString(conn, id)
			if err != nil {
				return err
			}

			_, err = conn.Write([]byte{byte(len(keyUri))})
			if err != nil {
				return err
			}
			_, err = io.WriteString(conn, keyUri)
			if err != nil {
				return err
			}
		}
		lastIndex = sp.descIndex

		err := binary.Write(conn, binary.LittleEndian, uint32(len(sp.data)))
		if err != nil {
			return err
		}

		_, err = conn.Write(sp.data)
		if err != nil {
			return err
		}

		de := make([]byte, len(sp.data))
		_, err = io.ReadFull(conn, de)
		if err != nil {
			return err
		}

		decrypted = append(decrypted, de...)
	}
	_, _ = conn.Write([]byte{0, 0, 0, 0, 0})

	fmt.Println("Decrypt finished.")

	create, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer create.Close()

	return writeM4a(mp4.NewWriter(create), info, manifest, decrypted, trackNum, trackTotal)
}

func checkUrl(url string) (string, string) {
	pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/album|\/album\/.+))\/(?:id)?(\d[^\D]+)(?:$|\?)`)
	matches := pat.FindAllStringSubmatch(url, -1)

	if matches == nil {
		return "", ""
	} else {
		return matches[0][1], matches[0][2]
	}
}
func checkUrlPlaylist(url string) (string, string) {
	pat := regexp.MustCompile(`^(?:https:\/\/(?:beta\.music|music)\.apple\.com\/(\w{2})(?:\/playlist|\/playlist\/.+))\/(?:id)?(pl\.[\w-]+)(?:$|\?)`)
	matches := pat.FindAllStringSubmatch(url, -1)

	if matches == nil {
		return "", ""
	} else {
		return matches[0][1], matches[0][2]
	}
}

func getMeta(albumId string, token string, storefront string) (*AutoGenerated, error) {
	var mtype string
	var page int
	if strings.Contains(albumId, "pl.") {
		mtype = "playlists"
	} else {
		mtype = "albums"
	}
	req, err := http.NewRequest("GET", fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/%s/%s", storefront, mtype, albumId), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
	req.Header.Set("Origin", "https://music.apple.com")
	query := url.Values{}
	query.Set("omit[resource]", "autos")
	query.Set("include", "tracks,artists,record-labels")
	query.Set("include[songs]", "artists")
	query.Set("fields[artists]", "name")
	query.Set("fields[albums:albums]", "artistName,artwork,name,releaseDate,url")
	query.Set("fields[record-labels]", "name")
	// query.Set("l", "en-gb")
	req.URL.RawQuery = query.Encode()
	do, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer do.Body.Close()
	if do.StatusCode != http.StatusOK {
		return nil, errors.New(do.Status)
	}
	obj := new(AutoGenerated)
	err = json.NewDecoder(do.Body).Decode(&obj)
	if err != nil {
		return nil, err
	}
	if strings.Contains(albumId, "pl.") {
		obj.Data[0].Attributes.ArtistName = "Apple Music"
		if len(obj.Data[0].Relationships.Tracks.Next) > 0 {
			page = 0
			for {
				page = page + 100
				pageStr := strconv.Itoa(page)
				req, err := http.NewRequest("GET", fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/%s/%s/tracks?offset=%s", storefront, mtype, albumId, pageStr), nil)
				if err != nil {
					return nil, err
				}
				req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
				req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
				req.Header.Set("Origin", "https://music.apple.com")
				do, err := http.DefaultClient.Do(req)
				if err != nil {
					return nil, err
				}
				defer do.Body.Close()
				if do.StatusCode != http.StatusOK {
					return nil, errors.New(do.Status)
				}
				obj2 := new(AutoGeneratedTrack)
				err = json.NewDecoder(do.Body).Decode(&obj2)
				if err != nil {
					return nil, err
				}
				for _, value := range obj2.Data {
					obj.Data[0].Relationships.Tracks.Data = append(obj.Data[0].Relationships.Tracks.Data, value)
				}
				if len(obj2.Next) == 0 {
					break
				}
			}
		}
	}
	return obj, nil
}

func getSongLyrics(songId string, storefront string, token string, userToken string) (string, error) {
	req, err := http.NewRequest("GET",
		fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/songs/%s/lyrics", storefront, songId), nil)
	if err != nil {
		return "", err
	}
	req.Header.Set("Origin", "https://music.apple.com")
	req.Header.Set("Referer", "https://music.apple.com/")
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	cookie := http.Cookie{Name: "media-user-token", Value: userToken}
	req.AddCookie(&cookie)
	do, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer do.Body.Close()
	obj := new(SongLyrics)
	err = json.NewDecoder(do.Body).Decode(&obj)
	if obj.Data != nil {
		return obj.Data[0].Attributes.Ttml, nil
	} else {
		return "", errors.New("failed to get lyrics")
	}
}

func writeCover(sanAlbumFolder, url string) error {
	covPath := filepath.Join(sanAlbumFolder, "cover." + config.CoverFormat)
	exists, err := fileExists(covPath)
	if err != nil {
		fmt.Println("Failed to check if cover exists.")
		return err
	}
	if exists {
		return nil
	}
	if config.CoverFormat == "png" {
		re := regexp.MustCompile(`\{w\}x\{h\}`)
		parts := re.Split(url, 2)
		url = parts[0] + "{w}x{h}" + strings.Replace(parts[1], ".jpg", ".png", 1)
	}
	url = strings.Replace(url, "{w}x{h}", config.CoverSize, 1)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return err
	}
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
	do, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer do.Body.Close()
	if do.StatusCode != http.StatusOK {
		errors.New(do.Status)
	}
	f, err := os.Create(covPath)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = io.Copy(f, do.Body)
	if err != nil {
		return err
	}
	return nil
}

func writeLyrics(sanAlbumFolder, filename string, lrc string) error {
	lyricspath := filepath.Join(sanAlbumFolder, filename)
	f, err := os.Create(lyricspath)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = f.WriteString(lrc)
	if err != nil {
		return err
	}
	return nil
}

func rip(albumId string, token string, storefront string, userToken string, albumURL string) error {
	meta, err := getMeta(albumId, token, storefront)
	if err != nil {
		fmt.Println("Failed to get album metadata.\n")
		return err
	}
	singerFoldername := fmt.Sprintf("%s", meta.Data[0].Attributes.ArtistName)
	if strings.HasSuffix(singerFoldername, ".") {
		singerFoldername = strings.ReplaceAll(singerFoldername, ".", "")
	}
	singerFoldername = strings.TrimSpace(singerFoldername)
	singerFolder := filepath.Join(config.AlacSaveFolder, forbiddenNames.ReplaceAllString(singerFoldername, "_"))
	albumFolder := fmt.Sprintf("%s", meta.Data[0].Attributes.Name)
	if strings.HasSuffix(albumFolder, ".") {
		albumFolder = strings.ReplaceAll(albumFolder, ".", "")
	}
	albumFolder = strings.TrimSpace(albumFolder)
	sanAlbumFolder := filepath.Join(singerFolder, forbiddenNames.ReplaceAllString(albumFolder, "_"))
	os.MkdirAll(sanAlbumFolder, os.ModePerm)
	fmt.Println(singerFoldername)
	fmt.Println(albumFolder)
	// New functionality: write the album URL to album_link.txt
    if err := writeAlbumURL(sanAlbumFolder, albumURL); err != nil { 
        fmt.Println("Error saving album URL:", err)
        // Decide if you want to return the error and halt or just log it and continue
    }
	err = writeCover(sanAlbumFolder, meta.Data[0].Attributes.Artwork.URL)
	if err != nil {
		fmt.Println("Failed to write cover.")
	}
	trackTotal := len(meta.Data[0].Relationships.Tracks.Data)
	for trackNum, track := range meta.Data[0].Relationships.Tracks.Data {
		trackNum++
		trackTotalnum += 1
		fmt.Printf("Track %d of %d:\n", trackNum, trackTotal)
		manifest, err := getInfoFromAdam(track.ID, token, storefront)
		if err != nil {
			fmt.Println("Failed to get manifest.\n", err)
			continue
		}
		if manifest.Attributes.ExtendedAssetUrls.EnhancedHls == "" {
			fmt.Println("Unavailable in ALAC.")
			continue
		}
		filename := fmt.Sprintf("%02d. %s.m4a", trackNum, forbiddenNames.ReplaceAllString(track.Attributes.Name, "_"))
		lrcFilename := fmt.Sprintf("%02d. %s.lrc", trackNum, forbiddenNames.ReplaceAllString(track.Attributes.Name, "_"))
		trackPath := filepath.Join(sanAlbumFolder, filename)
		var lrc string = ""
		if userToken != "your-media-user-token" && (config.EmbedLrc || config.SaveLrcFile) {
			ttml, err := getSongLyrics(track.ID, storefront, token, userToken)
			if err != nil {
				fmt.Println("Failed to get lyrics")
			} else {
				lrc, err = conventTTMLToLRC(ttml)
				if err != nil {
					fmt.Printf("Failed to parse lyrics: %s \n", err)
				} else {
					if config.SaveLrcFile {
						err := writeLyrics(sanAlbumFolder, lrcFilename, lrc)
						if err != nil {
							fmt.Printf("Failed to write lyrics")
						}
						if !config.EmbedLrc {
							lrc = ""
						}
					}
				}
			}
		}
		exists, err := fileExists(trackPath)
		if err != nil {
			fmt.Println("Failed to check if track exists.")
		}
		if exists {
			fmt.Println("Track already exists locally.")
			oktrackNum += 1
			continue
		}
		if config.Check != ""{
			config.Check=strings.TrimSpace(config.Check)
			if strings.HasSuffix(config.Check, "txt") {
				txtpath=config.Check
			}
			if strings.HasPrefix(config.Check, "http") {
				req, err := http.NewRequest("GET", config.Check, nil)
				if err != nil {
					fmt.Println(err)
				}

				query := req.URL.Query()
				query.Set("songid", track.ID)
				req.URL.RawQuery = query.Encode()

				do, err := http.DefaultClient.Do(req)
				if err != nil {
					fmt.Println(err)
				}
				defer do.Body.Close()

				Checkbody, err := ioutil.ReadAll(do.Body)
				if err != nil {
					fmt.Println(err)
				}
				if string(Checkbody) != "no_found"{
					manifest.Attributes.ExtendedAssetUrls.EnhancedHls=string(Checkbody)
					fmt.Println("Found m3u8 from API")
				} else {
					if config.ForceApi {
						fmt.Println(" Not Found m3u8 from API, Skip")
						continue
					}
					fmt.Println(" Not Found m3u8 from API")
				}
			}
		}
		if txtpath != "" {
			file, err := os.Open(txtpath)
			if err != nil {
				fmt.Println("cant open txt:", err)
			}
			defer file.Close()
			scanner := bufio.NewScanner(file)
			for scanner.Scan() {
				line := scanner.Text()
				if strings.HasPrefix(line, track.ID) {
					parts := strings.SplitN(line, ",", 2)
					if len(parts) == 2 {
						manifest.Attributes.ExtendedAssetUrls.EnhancedHls=parts[1]
						fmt.Println("Found m3u8 from txt")
					}
				}
			}
			if err := scanner.Err(); err != nil {
				fmt.Println(err)
			}
		}
		trackUrl, keys, err := extractMedia(manifest.Attributes.ExtendedAssetUrls.EnhancedHls)
		if err != nil {
			fmt.Println("Failed to extract info from manifest.\n", err)
			continue
		}
		info, err := extractSong(trackUrl)
		if err != nil {
			fmt.Println("Failed to extract track.", err)
			continue
		}
		samplesOk := true
		for samplesOk {
			for _, i := range info.samples {
				if int(i.descIndex) >= len(keys) {
					fmt.Println("Decryption size mismatch.")
					samplesOk = false
				}
			}
			break
		}
		if !samplesOk {
			continue
		}
		err = decryptSong(info, keys, meta, trackPath, trackNum, trackTotal)
		if err != nil {
			fmt.Println("Failed to decrypt track.\n", err)
			continue
		}
		tags := []string{
			fmt.Sprintf("lyrics=%s", lrc),
		}
		if config.EmbedCover {
			tags = append(tags, fmt.Sprintf("cover=%s/cover.%s", sanAlbumFolder, config.CoverFormat))
		}
		tagsString := strings.Join(tags, ":")
		cmd := exec.Command("MP4Box", "-itags", tagsString, trackPath)
		if err := cmd.Run(); err != nil {
			fmt.Printf("Embed failed: %v\n", err)
			continue
		}
		oktrackNum += 1
	}
	return err
}

func writeAlbumURL(sanAlbumFolder string, url string) error {
    albumLinkFile := filepath.Join(sanAlbumFolder, "apple_music_link.txt")
    err := ioutil.WriteFile(albumLinkFile, []byte(url), 0644)
    if err != nil {
        fmt.Println("Failed to write album URL to file")
        return err
    }
    fmt.Printf("Apple Music URL saved to %s\n", albumLinkFile)
    return nil
}

func main() {
	err := loadConfig()
	if err != nil {
		fmt.Printf("load config failed: %v", err)
		return
	}
	token, err := getToken()
	if err != nil {
		fmt.Println("Failed to get token.")
		return
	}
	albumTotal := len(os.Args[1:])
	for albumNum, url := range os.Args[1:] {
		fmt.Printf("Album %d of %d:\n", albumNum+1, albumTotal)
		var storefront, albumId string
		if strings.Contains(url, ".txt") {
			txtpath = url
			fileName := filepath.Base(url)
			parts := strings.SplitN(fileName, "_", 3)
			storefront = parts[0]
			albumId = parts[1]
		} else {
			if strings.Contains(url, "/playlist/") {
				storefront, albumId = checkUrlPlaylist(url)
				txtpath = ""
			} else {
				storefront, albumId = checkUrl(url)
				txtpath = ""
			}
		}

		if albumId == "" {
			fmt.Printf("Invalid URL: %s\n", url)
			continue
		}
		err = rip(albumId, token, storefront, config.MediaUserToken, url)
		if err != nil {
			fmt.Println("Album failed.")
			fmt.Println(err)
		}
	}
	fmt.Printf("=======  Completed %d/%d ###### %d errors!! =======\n", oktrackNum, trackTotalnum, trackTotalnum-oktrackNum)
}

func conventTTMLToLRC(ttml string) (string, error) {
	parsedTTML := etree.NewDocument()
	err := parsedTTML.ReadFromString(ttml)
	if err != nil {
		return "", err
	}
	var lrcLines []string
	for _, item := range parsedTTML.FindElement("tt").FindElement("body").ChildElements() {
		for _, lyric := range item.ChildElements() {
			var h, m, s, ms int
			if lyric.SelectAttr("begin") == nil {
				return "", errors.New("no synchronised lyrics")
			}
			if strings.Contains(lyric.SelectAttr("begin").Value, ":") {
				_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d:%d:%d.%d", &h, &m, &s, &ms)
				if err != nil {
					_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d:%d.%d", &m, &s, &ms)
					h = 0
				}
			} else {
				_, err = fmt.Sscanf(lyric.SelectAttr("begin").Value, "%d.%d", &s, &ms)
				h, m = 0, 0
			}
			if err != nil {
				return "", err
			}
			var text string
			if lyric.SelectAttr("text") == nil {
				var textTmp []string
				for _, span := range lyric.Child {
					if _, ok := span.(*etree.CharData); ok {
						textTmp = append(textTmp, span.(*etree.CharData).Data)
					} else {
						textTmp = append(textTmp, span.(*etree.Element).Text())
					}
				}
				text = strings.Join(textTmp, "")
			} else {
				text = lyric.SelectAttr("text").Value
			}
			m += h * 60
			ms = ms / 10
			lrcLines = append(lrcLines, fmt.Sprintf("[%02d:%02d.%02d]%s", m, s, ms, text))
		}
	}
	return strings.Join(lrcLines, "\n"), nil
}

func extractMedia(b string) (string, []string, error) {
	masterUrl, err := url.Parse(b)
	if err != nil {
		return "", nil, err
	}
	resp, err := http.Get(b)
	if err != nil {
		return "", nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return "", nil, errors.New(resp.Status)
	}
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", nil, err
	}
	masterString := string(body)
	from, listType, err := m3u8.DecodeFrom(strings.NewReader(masterString), true)
	if err != nil || listType != m3u8.MASTER {
		return "", nil, errors.New("m3u8 not of master type")
	}
	master := from.(*m3u8.MasterPlaylist)
	var streamUrl *url.URL
	sort.Slice(master.Variants, func(i, j int) bool {
		return master.Variants[i].AverageBandwidth > master.Variants[j].AverageBandwidth
	})
	for _, variant := range master.Variants {
		if variant.Codecs == "alac" {
			split := strings.Split(variant.Audio, "-")
			length := len(split)
			fmt.Printf("%s-bit / %s Hz\n", split[length-1], split[length-2])
			streamUrlTemp, err := masterUrl.Parse(variant.URI)
			if err != nil {
				panic(err)
			}
			streamUrl = streamUrlTemp
			break
		}
	}
	if streamUrl == nil {
		return "", nil, errors.New("no alac codec found")
	}
	var keys []string
	keys = append(keys, prefetchKey)
	streamUrl.Path = strings.TrimSuffix(streamUrl.Path, ".m3u8") + "_m.mp4"
	regex := regexp.MustCompile(`"(skd?://[^"]*)"`)
	matches := regex.FindAllStringSubmatch(masterString, -1)
	for _, match := range matches {
		if strings.HasSuffix(match[1], "c23") || strings.HasSuffix(match[1], "c6") {
			keys = append(keys, match[1])
		}
	}
	return streamUrl.String(), keys, nil
}

func extractSong(url string) (*SongInfo, error) {
	fmt.Println("Downloading...")
	track, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer track.Body.Close()
	if track.StatusCode != http.StatusOK {
		return nil, errors.New(track.Status)
	}
	rawSong, err := ioutil.ReadAll(track.Body)
	if err != nil {
		return nil, err
	}
	fmt.Println("Downloaded.")
	f := bytes.NewReader(rawSong)

	trex, err := mp4.ExtractBoxWithPayload(f, nil, []mp4.BoxType{
		mp4.BoxTypeMoov(),
		mp4.BoxTypeMvex(),
		mp4.BoxTypeTrex(),
	})
	if err != nil || len(trex) != 1 {
		return nil, err
	}
	trexPay := trex[0].Payload.(*mp4.Trex)

	stbl, err := mp4.ExtractBox(f, nil, []mp4.BoxType{
		mp4.BoxTypeMoov(),
		mp4.BoxTypeTrak(),
		mp4.BoxTypeMdia(),
		mp4.BoxTypeMinf(),
		mp4.BoxTypeStbl(),
	})
	if err != nil || len(stbl) != 1 {
		return nil, err
	}

	enca, err := mp4.ExtractBoxWithPayload(f, stbl[0], []mp4.BoxType{
		mp4.BoxTypeStsd(),
		mp4.BoxTypeEnca(),
	})
	if err != nil {
		return nil, err
	}

	aalac, err := mp4.ExtractBoxWithPayload(f, &enca[0].Info,
		[]mp4.BoxType{BoxTypeAlac()})
	if err != nil || len(aalac) != 1 {
		return nil, err
	}

	extracted := &SongInfo{
		r:         f,
		alacParam: aalac[0].Payload.(*Alac),
	}

	moofs, err := mp4.ExtractBox(f, nil, []mp4.BoxType{
		mp4.BoxTypeMoof(),
	})
	if err != nil || len(moofs) <= 0 {
		return nil, err
	}

	mdats, err := mp4.ExtractBoxWithPayload(f, nil, []mp4.BoxType{
		mp4.BoxTypeMdat(),
	})
	if err != nil || len(mdats) != len(moofs) {
		return nil, err
	}

	for i, moof := range moofs {
		tfhd, err := mp4.ExtractBoxWithPayload(f, moof, []mp4.BoxType{
			mp4.BoxTypeTraf(),
			mp4.BoxTypeTfhd(),
		})
		if err != nil || len(tfhd) != 1 {
			return nil, err
		}
		tfhdPay := tfhd[0].Payload.(*mp4.Tfhd)
		index := tfhdPay.SampleDescriptionIndex
		if index != 0 {
			index--
		}

		truns, err := mp4.ExtractBoxWithPayload(f, moof, []mp4.BoxType{
			mp4.BoxTypeTraf(),
			mp4.BoxTypeTrun(),
		})
		if err != nil || len(truns) <= 0 {
			return nil, err
		}

		mdat := mdats[i].Payload.(*mp4.Mdat).Data
		for _, t := range truns {
			for _, en := range t.Payload.(*mp4.Trun).Entries {
				info := SampleInfo{descIndex: index}

				switch {
				case t.Payload.CheckFlag(0x200):
					info.data = mdat[:en.SampleSize]
					mdat = mdat[en.SampleSize:]
				case tfhdPay.CheckFlag(0x10):
					info.data = mdat[:tfhdPay.DefaultSampleSize]
					mdat = mdat[tfhdPay.DefaultSampleSize:]
				default:
					info.data = mdat[:trexPay.DefaultSampleSize]
					mdat = mdat[trexPay.DefaultSampleSize:]
				}

				switch {
				case t.Payload.CheckFlag(0x100):
					info.duration = en.SampleDuration
				case tfhdPay.CheckFlag(0x8):
					info.duration = tfhdPay.DefaultSampleDuration
				default:
					info.duration = trexPay.DefaultSampleDuration
				}

				extracted.samples = append(extracted.samples, info)
			}
		}
		if len(mdat) != 0 {
			return nil, errors.New("offset mismatch")
		}
	}

	return extracted, nil
}

func init() {
	mp4.AddBoxDef((*Alac)(nil))
}

func BoxTypeAlac() mp4.BoxType { return mp4.StrToBoxType("alac") }

type Alac struct {
	mp4.FullBox `mp4:"extend"`

	FrameLength       uint32 `mp4:"size=32"`
	CompatibleVersion uint8  `mp4:"size=8"`
	BitDepth          uint8  `mp4:"size=8"`
	Pb                uint8  `mp4:"size=8"`
	Mb                uint8  `mp4:"size=8"`
	Kb                uint8  `mp4:"size=8"`
	NumChannels       uint8  `mp4:"size=8"`
	MaxRun            uint16 `mp4:"size=16"`
	MaxFrameBytes     uint32 `mp4:"size=32"`
	AvgBitRate        uint32 `mp4:"size=32"`
	SampleRate        uint32 `mp4:"size=32"`
}

func getInfoFromAdam(adamId string, token string, storefront string) (*SongData, error) {
	request, err := http.NewRequest("GET", fmt.Sprintf("https://amp-api.music.apple.com/v1/catalog/%s/songs/%s", storefront, adamId), nil)
	if err != nil {
		return nil, err
	}
	query := url.Values{}
	query.Set("extend", "extendedAssetUrls")
	query.Set("include", "albums")
	request.URL.RawQuery = query.Encode()

	request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	request.Header.Set("User-Agent", "iTunes/12.11.3 (Windows; Microsoft Windows 10 x64 Professional Edition (Build 19041); x64) AppleWebKit/7611.1022.4001.1 (dt:2)")
	request.Header.Set("Origin", "https://music.apple.com")

	do, err := http.DefaultClient.Do(request)
	if err != nil {
		return nil, err
	}
	defer do.Body.Close()
	if do.StatusCode != http.StatusOK {
		return nil, errors.New(do.Status)
	}

	obj := new(ApiResult)
	err = json.NewDecoder(do.Body).Decode(&obj)
	if err != nil {
		return nil, err
	}

	for _, d := range obj.Data {
		if d.ID == adamId {
			return &d, nil
		}
	}
	return nil, nil
}

func getToken() (string, error) {
	req, err := http.NewRequest("GET", "https://beta.music.apple.com", nil)
	if err != nil {
		return "", err
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	regex := regexp.MustCompile(`/assets/index-legacy-[^/]+\.js`)
	indexJsUri := regex.FindString(string(body))

	req, err = http.NewRequest("GET", "https://beta.music.apple.com"+indexJsUri, nil)
	if err != nil {
		return "", err
	}

	resp, err = http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err = io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	regex = regexp.MustCompile(`eyJh([^"]*)`)
	token := regex.FindString(string(body))

	return token, nil
}

type ApiResult struct {
	Data []SongData `json:"data"`
}

type SongAttributes struct {
	ArtistName        string   `json:"artistName"`
	DiscNumber        int      `json:"discNumber"`
	GenreNames        []string `json:"genreNames"`
	ExtendedAssetUrls struct {
		EnhancedHls string `json:"enhancedHls"`
	} `json:"extendedAssetUrls"`
	IsMasteredForItunes bool   `json:"isMasteredForItunes"`
	ReleaseDate         string `json:"releaseDate"`
	Name                string `json:"name"`
	Isrc                string `json:"isrc"`
	AlbumName           string `json:"albumName"`
	TrackNumber         int    `json:"trackNumber"`
	ComposerName        string `json:"composerName"`
}

type AlbumAttributes struct {
	ArtistName          string   `json:"artistName"`
	IsSingle            bool     `json:"isSingle"`
	IsComplete          bool     `json:"isComplete"`
	GenreNames          []string `json:"genreNames"`
	TrackCount          int      `json:"trackCount"`
	IsMasteredForItunes bool     `json:"isMasteredForItunes"`
	ReleaseDate         string   `json:"releaseDate"`
	Name                string   `json:"name"`
	RecordLabel         string   `json:"recordLabel"`
	Upc                 string   `json:"upc"`
	Copyright           string   `json:"copyright"`
	IsCompilation       bool     `json:"isCompilation"`
}

type SongData struct {
	ID            string         `json:"id"`
	Attributes    SongAttributes `json:"attributes"`
	Relationships struct {
		Albums struct {
			Data []struct {
				ID         string          `json:"id"`
				Type       string          `json:"type"`
				Href       string          `json:"href"`
				Attributes AlbumAttributes `json:"attributes"`
			} `json:"data"`
		} `json:"albums"`
		Artists struct {
			Href string `json:"href"`
			Data []struct {
				ID   string `json:"id"`
				Type string `json:"type"`
				Href string `json:"href"`
			} `json:"data"`
		} `json:"artists"`
	} `json:"relationships"`
}

type SongResult struct {
	Artwork struct {
		Width                int    `json:"width"`
		URL                  string `json:"url"`
		Height               int    `json:"height"`
		TextColor3           string `json:"textColor3"`
		TextColor2           string `json:"textColor2"`
		TextColor4           string `json:"textColor4"`
		HasAlpha             bool   `json:"hasAlpha"`
		TextColor1           string `json:"textColor1"`
		BgColor              string `json:"bgColor"`
		HasP3                bool   `json:"hasP3"`
		SupportsLayeredImage bool   `json:"supportsLayeredImage"`
	} `json:"artwork"`
	ArtistName             string   `json:"artistName"`
	CollectionID           string   `json:"collectionId"`
	DiscNumber             int      `json:"discNumber"`
	GenreNames             []string `json:"genreNames"`
	ID                     string   `json:"id"`
	DurationInMillis       int      `json:"durationInMillis"`
	ReleaseDate            string   `json:"releaseDate"`
	ContentRatingsBySystem struct {
	} `json:"contentRatingsBySystem"`
	Name     string `json:"name"`
	Composer struct {
		Name string `json:"name"`
		URL  string `json:"url"`
	} `json:"composer"`
	EditorialArtwork struct {
	} `json:"editorialArtwork"`
	CollectionName string `json:"collectionName"`
	AssetUrls      struct {
		Plus             string `json:"plus"`
		Lightweight      string `json:"lightweight"`
		SuperLightweight string `json:"superLightweight"`
		LightweightPlus  string `json:"lightweightPlus"`
		EnhancedHls      string `json:"enhancedHls"`
	} `json:"assetUrls"`
	AudioTraits []string `json:"audioTraits"`
	Kind        string   `json:"kind"`
	Copyright   string   `json:"copyright"`
	ArtistID    string   `json:"artistId"`
	Genres      []struct {
		GenreID   string `json:"genreId"`
		Name      string `json:"name"`
		URL       string `json:"url"`
		MediaType string `json:"mediaType"`
	} `json:"genres"`
	TrackNumber int    `json:"trackNumber"`
	AudioLocale string `json:"audioLocale"`
	Offers      []struct {
		ActionText struct {
			Short       string `json:"short"`
			Medium      string `json:"medium"`
			Long        string `json:"long"`
			Downloaded  string `json:"downloaded"`
			Downloading string `json:"downloading"`
		} `json:"actionText"`
		Type           string  `json:"type"`
		PriceFormatted string  `json:"priceFormatted"`
		Price          float64 `json:"price"`
		BuyParams      string  `json:"buyParams"`
		Variant        string  `json:"variant,omitempty"`
		Assets         []struct {
			Flavor  string `json:"flavor"`
			Preview struct {
				Duration int    `json:"duration"`
				URL      string `json:"url"`
			} `json:"preview"`
			Size     int `json:"size"`
			Duration int `json:"duration"`
		} `json:"assets"`
	} `json:"offers"`
}
type iTunesLookup struct {
	Results map[string]SongResult `json:"results"`
}

type Meta struct {
	Context     string `json:"@context"`
	Type        string `json:"@type"`
	Name        string `json:"name"`
	Description string `json:"description"`
	Tracks      []struct {
		Type  string `json:"@type"`
		Name  string `json:"name"`
		Audio struct {
			Type string `json:"@type"`
		} `json:"audio"`
		Offers struct {
			Type     string `json:"@type"`
			Category string `json:"category"`
			Price    int    `json:"price"`
		} `json:"offers"`
		Duration string `json:"duration"`
	} `json:"tracks"`
	Citation    []interface{} `json:"citation"`
	WorkExample []struct {
		Type  string `json:"@type"`
		Name  string `json:"name"`
		URL   string `json:"url"`
		Audio struct {
			Type string `json:"@type"`
		} `json:"audio"`
		Offers struct {
			Type     string `json:"@type"`
			Category string `json:"category"`
			Price    int    `json:"price"`
		} `json:"offers"`
		Duration string `json:"duration"`
	} `json:"workExample"`
	Genre         []string  `json:"genre"`
	DatePublished time.Time `json:"datePublished"`
	ByArtist      struct {
		Type string `json:"@type"`
		URL  string `json:"url"`
		Name string `json:"name"`
	} `json:"byArtist"`
}

type AutoGenerated struct {
	Data []struct {
		ID         string `json:"id"`
		Type       string `json:"type"`
		Href       string `json:"href"`
		Attributes struct {
			Artwork struct {
				Width      int    `json:"width"`
				Height     int    `json:"height"`
				URL        string `json:"url"`
				BgColor    string `json:"bgColor"`
				TextColor1 string `json:"textColor1"`
				TextColor2 string `json:"textColor2"`
				TextColor3 string `json:"textColor3"`
				TextColor4 string `json:"textColor4"`
			} `json:"artwork"`
			ArtistName          string   `json:"artistName"`
			IsSingle            bool     `json:"isSingle"`
			URL                 string   `json:"url"`
			IsComplete          bool     `json:"isComplete"`
			GenreNames          []string `json:"genreNames"`
			TrackCount          int      `json:"trackCount"`
			IsMasteredForItunes bool     `json:"isMasteredForItunes"`
			ReleaseDate         string   `json:"releaseDate"`
			Name                string   `json:"name"`
			RecordLabel         string   `json:"recordLabel"`
			Upc                 string   `json:"upc"`
			AudioTraits         []string `json:"audioTraits"`
			Copyright           string   `json:"copyright"`
			PlayParams          struct {
				ID   string `json:"id"`
				Kind string `json:"kind"`
			} `json:"playParams"`
			IsCompilation bool `json:"isCompilation"`
		} `json:"attributes"`
		Relationships struct {
			RecordLabels struct {
				Href string        `json:"href"`
				Data []interface{} `json:"data"`
			} `json:"record-labels"`
			Artists struct {
				Href string `json:"href"`
				Data []struct {
					ID         string `json:"id"`
					Type       string `json:"type"`
					Href       string `json:"href"`
					Attributes struct {
						Name string `json:"name"`
					} `json:"attributes"`
				} `json:"data"`
			} `json:"artists"`
			Tracks struct {
				Href string `json:"href"`
				Next string `json:"next"`
				Data []struct {
					ID         string `json:"id"`
					Type       string `json:"type"`
					Href       string `json:"href"`
					Attributes struct {
						Previews []struct {
							URL string `json:"url"`
						} `json:"previews"`
						Artwork struct {
							Width      int    `json:"width"`
							Height     int    `json:"height"`
							URL        string `json:"url"`
							BgColor    string `json:"bgColor"`
							TextColor1 string `json:"textColor1"`
							TextColor2 string `json:"textColor2"`
							TextColor3 string `json:"textColor3"`
							TextColor4 string `json:"textColor4"`
						} `json:"artwork"`
						ArtistName          string   `json:"artistName"`
						URL                 string   `json:"url"`
						DiscNumber          int      `json:"discNumber"`
						GenreNames          []string `json:"genreNames"`
						HasTimeSyncedLyrics bool     `json:"hasTimeSyncedLyrics"`
						IsMasteredForItunes bool     `json:"isMasteredForItunes"`
						DurationInMillis    int      `json:"durationInMillis"`
						ReleaseDate         string   `json:"releaseDate"`
						Name                string   `json:"name"`
						Isrc                string   `json:"isrc"`
						AudioTraits         []string `json:"audioTraits"`
						HasLyrics           bool     `json:"hasLyrics"`
						AlbumName           string   `json:"albumName"`
						PlayParams          struct {
							ID   string `json:"id"`
							Kind string `json:"kind"`
						} `json:"playParams"`
						TrackNumber  int    `json:"trackNumber"`
						AudioLocale  string `json:"audioLocale"`
						ComposerName string `json:"composerName"`
					} `json:"attributes"`
					Relationships struct {
						Artists struct {
							Href string `json:"href"`
							Data []struct {
								ID         string `json:"id"`
								Type       string `json:"type"`
								Href       string `json:"href"`
								Attributes struct {
									Name string `json:"name"`
								} `json:"attributes"`
							} `json:"data"`
						} `json:"artists"`
					} `json:"relationships"`
				} `json:"data"`
			} `json:"tracks"`
		} `json:"relationships"`
	} `json:"data"`
}

type AutoGeneratedTrack struct {
	Href string `json:"href"`
	Next string `json:"next"`
	Data []struct {
		ID         string `json:"id"`
		Type       string `json:"type"`
		Href       string `json:"href"`
		Attributes struct {
			Previews []struct {
				URL string `json:"url"`
			} `json:"previews"`
			Artwork struct {
				Width      int    `json:"width"`
				Height     int    `json:"height"`
				URL        string `json:"url"`
				BgColor    string `json:"bgColor"`
				TextColor1 string `json:"textColor1"`
				TextColor2 string `json:"textColor2"`
				TextColor3 string `json:"textColor3"`
				TextColor4 string `json:"textColor4"`
			} `json:"artwork"`
			ArtistName          string   `json:"artistName"`
			URL                 string   `json:"url"`
			DiscNumber          int      `json:"discNumber"`
			GenreNames          []string `json:"genreNames"`
			HasTimeSyncedLyrics bool     `json:"hasTimeSyncedLyrics"`
			IsMasteredForItunes bool     `json:"isMasteredForItunes"`
			DurationInMillis    int      `json:"durationInMillis"`
			ReleaseDate         string   `json:"releaseDate"`
			Name                string   `json:"name"`
			Isrc                string   `json:"isrc"`
			AudioTraits         []string `json:"audioTraits"`
			HasLyrics           bool     `json:"hasLyrics"`
			AlbumName           string   `json:"albumName"`
			PlayParams          struct {
				ID   string `json:"id"`
				Kind string `json:"kind"`
			} `json:"playParams"`
			TrackNumber  int    `json:"trackNumber"`
			AudioLocale  string `json:"audioLocale"`
			ComposerName string `json:"composerName"`
		} `json:"attributes"`
		Relationships struct {
			Artists struct {
				Href string `json:"href"`
				Data []struct {
					ID         string `json:"id"`
					Type       string `json:"type"`
					Href       string `json:"href"`
					Attributes struct {
						Name string `json:"name"`
					} `json:"attributes"`
				} `json:"data"`
			} `json:"artists"`
		} `json:"relationships"`
	} `json:"data"`
}

type SongLyrics struct {
	Data []struct {
		Id         string `json:"id"`
		Type       string `json:"type"`
		Attributes struct {
			Ttml       string `json:"ttml"`
			PlayParams struct {
				Id          string `json:"id"`
				Kind        string `json:"kind"`
				CatalogId   string `json:"catalogId"`
				DisplayType int    `json:"displayType"`
			} `json:"playParams"`
		} `json:"attributes"`
	} `json:"data"`
}


fix my code ?