Created
July 7, 2015 07:50
-
-
Save arturaz/1847462e8fac5d3fb736 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package app | |
import java.io.File | |
import java.nio.charset.StandardCharsets | |
import java.nio.file.Files | |
import scala.collection.JavaConverters._ | |
import scala.io.Source | |
import scalaz._, Scalaz._, scalaz.effect._ | |
object Main extends SafeApp { | |
import Extractor._ | |
val Extractors = Vector( | |
new Extractor { | |
// 1.) [1:00:00] Will Canas - Find Our Way (Vintage & Morelli Remix) [sunsetmelodies] | |
// 1.[0:00] Justin Oh - A Different Place (PROFF Remix)[Nueva digital] | |
val Re = """^\s*(\d+)\.\)?\s*\[((\d+):)?(\d+):(\d+)\] (.+?)$""".r | |
def extract(tracksRead: Int, line: String) = line match { | |
case Re(IntE(no), _, OptE.int(hh), IntE(mm), IntE(ss), title) => | |
Some(Track(no, Time(hh getOrElse 0, mm, ss), title)) | |
case _ => None | |
} | |
}, | |
new Extractor { | |
// 0:00 Module Module - An Interlude | |
// 00:00 - 04:10 - Feint Promises | |
val Re = """^\s*((\d+):)?(\d+):(\d+)( - \d*:?\d+:\d+ -)?\s+(.+?)$""".r | |
def extract(tracksRead: Int, line: String) = line match { | |
case Re(_, OptE.int(hh), IntE(mm), IntE(ss), _, title) => | |
Some(Track(tracksRead + 1, Time(hh getOrElse 0, mm, ss), title)) | |
case _ => None | |
} | |
} | |
) | |
override def run(args: ImmutableArray[String]) = { | |
if (args.size < 2) for { | |
_ <- IO.putStrLn("Usage: tracklist_parser file.mp3 file.cue [tracklist_or_stdin]") | |
_ <- IO { System.exit(1) } | |
} yield () | |
else process(args(0), args(1), args.lift(2)) | |
} | |
def process(tracksRead: Int, line: String) = | |
Extractors.findMap(_.extract(tracksRead, line)) | |
def readStdin: IO[Vector[Track]] = { | |
def rec(read: Vector[Track]): IO[Vector[Track]] = | |
IO.readLn.map(_.trim).flatMap { | |
case "!done" => IO(read) | |
case line => rec(read ++ process(read.size, line)) | |
} | |
rec(Vector.empty) | |
} | |
def readFile(file: File): IO[Vector[Track]] = IO { | |
Source.fromFile(file).getLines().foldLeft(Vector.empty[Track]) { | |
case (tracks, line) => tracks ++ process(tracks.size, line) | |
} | |
} | |
def writeFile(file: File, lines: Seq[String]): IO[Unit] = | |
IO { Files.write(file.toPath, lines.asJava, StandardCharsets.UTF_8) } | |
def cueLines(mp3: File, tracks: Vector[Track]): Vector[String] = Vector( | |
// # FILE "Faithless - Live in Berlin.mp3" MP3 | |
"""PERFORMER "VA"""", | |
s"""TITLE "${mp3.basename}"""", | |
s"""FILE "${mp3.getName}" MP3""" | |
) ++ tracks.zipWithIndex.flatMap { case (track, index) => Vector( | |
// TRACK 01 AUDIO | |
// TITLE "Reverence" | |
// PERFORMER "Faithless" | |
// INDEX 01 00:00:00 | |
f" TRACK ${index + 1}%02d AUDIO", | |
s""" TITLE "${track.songName}"""", | |
s""" PERFORMER "${track.performer}"""", | |
s""" INDEX 01 ${track.time.cue}""" | |
) } | |
def info(cue: File, mp3: File) = Vector( | |
s"", | |
s".cue file generation complete: $cue", | |
s"", | |
s"Split with:", | |
s"""mp3splt -o "@n - @p - @t" -c "$cue" "$mp3" """ | |
) | |
def process( | |
fullFileS: String, cueS: String, inputFileS: Option[String] | |
): IO[Unit] = { | |
val fullFile = new File(fullFileS) | |
val cue = new File(cueS) | |
for { | |
tracks <- inputFileS.map(new File(_)).fold(readStdin)(readFile) | |
cueStrs = cueLines(fullFile, tracks) | |
_ <- writeFile(cue, cueStrs) | |
_ <- info(cue, fullFile).map(IO.putStrLn).sequence_ | |
// Debug tracklist | |
//_ <- tracks.map(_.toString).map(IO.putStrLn).sequence_ | |
} yield () | |
} | |
} | |
case class Time(hours: Int, minutes: Int, seconds: Int) { | |
def mp3splt = s"${hours * 60 + minutes}.$seconds" | |
def cue = f"${hours * 60 + minutes}%02d:$seconds%02d:00" | |
} | |
object Track { | |
def apply(no: Int, time: Time, title: String): Track = { | |
val (performer, songName) = title.split("-", 2) match { | |
case Array(performer, songName) => | |
(performer, songName) | |
case Array(songName) => | |
("Unknown", songName) | |
} | |
Track(no, time, performer.trim, songName.trim) | |
} | |
} | |
case class Track( | |
no: Int, time: Time, performer: String, songName: String | |
) { | |
def title = s"$performer - $songName" | |
} | |
trait Extractor { | |
def extract(tracksRead: Int, line: String): Option[Track] | |
} | |
object Extractor { | |
trait E[A] { self => | |
def unapply(str: String): Option[A] | |
def map[B](f: A => B): E[B] = new E[B] { | |
def unapply(str: String) = self.unapply(str).map(f) | |
} | |
def mapO[OA, B](f: OA => Option[B])(implicit ev: A <:< Option[OA]) | |
: E[Option[B]] = map(_ flatMap f) | |
} | |
object IntE extends E[Int] { | |
def unapply(str: String) = str.parseInt.toOption | |
} | |
object OptE extends E[Option[String]] { | |
def unapply(str: String) = Some(Option(str)) | |
val int = this mapO IntE.unapply | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.File | |
import scala.util.matching.Regex.Match | |
/** | |
* Created by arturas on 15.6.14. | |
*/ | |
package object app { | |
implicit class MatchExts(val m: Match) extends AnyVal { | |
def oGroup(idx: Int) = Option(m.group(idx)) | |
def mapOGroup[A](idx: Int, f: String => A, orElse: => A) = | |
oGroup(idx).map(f).getOrElse(orElse) | |
} | |
implicit class FileExts(val file: File) extends AnyVal { | |
def basename = file.getName.replaceAll("\\..*$", "") | |
} | |
implicit class IterableExts[A](val i: Iterable[A]) extends AnyVal { | |
def findMap[B](f: A => Option[B]): Option[B] = { | |
i.foreach { a => | |
val opt = f(a) | |
if (opt.isDefined) return opt | |
} | |
None | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment