Compare commits
2 Commits
bdf4d4810e
...
51e01139ed
Author | SHA1 | Date | |
---|---|---|---|
51e01139ed | |||
6a0669df4a |
@ -23,6 +23,12 @@
|
||||
</header>
|
||||
<main id="main">
|
||||
<h1>Editing <code id="filename"></code><span id="unsaved"> - Unsaved</span></h1>
|
||||
<div id="series-toolbar">
|
||||
<button id="prev-episode"></button>
|
||||
<div id="cur-episode"></div>
|
||||
<button id="next-episode"></button>
|
||||
</div>
|
||||
<div class="sep"></div>
|
||||
<div class="fields">
|
||||
<div class="field">
|
||||
<label for="title">Title</label>
|
||||
|
@ -5,6 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Metadata Editor</title>
|
||||
<link rel="stylesheet" href="/static/css/base.css">
|
||||
<link rel="stylesheet" href="/static/css/index.css">
|
||||
<script src="/static/js/index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@ -12,10 +13,16 @@
|
||||
<h1>Metadata Editor</h1>
|
||||
</header>
|
||||
<main>
|
||||
<div>
|
||||
<label for="file-sel">Choose a file</label>
|
||||
<select name="file-sel" id="file-sel" oninput="selectFile(event)"></select>
|
||||
</div>
|
||||
<a id="film-template" class="template file film">
|
||||
<img src="/static/images/film.svg">
|
||||
<div class="title"></div>
|
||||
</a>
|
||||
<a id="series-template" class="template file series">
|
||||
<img src="/static/images/series.svg">
|
||||
<div class="title"></div>
|
||||
<div class="episodes"><span class="num"></span> episode(s)</div>
|
||||
</a>
|
||||
<div id="files"></div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
37
editor/public/static/css/index.css
Normal file
37
editor/public/static/css/index.css
Normal file
@ -0,0 +1,37 @@
|
||||
#files {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(15em, 1fr));
|
||||
grid-auto-rows: 15em;
|
||||
gap: 0.8em;
|
||||
place-items: center;
|
||||
|
||||
.file {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
padding: 0.4em;
|
||||
border-radius: 1.2em;
|
||||
|
||||
&:hover {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 10em;
|
||||
height: 10em;
|
||||
}
|
||||
|
||||
.title {
|
||||
overflow-wrap: anywhere;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
148
editor/public/static/images/film.svg
Normal file
148
editor/public/static/images/film.svg
Normal file
@ -0,0 +1,148 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 128 128"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
sodipodi:docname="film.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="true"
|
||||
inkscape:zoom="5.7798541"
|
||||
inkscape:cx="54.932183"
|
||||
inkscape:cy="64.274979"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="1"
|
||||
spacingy="0.99999998"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="16"
|
||||
enabled="true"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g12"
|
||||
transform="translate(1.7078686e-5,-8)">
|
||||
<g
|
||||
id="g11">
|
||||
<g
|
||||
id="g10">
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path1"
|
||||
cx="56"
|
||||
cy="72"
|
||||
r="40" />
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path1-5"
|
||||
cx="56"
|
||||
cy="72"
|
||||
r="32" />
|
||||
</g>
|
||||
<g
|
||||
id="g7">
|
||||
<g
|
||||
id="g6">
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path2"
|
||||
cx="56"
|
||||
cy="53"
|
||||
r="6" />
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path2-2"
|
||||
cx="72.454483"
|
||||
cy="62.5"
|
||||
r="6" />
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path2-7"
|
||||
cx="72.454483"
|
||||
cy="81.5"
|
||||
r="6" />
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path2-6"
|
||||
cx="56"
|
||||
cy="91"
|
||||
r="6" />
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path2-61"
|
||||
cx="39.545517"
|
||||
cy="81.5"
|
||||
r="6" />
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path2-20"
|
||||
cx="39.545517"
|
||||
cy="62.5"
|
||||
r="6" />
|
||||
</g>
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:square;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path3"
|
||||
cx="56"
|
||||
cy="72"
|
||||
r="3" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g9">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 70.704221,109.19927 C 78.261106,107.5993 90,108 98,103 c 8,-5 12,-6 14,-7 l -7,-14 c -5,2 -6,7 -9,11 -3,4 -10.113506,5.585663 -10.113506,5.585663"
|
||||
id="path4"
|
||||
sodipodi:nodetypes="czcczc" />
|
||||
<g
|
||||
id="g8">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 97.649449,90.439824 5.231401,9.711746"
|
||||
id="path5" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 90.543116,96.977586 -0.626729,9.286354"
|
||||
id="path6"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.9 KiB |
64
editor/public/static/images/next.svg
Normal file
64
editor/public/static/images/next.svg
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="64mm"
|
||||
height="64mm"
|
||||
viewBox="0 0 64 64"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
sodipodi:docname="next_icon.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="true"
|
||||
inkscape:zoom="2.0560856"
|
||||
inkscape:cx="8.7544994"
|
||||
inkscape:cy="178.49452"
|
||||
inkscape:window-width="1858"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="1982"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="0.26458333"
|
||||
spacingy="0.26458333"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="8"
|
||||
dotted="false"
|
||||
gridanglex="30"
|
||||
gridanglez="30"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="color:#000000;fill:#000000;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||
d="m 19.698341,10.884614 a 4.2333331,4.2333331 0 0 0 -2.763672,1.691406 4.2333331,4.2333331 0 0 0 0.933594,5.914062 L 36.444435,31.999848 17.868263,45.509614 a 4.2333331,4.2333331 0 0 0 -0.933594,5.914062 4.2333331,4.2333331 0 0 0 5.914063,0.933594 L 46.131935,35.423676 a 4.2337564,4.2337564 0 0 0 0,-6.847656 L 22.848732,11.642426 a 4.2333331,4.2333331 0 0 0 -3.150391,-0.757812 z"
|
||||
id="path1" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
64
editor/public/static/images/prev.svg
Normal file
64
editor/public/static/images/prev.svg
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="64mm"
|
||||
height="64mm"
|
||||
viewBox="0 0 64 64"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
sodipodi:docname="prev_icon.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="true"
|
||||
inkscape:zoom="2.0560856"
|
||||
inkscape:cx="8.7544994"
|
||||
inkscape:cy="178.49452"
|
||||
inkscape:window-width="1858"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="1982"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="0.26458333"
|
||||
spacingy="0.26458333"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="8"
|
||||
dotted="false"
|
||||
gridanglex="30"
|
||||
gridanglez="30"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="color:#000000;fill:#000000;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
|
||||
d="m 44.301659,10.884614 a 4.2333331,4.2333331 0 0 1 2.763672,1.691406 4.2333331,4.2333331 0 0 1 -0.933594,5.914062 L 27.555565,31.999848 46.131737,45.509614 a 4.2333331,4.2333331 0 0 1 0.933594,5.914062 4.2333331,4.2333331 0 0 1 -5.914063,0.933594 L 17.868065,35.423676 a 4.2337564,4.2337564 0 0 1 0,-6.847656 L 41.151268,11.642426 a 4.2333331,4.2333331 0 0 1 3.150391,-0.757812 z"
|
||||
id="path1" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
188
editor/public/static/images/series.svg
Normal file
188
editor/public/static/images/series.svg
Normal file
@ -0,0 +1,188 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 128 128"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
sodipodi:docname="series.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="true"
|
||||
inkscape:zoom="4.086974"
|
||||
inkscape:cx="58.845493"
|
||||
inkscape:cy="81.356034"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="1"
|
||||
spacingy="0.99999998"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="16"
|
||||
enabled="true"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,8,0,1 @ F,0,1,1,0,8,0,1 @ F,0,1,1,0,8,0,1 @ F,0,0,1,0,8,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect14"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,2.1961523,0,1 @ F,0,1,1,0,2.1961523,0,1 @ F,0,0,1,0,2.1961523,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15-4"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,8,0,1 @ F,0,1,1,0,8,0,1 @ F,0,1,1,0,8,0,1 @ F,0,0,1,0,8,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15-8"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,8,0,1 @ F,0,1,1,0,8,0,1 @ F,0,1,1,0,8,0,1 @ F,0,0,1,0,8,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15-9"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,8,0,1 @ F,0,1,1,0,8,0,1 @ F,0,1,1,0,8,0,1 @ F,0,0,1,0,8,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
id="g4"
|
||||
transform="translate(-8,-8)">
|
||||
<g
|
||||
id="g3">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 24,56 v 48 a 8,8 45 0 0 8,8 h 64 a 8,8 135 0 0 8,-8 V 56 A 8,8 45 0 0 96,48 H 32 a 8,8 135 0 0 -8,8 z"
|
||||
id="path12"
|
||||
inkscape:path-effect="#path-effect15"
|
||||
inkscape:original-d="m 24,48 v 64 h 80 V 48 Z" />
|
||||
<g
|
||||
id="g1">
|
||||
<circle
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path13"
|
||||
cx="64"
|
||||
cy="80"
|
||||
r="16" />
|
||||
<path
|
||||
id="path14"
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
|
||||
inkscape:transform-center-x="-2"
|
||||
transform="translate(-1)"
|
||||
d="m 70.098076,81.098076 -8.196152,4.732051 A 1.2679491,1.2679491 30 0 1 60,84.732051 v -9.464102 a 1.2679491,1.2679491 150 0 1 1.901924,-1.098076 l 8.196152,4.732051 a 1.2679491,1.2679491 90 0 1 0,2.196152 z"
|
||||
inkscape:path-effect="#path-effect14"
|
||||
inkscape:original-d="M 72,80 60,86.928203 V 73.071797 Z" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g2">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 99.289904,111.29439 C 102.06725,110.03979 104,107.2456 104,104 V 56 c 0,-4.418278 -3.58172,-8 -8,-8 H 32 c -3.244751,0 -6.038321,1.93174 -7.293397,4.707908"
|
||||
id="path12-9"
|
||||
transform="translate(8,-8)"
|
||||
sodipodi:nodetypes="cssssc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 115.2899,95.29439 C 118.06725,94.03979 120,91.2456 120,88 V 40 c 0,-4.418278 -3.58172,-8 -8,-8 H 48 c -3.244751,0 -6.038321,1.93174 -7.293397,4.707908"
|
||||
id="path12-9-5"
|
||||
sodipodi:nodetypes="cssssc" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.2 KiB |
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +1,59 @@
|
||||
function addOptions(files) {
|
||||
const select = document.getElementById("file-sel")
|
||||
select.innerHTML = ""
|
||||
const defaultOpt = document.createElement("option")
|
||||
defaultOpt.innerText = "----- Select a file -----"
|
||||
defaultOpt.value = ""
|
||||
select.appendChild(defaultOpt)
|
||||
files.forEach(file => {
|
||||
const option = document.createElement("option")
|
||||
option.innerText = file
|
||||
option.value = file
|
||||
select.appendChild(option)
|
||||
})
|
||||
function makeFilm(meta) {
|
||||
const file = document.getElementById("film-template").cloneNode(true)
|
||||
file.querySelector(".title").innerText = meta.title
|
||||
return file
|
||||
}
|
||||
|
||||
function selectFile(event) {
|
||||
const file = event.target.value
|
||||
if (file !== "") {
|
||||
const url = new URL("/edit/", window.location.origin)
|
||||
url.searchParams.set("f", file)
|
||||
window.location.href = url.href
|
||||
function makeSeries(meta) {
|
||||
const file = document.getElementById("series-template").cloneNode(true)
|
||||
file.querySelector(".title").innerText = meta.title
|
||||
file.querySelector(".episodes .num").innerText = meta.episodes
|
||||
return file
|
||||
}
|
||||
|
||||
function makeFile(meta) {
|
||||
let file
|
||||
switch (meta.type) {
|
||||
case "film":
|
||||
file = makeFilm(meta)
|
||||
break
|
||||
case "series":
|
||||
file = makeSeries(meta)
|
||||
break
|
||||
default:
|
||||
throw new Error(`Invalid file type '${meta.type}'`)
|
||||
}
|
||||
|
||||
file.title = meta.filename
|
||||
file.id = null
|
||||
file.classList.remove("template")
|
||||
const url = new URL("/edit/", window.location.origin)
|
||||
url.searchParams.set("f", meta.filename)
|
||||
file.href = url.href
|
||||
return file
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object[]} files
|
||||
*/
|
||||
function addFiles(files) {
|
||||
const list = document.getElementById("files")
|
||||
list.innerHTML = ""
|
||||
const filenames = files.map(meta => meta.filename)
|
||||
// Copy array because sort changes it in place
|
||||
Array.from(filenames).sort().forEach(filename => {
|
||||
const i = filenames.indexOf(filename)
|
||||
const meta = files[i]
|
||||
const file = makeFile(meta)
|
||||
list.appendChild(file)
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
fetch("/api/files").then(res => {
|
||||
return res.json()
|
||||
}).then(files => {
|
||||
addOptions(files)
|
||||
addFiles(files)
|
||||
})
|
||||
})
|
@ -316,7 +316,7 @@ export default class IntegrityManager {
|
||||
if (fields.flags.hearing_impaired) {
|
||||
name += " SDH"
|
||||
}
|
||||
name += " : " + fields.type
|
||||
name += " | " + fields.type
|
||||
break
|
||||
}
|
||||
return name
|
||||
|
5
editor/public/static/js/media_file.mjs
Normal file
5
editor/public/static/js/media_file.mjs
Normal file
@ -0,0 +1,5 @@
|
||||
export default class MediaFile {
|
||||
constructor(data) {
|
||||
|
||||
}
|
||||
}
|
85
editor/public/static/js/metadata.mjs
Normal file
85
editor/public/static/js/metadata.mjs
Normal file
@ -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(?<season>\d+)e(?<episode>\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)
|
||||
}
|
@ -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() {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,6 +11,7 @@ MAX_SIZE = 10e6
|
||||
|
||||
class MyHandler(SimpleHTTPRequestHandler):
|
||||
DATA_DIR = "metadata"
|
||||
CACHE = {}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(
|
||||
@ -22,7 +23,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,18 +54,16 @@ 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()
|
||||
files: list[str] = self.get_files_meta()
|
||||
self.send_json(files)
|
||||
elif path.startswith("file"):
|
||||
filename: str = path.split("/", 1)[1]
|
||||
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,13 +102,51 @@ 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
|
||||
return True
|
||||
|
||||
def get_files_meta(self):
|
||||
files: list[str] = self.get_files()
|
||||
files_meta: list[dict] = []
|
||||
|
||||
deleted = set(self.CACHE.keys()) - set(files)
|
||||
for filename in deleted:
|
||||
del self.CACHE[deleted]
|
||||
|
||||
for filename in files:
|
||||
path: str = os.path.join(self.DATA_DIR, filename)
|
||||
last_modified: float = os.path.getmtime(path)
|
||||
if filename not in self.CACHE or self.CACHE[filename]["ts"] < last_modified:
|
||||
self.update_file_meta(filename)
|
||||
|
||||
files_meta.append(self.CACHE[filename])
|
||||
|
||||
return files_meta
|
||||
|
||||
def update_file_meta(self, filename: str):
|
||||
path: str = os.path.join(self.DATA_DIR, filename)
|
||||
|
||||
meta = {
|
||||
"filename": filename,
|
||||
"ts": os.path.getmtime(path)
|
||||
}
|
||||
|
||||
with open(path, "r") as f:
|
||||
data = json.load(f)
|
||||
is_series = "filename" not in data
|
||||
meta["type"] = "series" if is_series else "film"
|
||||
if is_series:
|
||||
meta["episodes"] = len(data)
|
||||
meta["title"] = filename.split("_metadata")[0]
|
||||
else:
|
||||
meta["title"] = data["title"]
|
||||
|
||||
self.CACHE[filename] = meta
|
||||
|
||||
|
||||
def main():
|
||||
with socketserver.TCPServer(("", PORT), MyHandler) as httpd:
|
||||
|
Loading…
x
Reference in New Issue
Block a user