diff --git a/editor/public/static/css/edit.css b/editor/public/static/css/edit.css
index 0b80eba..4d57952 100644
--- a/editor/public/static/css/edit.css
+++ b/editor/public/static/css/edit.css
@@ -337,6 +337,7 @@ button.improve {
height: 100%;
display: flex;
flex-direction: column;
+ max-width: 30em;
&:not(.show) {
display: none;
@@ -369,4 +370,44 @@ button.improve {
padding: 0.4em;
}
}
+}
+
+.sep {
+ border-bottom: solid black 1px;
+}
+
+#series-toolbar {
+ display: flex;
+ gap: 0.4em;
+ padding: 0.4em;
+ align-items: center;
+
+ &:not(.show) {
+ display: none;
+ }
+
+ button {
+ background: var(--img);
+ width: 2.4em;
+ height: 2.4em;
+ background-color: transparent;
+ border: none;
+ background-size: 80%;
+ background-position: center;
+ background-repeat: no-repeat;
+ cursor: pointer;
+ border-radius: 0.4em;
+
+ &:hover {
+ background-color: #f1f1f1;
+ }
+
+ prev-episode {
+ --img: url("/static/images/prev.svg");
+ }
+
+ next-episode {
+ --img: url("/static/images/next.svg");
+ }
+ }
}
\ No newline at end of file
diff --git a/editor/public/static/images/next.svg b/editor/public/static/images/next.svg
new file mode 100644
index 0000000..db48ec2
--- /dev/null
+++ b/editor/public/static/images/next.svg
@@ -0,0 +1,64 @@
+
+
+
+
diff --git a/editor/public/static/images/prev.svg b/editor/public/static/images/prev.svg
new file mode 100644
index 0000000..4217564
--- /dev/null
+++ b/editor/public/static/images/prev.svg
@@ -0,0 +1,64 @@
+
+
+
+
diff --git a/editor/public/static/js/editor.mjs b/editor/public/static/js/editor.mjs
index ba62581..1cfd1ea 100644
--- a/editor/public/static/js/editor.mjs
+++ b/editor/public/static/js/editor.mjs
@@ -1,6 +1,7 @@
import TracksTable from "./tracks_table.mjs"
import IntegrityManager from "./integrity_manager.mjs"
import { updateObjectFromJoinedKey } from "./utils.mjs"
+import { loadMetadata, SeriesMetadata } from "./metadata.mjs"
export default class Editor {
constructor() {
@@ -18,10 +19,11 @@ export default class Editor {
subtitle: new TracksTable(this, "subtitle", "subtitle-tracks", "subtitle_tracks")
}
+ this.metadata = null
this.data = {}
this.dirty = false
- this.integrity_mgr = new IntegrityManager(this)
+ this.integrityMgr = new IntegrityManager(this)
document.getElementById("check-integrity").addEventListener("click", () => this.checkIntegrity())
document.getElementById("improve-all").addEventListener("click", () => this.improveAllNames())
@@ -29,6 +31,14 @@ export default class Editor {
document.getElementById("reload").addEventListener("click", () => window.location.reload())
document.getElementById("toggle-notifs").addEventListener("click", () => this.toggleNotifications())
document.getElementById("close-notifs").addEventListener("click", () => this.closeNotifications())
+ document.getElementById("prev-episode").addEventListener("click", () => this.prevEpisode())
+ document.getElementById("next-episode").addEventListener("click", () => this.nextEpisode())
+
+ this.titleInput = document.getElementById("title")
+ this.titleInput.addEventListener("change", () => {
+ this.data.title = this.titleInput.value
+ this.setDirty()
+ })
this.setup()
}
@@ -46,14 +56,27 @@ export default class Editor {
return null
}).then(res => {
if (res !== null) {
- this.data = res
+ this.metadata = loadMetadata(res)
this.displayData()
}
})
}
displayData() {
- document.getElementById("title").value = this.data.title
+ const seriesToolbar = document.getElementById("series-toolbar")
+ if (this.metadata instanceof SeriesMetadata) {
+ seriesToolbar.classList.add("show")
+ const cur = this.metadata.episodeIdx + 1
+ const tot = this.metadata.episodes.length
+ const epMeta = this.metadata.getCurrentEpisode()
+ const season = epMeta.season
+ const episode = epMeta.episode
+ seriesToolbar.querySelector("#cur-episode").innerText = `S${season}E${episode} (${cur} / ${tot})`
+ } else {
+ seriesToolbar.classList.remove("show")
+ }
+ this.data = this.metadata.getData()
+ this.titleInput.value = this.data.title
this.tables.audio.loadTracks(this.data.audio_tracks)
this.tables.subtitle.loadTracks(this.data.subtitle_tracks)
}
@@ -61,7 +84,7 @@ export default class Editor {
save() {
fetch(`/api/file/${this.filename}`, {
method: "POST",
- body: JSON.stringify(this.data),
+ body: JSON.stringify(this.metadata.data),
headers: {
"Content-Type": "application/json"
}
@@ -100,13 +123,13 @@ export default class Editor {
}
checkIntegrity() {
- if (this.integrity_mgr.checkIntegrity()) {
+ if (this.integrityMgr.checkIntegrity()) {
this.notify("No integrity error detected !", "success")
}
}
improveAllNames() {
- this.integrity_mgr.improveAllNames()
+ this.integrityMgr.improveAllNames()
this.notify("Improved all names !", "success")
}
@@ -128,4 +151,20 @@ export default class Editor {
const hist = document.getElementById("notifs-hist")
hist.classList.remove("show")
}
+
+ prevEpisode() {
+ if (this.metadata instanceof SeriesMetadata) {
+ if (this.metadata.prev()) {
+ this.displayData()
+ }
+ }
+ }
+
+ nextEpisode() {
+ if (this.metadata instanceof SeriesMetadata) {
+ if (this.metadata.next()) {
+ this.displayData()
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/editor/public/static/js/index.js b/editor/public/static/js/index.js
index c9c646f..71fd02d 100644
--- a/editor/public/static/js/index.js
+++ b/editor/public/static/js/index.js
@@ -5,7 +5,7 @@ function addOptions(files) {
defaultOpt.innerText = "----- Select a file -----"
defaultOpt.value = ""
select.appendChild(defaultOpt)
- files.forEach(file => {
+ files.sort().forEach(file => {
const option = document.createElement("option")
option.innerText = file
option.value = file
diff --git a/editor/public/static/js/media_file.mjs b/editor/public/static/js/media_file.mjs
new file mode 100644
index 0000000..6568723
--- /dev/null
+++ b/editor/public/static/js/media_file.mjs
@@ -0,0 +1,5 @@
+export default class MediaFile {
+ constructor(data) {
+
+ }
+}
\ No newline at end of file
diff --git a/editor/public/static/js/metadata.mjs b/editor/public/static/js/metadata.mjs
new file mode 100644
index 0000000..4eb48db
--- /dev/null
+++ b/editor/public/static/js/metadata.mjs
@@ -0,0 +1,85 @@
+export default class Metadata {
+ constructor(data) {
+ this.data = data
+ }
+
+ getData() {
+ return this.data
+ }
+}
+
+export class MediaMetadata extends Metadata {
+ constructor(data) {
+ super(data)
+ }
+}
+
+export class EpisodeMetadata extends MediaMetadata {
+ REGEXP = /s(?
\d+)e(?\d+)/i
+
+ /**
+ *
+ * @param {object} data
+ * @param {string} episodeKey
+ */
+ constructor(data, episodeKey) {
+ super(data)
+ this.key = episodeKey
+
+ let m = this.key.match(this.REGEXP) ?? this.data.filename.match(this.REGEXP)
+ this.season = "xx"
+ this.episode = "xx"
+ if (m) {
+ this.season = m.groups.season
+ this.episode = m.groups.episode
+ }
+ }
+}
+
+export class SeriesMetadata extends Metadata {
+ constructor(data) {
+ super(data)
+ const episodeKeys = Object.keys(data).sort()
+ this.episodes = episodeKeys.map(key => {
+ return new EpisodeMetadata(data[key], key)
+ })
+
+ this.episodeIdx = 0
+ }
+
+ getCurrentEpisode() {
+ return this.episodes[this.episodeIdx]
+ }
+
+ getData() {
+ return this.getCurrentEpisode().getData()
+ }
+
+ prev() {
+ if (this.episodeIdx === 0) {
+ return false
+ }
+ this.episodeIdx -= 1
+ return true
+ }
+
+ next() {
+ if (this.episodeIdx === this.episodes.length - 1) {
+ return false
+ }
+ this.episodeIdx += 1
+ return true
+ }
+}
+
+/**
+ *
+ * @param {object} data
+ * @returns {Metadata}
+ */
+export function loadMetadata(data) {
+ if ("filename" in data) {
+ return new MediaMetadata(data)
+ }
+ return new SeriesMetadata(data)
+}
\ No newline at end of file
diff --git a/editor/public/static/js/tracks_table.mjs b/editor/public/static/js/tracks_table.mjs
index 34474b2..2069323 100644
--- a/editor/public/static/js/tracks_table.mjs
+++ b/editor/public/static/js/tracks_table.mjs
@@ -113,6 +113,7 @@ export class Track {
"warning"
)
value = lang
+ this.editValue(field.key, value)
}
}
@@ -140,19 +141,21 @@ export class Track {
this.table.editTrack(this.idx, key, value)
const input = this.row.querySelector(`[data-key='${key}']`)
- const fieldType = this.table.getFieldProps(key).type
- switch (fieldType) {
- case "bool":
- input.checked = value
- break
- default:
- input.value = value
- break
+ if (input) {
+ const fieldType = this.table.getFieldProps(key).type
+ switch (fieldType) {
+ case "bool":
+ input.checked = value
+ break
+ default:
+ input.value = value
+ break
+ }
}
}
improveName() {
- this.table.editor.integrity_mgr.improveName(this)
+ this.table.editor.integrityMgr.improveName(this)
}
}
@@ -184,9 +187,10 @@ export default class TracksTable {
this.table = document.getElementById(tableId)
this.headers = this.table.querySelector("thead tr")
this.body = this.table.querySelector("tbody")
- this.fields = []
- this.tracks = []
this.dataKey = dataKey
+
+ this.tracks = []
+ this.fields = []
this.onehots = {}
}
@@ -195,8 +199,8 @@ export default class TracksTable {
}
loadTracks(tracks) {
- this.tracks = tracks.map((t, i) => new Track(this, i, t))
this.clear()
+ this.tracks = tracks.map((t, i) => new Track(this, i, t))
if (tracks.length === 0) {
return
}
@@ -208,9 +212,11 @@ export default class TracksTable {
}
clear() {
+ this.tracks = []
+ this.fields = []
+ this.onehots = {}
this.headers.innerHTML = ""
this.body.innerHTML = ""
- this.fields = []
}
detectFields() {
diff --git a/editor/public/static/js/utils.mjs b/editor/public/static/js/utils.mjs
index e771bbb..3b711a3 100644
--- a/editor/public/static/js/utils.mjs
+++ b/editor/public/static/js/utils.mjs
@@ -41,6 +41,11 @@ export const LANGUAGES = {
code: "de",
aliases: ["de", "ger", "german", "allemand", "deutsch", "germany", "allemagne"]
},
+ "ita": {
+ display: "Italiano",
+ code: "it",
+ aliases: ["it", "ita", "italian", "italien", "italiano", "italy", "italie"]
+ },
"kor": {
display: "Korean",
code: "kr",
@@ -64,7 +69,7 @@ export const LANGUAGES = {
}
export function getLanguageAliases(langTag) {
- return (langTag === "und" ? [] : [langTag]).concat(LANGUAGES[langTag].aliases)
+ return [langTag].concat(LANGUAGES[langTag].aliases)
}
/**
diff --git a/editor/server.py b/editor/server.py
index 5d67811..2fbd994 100644
--- a/editor/server.py
+++ b/editor/server.py
@@ -22,7 +22,6 @@ class MyHandler(SimpleHTTPRequestHandler):
self.data: Optional[dict|list] = None
def read_body_data(self):
- self.log_message("Reading body data")
try:
size: int = int(self.headers["Content-Length"])
if size > MAX_SIZE:
@@ -54,7 +53,7 @@ class MyHandler(SimpleHTTPRequestHandler):
self.send_error(HTTPStatus.NOT_FOUND)
def handle_api_get(self, path: str):
- print(f"API request at {path}")
+ self.log_message(f"API request at {path}")
if path == "files":
files: list[str] = self.get_files()
self.send_json(files)
@@ -63,9 +62,7 @@ class MyHandler(SimpleHTTPRequestHandler):
data = self.read_file(filename)
if data is None:
self.send_error(HTTPStatus.NOT_FOUND)
- self.log_message("File not found")
else:
- self.log_message("Got file")
self.send_json(data)
else:
self.send_response(HTTPStatus.NOT_FOUND, f"Unknown path {path}")
@@ -104,8 +101,8 @@ class MyHandler(SimpleHTTPRequestHandler):
return False
try:
- with open(os.path.join(self.DATA_DIR, filename), "w") as f:
- json.dump(data, f, indent=2)
+ with open(os.path.join(self.DATA_DIR, filename), "w", encoding="utf-8") as f:
+ json.dump(data, f, indent=2, ensure_ascii=False)
except:
self.send_error(HTTPStatus.INTERNAL_SERVER_ERROR)
return False