diff --git a/clean.bat b/clean.bat index 9949a2a..8b43cdd 100644 --- a/clean.bat +++ b/clean.bat @@ -1,3 +1,4 @@ cd scripts -deno --allow-read --allow-write clean.ts +@REM deno --allow-read --allow-write clean.ts +for %%f in (.\*.m3u8) do .\clean-id3.exe %%f cd .. diff --git a/scripts/clean-id3.exe b/scripts/clean-id3.exe new file mode 100644 index 0000000..f954dbb Binary files /dev/null and b/scripts/clean-id3.exe differ diff --git a/scripts/clean-id3/go.mod b/scripts/clean-id3/go.mod new file mode 100644 index 0000000..18fbfbb --- /dev/null +++ b/scripts/clean-id3/go.mod @@ -0,0 +1,8 @@ +module zomo.dev/clean-id3 + +go 1.25.0 + +require ( + github.com/Eyevinn/hls-m3u8 v0.6.0 // indirect + github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 // indirect +) diff --git a/scripts/clean-id3/go.sum b/scripts/clean-id3/go.sum new file mode 100644 index 0000000..bc68127 --- /dev/null +++ b/scripts/clean-id3/go.sum @@ -0,0 +1,4 @@ +github.com/Eyevinn/hls-m3u8 v0.6.0 h1:i4eyofj5zStgUPcy+UwUQ4oOgcLJVGbrw4XOcxVMVw8= +github.com/Eyevinn/hls-m3u8 v0.6.0/go.mod h1:9jzVfwCo1+TC6yz+TKDBt9gIshzI9fhVE7M5AhcOSnQ= +github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 h1:OtSeLS5y0Uy01jaKK4mA/WVIYtpzVm63vLVAPzJXigg= +github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8/go.mod h1:apkPC/CR3s48O2D7Y++n1XWEpgPNNCjXYga3PPbJe2E= diff --git a/scripts/clean-id3/main.go b/scripts/clean-id3/main.go new file mode 100644 index 0000000..9d89f39 --- /dev/null +++ b/scripts/clean-id3/main.go @@ -0,0 +1,183 @@ +package main + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "log" + "os" + "path" + "strings" + "text/template" + + "github.com/dhowden/tag" +) + +const ID3_TEMPLATE = "{{.Artist}} // {{.Album}} // {{.Title}}" + +type Tags struct { + Title string + Album string + Artist string + AlbumArtist string + Composer string + Genre string + Year int + Track int + TrackCount int + Disc int + DiscCount int +} + +func (t *Tags) Validate() error { + if t.Title == "" { + return errors.New("missing Title") + } + if t.Album == "" { + return errors.New("missing Album") + } + if t.Artist == "" { + return errors.New("missing Artist") + } + if t.AlbumArtist == "" { + return errors.New("missing AlbumArtist") + } + return nil +} + +func main() { + err := mainErr() + if err != nil { + log.Fatal("ERROR: ", err) + } +} + +func mainErr() error { + // file from arg + playlistFile := os.Args[1] + + newFileLines, err := loadPlaylistFile(playlistFile) + if err != nil { + return err + } + + newFileContent := strings.Join(newFileLines, "\n") + + return writeFile(playlistFile, newFileContent) +} + +// returns array of all id3 tags +func loadPlaylistFile(playlistFile string) ([]string, error) { + file, err := os.Open(playlistFile) + if err != nil { + log.Fatal(err) + } + defer file.Close() + + playlistTags := make([]string, 0) + lineNumber := 0 + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + // line by line + line := scanner.Text() + tags, err := loadPlaylistFileLine(line) + if err != nil { + return nil, fmt.Errorf("%s line %d: %s", playlistFile, lineNumber, err) + } + if tags != nil { + playlistTags = append(playlistTags, *tags) + } + + lineNumber++ + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return playlistTags, nil +} + +// ignore m3u8 tags +func loadPlaylistFileLine(line string) (*string, error) { + line = strings.TrimSpace(line) + + if []rune(line)[1] == '#' { + return nil, nil + } + if len(line) == 0 { + return nil, nil + } + + tags, err := loadSongFile(line) + return &tags, err +} + +// returns id3 tags +func loadSongFile(fileName string) (string, error) { + // load file + file, err := os.Open(fileName) + if err != nil { + return "", err + } + defer file.Close() + + tags, err := tag.ReadFrom(file) + if err != nil { + return "", err + } + + // parse tags + track, trackCount := tags.Track() + disc, discCount := tags.Disc() + + loadedTags := Tags{ + Title: tags.Title(), + Album: tags.Album(), + Artist: tags.Artist(), + AlbumArtist: tags.AlbumArtist(), + Composer: tags.Composer(), + Genre: tags.Genre(), + Year: tags.Year(), + Track: track, + TrackCount: trackCount, + Disc: disc, + DiscCount: discCount, + } + + // Ivy Lab // Infinite Falling Ground // Touch + if loadedTags.Artist == "Ivy Lab" { + log.Println(loadedTags.Title) + } + + err = loadedTags.Validate() + if err != nil { + return "", err + } + + return id3Template(loadedTags) +} + +// converts an id3 tag into a string +func id3Template(tags Tags) (string, error) { + tmpl := template.Must(template.New("id3template").Parse(ID3_TEMPLATE)) + + bufPath := bytes.NewBufferString("") + err := tmpl.Execute(bufPath, tags) + if err != nil { + return "", err + } + + return bufPath.String(), nil +} + +// outputs all strings as a file +func writeFile(inFileName string, fileContent string) error { + inFileExt := path.Ext(inFileName) + extIndex := strings.LastIndex(inFileName, inFileExt) + newFileName := inFileName[:extIndex] + ".txt" + + return os.WriteFile(newFileName, []byte(fileContent), 0666) +} diff --git a/scripts/clean-id3/reame.md b/scripts/clean-id3/reame.md new file mode 100644 index 0000000..599257c --- /dev/null +++ b/scripts/clean-id3/reame.md @@ -0,0 +1,9 @@ +# clean-id3 + +This will take in an m3u8 file, and will create a text file with just the song name info taken from the ID3 tags + +## usage + +`./clean ./01-01.m3u8` + +This will create a file `./01-01.txt` diff --git a/scripts/clean.ts b/scripts/clean.ts deleted file mode 100644 index 90666a1..0000000 --- a/scripts/clean.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * this is a deno script to clean up the m3u8 files from the music player - * - */ - -import { opendir, readFile, writeFile } from 'node:fs/promises'; -import * as path from "jsr:@std/path"; -import { Dirent } from 'node:fs'; - -/** - * longEnough will be true if splitting the line gave 3+ sections, and false if it's too short - * - * @param cleanLine - * @returns [cleanLine, longEnough] - */ -function replaceLineDashes(cleanLine: string): [string, boolean] { - const spl = cleanLine.split(' - ') - if (spl.length < 3) { - return [spl.join(' // '), false] - } - - const [spl1, spl2, ...splRemain] = spl - const spl3 = splRemain.join(' - ') - - return [[spl1, spl2, spl3].join(' // '), true] -} - -// loop through every line of the file and clean up each line -function cleanFile(dirent: Dirent, content: string): string { - const lines = content.split('\n') - const cleanLines: string[] = [] - - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim() - - if (!line || line.startsWith("#")) { - continue - } - - const ext = path.extname(line) - const name = path.basename(line, ext) - - let cleanLine = name - .replace(/ - Single/g, '') - .replace(/- \d+ -/g, '-') - - const [updatedCleanLine, longEnough] = replaceLineDashes(cleanLine) - cleanLine = updatedCleanLine - - if (!longEnough) { - cleanLine = `BAD LINE --- ${cleanLine}` - const fileName = path.basename(dirent.name) - console.log(`Bad Line found in ${fileName} on line ${i+1}: \t${line}`) - } - - cleanLines.push(cleanLine) - } - - return cleanLines.join('\n') + '\n' -} - -// get details from the file, ensure it's a playlist file, then store the updated file contents to a txt file -async function processFile(dirent: Dirent) { - if (dirent.isFile()) { - const ext = path.extname(dirent.name) - const name = path.basename(dirent.name, ext) - const dirname = path.dirname(dirent.name) - const newPath = path.join(dirname, `${name}.txt`) - - if (ext.toUpperCase() === ".M3U8") { - const fileContent = await readFile(dirent.name) - const cleanContent = cleanFile(dirent, fileContent.toString()) - await writeFile(newPath, cleanContent) - } - } -} - -// loop through every file in the current directory -try { - const dir = await opendir('./'); - for await (const dirent of dir) { - await processFile(dirent) - } -} catch (err) { - console.error(err) -}