parent
8ad4567e71
commit
9b8e91f02e
@ -1,3 +1,28 @@ |
||||
# brasero2tex |
||||
|
||||
Programme de génération de pochette de CD LaTeX à partir d'un fichier XML de projet `brasero`. |
||||
Programme de génération de pochette de CD LaTeX à partir d'un fichier XML de projet `brasero`. La pochette générée se base sur le paquetage `cd-cover` de LaTeX. Ce programme a été écrit en plusieurs langages: `python`, `rust`, `golang`. Les trois versions sont équivalentes. |
||||
|
||||
Pour utiliser la version python: |
||||
``` |
||||
# rien à compiler |
||||
``` |
||||
|
||||
Pour utiliser la version rust: |
||||
``` |
||||
cargo build --release |
||||
``` |
||||
|
||||
Pour utiliser la version golang: |
||||
``` |
||||
go build brasero2tex.go |
||||
``` |
||||
|
||||
Utilisation du programme: |
||||
``` |
||||
brasero2tex projet.brasero > pochette.tex |
||||
``` |
||||
|
||||
Génération du fichier PDF: |
||||
``` |
||||
pdflatex pochette.tex |
||||
``` |
||||
|
@ -0,0 +1,129 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"os" |
||||
"time" |
||||
"strings" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"encoding/xml" |
||||
"net/url" |
||||
) |
||||
|
||||
type Braseroaudio struct { |
||||
XMLName xml.Name `xml:"audio"` |
||||
Uri string `xml:"uri"` |
||||
Start uint64 `xml:"start"` |
||||
End uint64 `xml:"end"` |
||||
Title string `xml:"title"` |
||||
Artist string `xml:"artist"` |
||||
} |
||||
|
||||
type Braserotrack struct { |
||||
XMLName xml.Name `xml:"track"` |
||||
Audio []Braseroaudio `xml:"audio"` |
||||
} |
||||
|
||||
type Braseroproject struct { |
||||
XMLName xml.Name `xml:"braseroproject"` |
||||
Version string `xml:"version"` |
||||
Label string `xml:"label"` |
||||
Track Braserotrack `xml:"track"` |
||||
} |
||||
|
||||
func escape(s string) string { |
||||
escape := map[string]string { |
||||
"_": "\\_", |
||||
"#": "\\#", |
||||
"$": "\\$", |
||||
"%": "\\%", |
||||
"^": "\\^", |
||||
"&": "\\&", |
||||
"{": "\\{", |
||||
"}": "\\}", |
||||
"~": "\\~", |
||||
} |
||||
|
||||
var escaped = s |
||||
escaped = strings.Replace(escaped, "\\", "\\textbackslash", -1) |
||||
for k, v := range escape { |
||||
escaped = strings.Replace(escaped, k, v, -1) |
||||
} |
||||
|
||||
return escaped |
||||
} |
||||
|
||||
func main() { |
||||
var args = os.Args[1:] |
||||
|
||||
if len(args) != 1 { |
||||
fmt.Println("missing brasero project file") |
||||
os.Exit(1) |
||||
} |
||||
|
||||
content, err := ioutil.ReadFile(args[0]) |
||||
|
||||
if (err != nil) { |
||||
fmt.Println("error reading brasero project file") |
||||
os.Exit(1) |
||||
} |
||||
|
||||
var x Braseroproject; |
||||
if err := xml.Unmarshal(content, &x); err != nil { |
||||
fmt.Println("xml error parsing brasero project") |
||||
os.Exit(1) |
||||
} |
||||
|
||||
var tex_begin = `\documentclass{cd-cover}[a4paper,landscape] |
||||
\usepackage[french]{babel} |
||||
\usepackage[utf8]{inputenc} |
||||
\usepackage[T1]{fontenc} |
||||
\usepackage{sans} |
||||
` |
||||
var com = fmt.Sprintf("\\newcommand{\\TITLE}{%s}\n", x.Label) |
||||
tex_begin += com |
||||
com = fmt.Sprintf("\\newcommand{\\SUBTITLE}{%s}\n", "Compilation") |
||||
tex_begin += com |
||||
var date = time.Now() |
||||
com = fmt.Sprintf("\\newcommand{\\DATE}{%02d-%02d-%d}\n", date.Day(), date.Month(), date.Year()); |
||||
tex_begin += com |
||||
com = ` |
||||
\begin{document} |
||||
\parindent=0pt\parskip=0pt |
||||
\begin{bookletsheets} |
||||
|
||||
\begin{center} |
||||
\begin{framebox} |
||||
{\LARGE \textbf{\TITLE}} |
||||
\end{framebox} |
||||
\end{center} |
||||
\vfill |
||||
{\large{\textbf\SUBTITLE}}\hfill{\large\DATE} |
||||
|
||||
\end{bookletsheets} |
||||
\begin{backsheet}{\hfil\TITLE\hfil\SUBTITLE\hfil\DATE} |
||||
|
||||
\begin{center} |
||||
\textbf{\TITLE} |
||||
\end{center} |
||||
\begin{enumerate} |
||||
` |
||||
tex_begin += com |
||||
fmt.Println(tex_begin) |
||||
for i := 0; i < len(x.Track.Audio); i++ { |
||||
audio := x.Track.Audio[i] |
||||
title, _ := url.QueryUnescape(audio.Title) |
||||
artist, _ := url.QueryUnescape(audio.Artist) |
||||
duration := (audio.End - audio.Start) / 1000000 |
||||
sec := duration / 1000 |
||||
min := sec / 60 |
||||
sec = sec % 60 |
||||
fmt.Printf("\\item %s (%s) \\dotfill %d'%02d\n", escape(title), escape(artist), min, sec) |
||||
} |
||||
|
||||
fmt.Println("\\end{enumerate}") |
||||
fmt.Println() |
||||
fmt.Println("\\end{backsheet}") |
||||
fmt.Println("\\end{document}") |
||||
|
||||
} |
@ -0,0 +1,66 @@ |
||||
#!/usr/bin/env python3 |
||||
# -*- coding: utf-8 -*- |
||||
import sys, re, datetime, urllib.parse, xml.etree.ElementTree as ET |
||||
|
||||
if len(sys.argv) < 2: |
||||
sys.exit('Usage: %s brasero.project.file' % sys.argv[0]) |
||||
|
||||
tree = ET.ElementTree(file=sys.argv[1]) |
||||
root = tree.getroot() |
||||
|
||||
label = next(root.iter("label")).text.replace('&', '\\&') |
||||
sub = "Compil Benoît" |
||||
today = datetime.date.today() |
||||
|
||||
print(""" |
||||
\\documentclass{cd-cover}[a4paper,landscape] |
||||
\\usepackage[french]{babel} |
||||
\\usepackage[utf8]{inputenc} |
||||
\\usepackage[T1]{fontenc} |
||||
\\usepackage{sans} |
||||
\\newcommand{\\TITLE}{""" + label + """} |
||||
\\newcommand{\\SUBTITLE}{""" + sub + """} |
||||
\\newcommand{\\DATE}{""" + str(today) + """} |
||||
|
||||
\\begin{document} |
||||
\\parindent=0pt\\parskip=0pt |
||||
\\begin{bookletsheets} |
||||
|
||||
\\begin{center} |
||||
\\begin{framebox} |
||||
{\\LARGE \\textbf{\\TITLE}} |
||||
\\end{framebox} |
||||
\\end{center} |
||||
\\vfill |
||||
{\\large{\\textbf\\SUBTITLE}}\\hfill{\\large\\DATE} |
||||
|
||||
\\end{bookletsheets} |
||||
\\begin{backsheet}{\\hfil\\TITLE\\hfil\\SUBTITLE\\hfil\\DATE} |
||||
|
||||
\\begin{center} |
||||
\\textbf{\\TITLE} |
||||
\\end{center} |
||||
\\begin{enumerate}""") |
||||
|
||||
for e in root.iter("audio"): |
||||
title = urllib.parse.unquote(next(e.iter("title")).text).replace('&','\\&') |
||||
artist = urllib.parse.unquote(next(e.iter("artist")).text).replace('&', '\\&') |
||||
duration = int (next(e.iter("end")).text) - int (next(e.iter("start")).text) |
||||
duration /= 1000000 |
||||
sec = duration / 1000 |
||||
m = str(int(sec / 60)) |
||||
s = int(sec) % 60 |
||||
|
||||
if s < 10: |
||||
s = "0" + str(s) |
||||
else: |
||||
s = str(s) |
||||
|
||||
print ("\\item {} ({}) \\dotfill {}'{}".format(title, artist, m, s)) |
||||
|
||||
print(""" |
||||
\\end{enumerate} |
||||
|
||||
\\end{backsheet} |
||||
\\end{document} |
||||
""") |
@ -0,0 +1 @@ |
||||
/target |
@ -0,0 +1,15 @@ |
||||
[package] |
||||
name = "brasero2tex" |
||||
version = "0.1.0" |
||||
authors = ["Benoît Rouits <brouits@free.fr>"] |
||||
edition = "2018" |
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
||||
[dependencies] |
||||
structopt = "*" |
||||
serde = "*" |
||||
serde_derive = "*" |
||||
serde-xml-rs = "*" |
||||
percent-encoding = "*" |
||||
chrono = "*" |
@ -0,0 +1,119 @@ |
||||
use serde_derive::Deserialize; |
||||
use structopt::StructOpt; |
||||
use serde_xml_rs::from_str; |
||||
use percent_encoding::percent_decode_str; |
||||
use chrono::Local; |
||||
|
||||
#[derive(StructOpt)] |
||||
struct Cli { |
||||
/// The path to the file to read
|
||||
#[structopt(parse(from_os_str))] |
||||
path: std::path::PathBuf, |
||||
} |
||||
|
||||
|
||||
#[derive(Debug, Deserialize)] |
||||
struct Audio { |
||||
uri: String, |
||||
start: u64, |
||||
end: u64, |
||||
title: String, |
||||
artist: String |
||||
} |
||||
|
||||
#[derive(Debug, Deserialize)] |
||||
struct Track { |
||||
audio: Vec<Audio> |
||||
} |
||||
|
||||
#[derive(Debug, Deserialize)] |
||||
struct Braseroproject { |
||||
version: String, |
||||
label: String, |
||||
track: Track
|
||||
} |
||||
|
||||
fn escape(s: String) -> String { |
||||
let escape = vec![ |
||||
('\\', "\\textbackslash{}"), |
||||
('_', "\\_"), |
||||
('#', "\\#"), |
||||
('$', "\\$"), |
||||
('%', "\\%"), |
||||
('^', "\\^"), |
||||
('&', "\\&"), |
||||
('{', "\\{"), |
||||
('}', "\\}"), |
||||
('~', "\\~") |
||||
]; |
||||
|
||||
|
||||
let mut escaped = s; |
||||
for esc in escape { |
||||
escaped = escaped.replace(esc.0, esc.1); |
||||
} |
||||
|
||||
return escaped; |
||||
} |
||||
|
||||
fn main() { |
||||
let args = Cli::from_args(); |
||||
let content = std::fs::read_to_string(&args.path) |
||||
.expect("could not read file"); |
||||
|
||||
let xml: Braseroproject = from_str(&content).unwrap(); |
||||
|
||||
let mut tex_begin = r###"\documentclass{cd-cover}[a4paper,landscape] |
||||
\usepackage[french]{babel} |
||||
\usepackage[utf8]{inputenc} |
||||
\usepackage[T1]{fontenc} |
||||
\usepackage{sans} |
||||
"###.to_string(); |
||||
|
||||
let label = escape(xml.label); |
||||
let com = format!("\\newcommand{{\\TITLE}}{{{}}}\n", label); |
||||
tex_begin += &com; |
||||
let com = format!("\\newcommand{{\\SUBTITLE}}{{{}}}\n", "Compilation"); |
||||
tex_begin += &com; |
||||
let date = Local::now().date().format("%d-%m-%Y"); |
||||
let com = format!("\\newcommand{{\\DATE}}{{{}}}\n", date); |
||||
tex_begin += &com; |
||||
let com = r###" |
||||
\begin{document} |
||||
\parindent=0pt\parskip=0pt |
||||
\begin{bookletsheets} |
||||
|
||||
\begin{center} |
||||
\begin{framebox} |
||||
{\LARGE \textbf{\TITLE}} |
||||
\end{framebox} |
||||
\end{center} |
||||
\vfill |
||||
{\large{\textbf\SUBTITLE}}\hfill{\large\DATE} |
||||
|
||||
\end{bookletsheets} |
||||
\begin{backsheet}{\hfil\TITLE\hfil\SUBTITLE\hfil\DATE} |
||||
|
||||
\begin{center} |
||||
\textbf{\TITLE} |
||||
\end{center} |
||||
\begin{enumerate} |
||||
"###; |
||||
|
||||
tex_begin += &com; |
||||
println!("{}", tex_begin); |
||||
for audio in xml.track.audio { |
||||
let title = escape(percent_decode_str(&audio.title).decode_utf8().unwrap().to_string()); |
||||
let artist = escape(percent_decode_str(&audio.artist).decode_utf8().unwrap().to_string()); |
||||
let duration = (audio.end - audio.start) / 1000000; |
||||
let sec = duration / 1000; |
||||
let m = (sec / 60).to_string(); |
||||
let sec = sec % 60; |
||||
println!("\\item {} ({}) \\dotfill {}'{:02}", title, artist, m, sec); |
||||
} |
||||
|
||||
println!(r"\end{{enumerate}}"); |
||||
println!(); |
||||
println!(r"\end{{backsheet}}"); |
||||
println!(r"\end{{document}}"); |
||||
} |
Loading…
Reference in new issue