new m3u8 cleanup script

This commit is contained in:
thezomo
2025-09-15 19:00:08 -05:00
parent 6719d2aced
commit b3969a5274
7 changed files with 206 additions and 87 deletions

BIN
scripts/clean-id3.exe Normal file

Binary file not shown.

8
scripts/clean-id3/go.mod Normal file
View File

@@ -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
)

4
scripts/clean-id3/go.sum Normal file
View File

@@ -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=

183
scripts/clean-id3/main.go Normal file
View File

@@ -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)
}

View File

@@ -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`

View File

@@ -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)
}