home · contact · privacy
Show reduced file data form/table in playlist view. master
authorChristian Heller <c.heller@plomlompom.de>
Wed, 4 Dec 2024 16:24:00 +0000 (17:24 +0100)
committerChristian Heller <c.heller@plomlompom.de>
Wed, 4 Dec 2024 16:24:00 +0000 (17:24 +0100)
src/templates/_base.tmpl
src/templates/_macros.tmpl
src/templates/file_data.tmpl
src/templates/files.tmpl
src/templates/playlist.tmpl
src/ytplom/http.py
src/ytplom/misc.py

index 3f9edf40b2134f9ec6d28a803c1e4e1ceb7881f8..d87faebcc3cf98e0ca72b4de208fa7f7ebdbb136 100644 (file)
@@ -10,8 +10,7 @@
 <style>
 body { background-color: #aaaa00; }
 table { width: 100%; }
 <style>
 body { background-color: #aaaa00; }
 table { width: 100%; }
-th { text-align: left; }
-td { vertical-align: top; }
+td, th { vertical-align: top; text-align: left; }
 {% block css %}
 {% endblock %}
 </style>
 {% block css %}
 {% endblock %}
 </style>
index aa6ed501695179a0da89deb7685a8d359aa4da5c..89838d87b8c3a2e4b0633c73968a5f086f9650f4 100644 (file)
 </p>
 <hr />
 {% endmacro %}
 </p>
 <hr />
 {% endmacro %}
+
+
+{% macro file_data_form(file, unused_tags, page_names, flag_names=[], playlist_view=false) %}
+<form action="/{{page_names.file}}/{{file.digest.b64}}" method="POST" />
+<input type="hidden" name="redir" value="/{% if playlist_view %}{{page_names.playlist}}{% else %}{{page_names.file}}/{{file.digest.b64}}{% endif %}" />
+<table>
+<tr><th>path:</th><td class="top_field">{% if playlist_view %}<a href="/{{page_names.file}}/{{file.digest.b64}}">{% endif %}{{file.rel_path}}{% if playlist_view %}</a>{% endif %}</td></tr>
+{% if not playlist_view %}
+<tr><th>present:</th><td>{% if file.present %}<a href="/{{page_names.download}}/{{file.yt_id}}">yes</a>{% else %}no{% endif %}</td></tr>
+{% endif %}
+<tr><th>YouTube&nbsp;ID:</th><td><a href="/{{page_names.yt_result}}/{{file.yt_id}}">{{file.yt_id}}</a></tr>
+<tr>
+<th>tags</th>
+<td>
+<table>
+{% for tag in file.tags %}
+<tr><td class="tag_checkboxes"><input type="checkbox" name="tags" value="{{tag}}" checked /></td><td>{{tag}}</td></tr>
+{% endfor %}
+<tr><td class="tag_checkboxes">add:</td><td><input name="tags" list="unused_tags" autocomplete="off" /></td></tr>
+<datalist id="unused_tags" />
+{% for tag in unused_tags %}
+<option value="{{tag}}">{{tag}}</option>
+{% endfor %}
+</datalist>
+</table>
+</td>
+</tr>
+{% if not playlist_view %}
+<tr>
+<th>flags:</th>
+<td class="flags">
+{% for flag_name in flag_names %}
+{{ flag_name }}: <input type="checkbox" name="flags" value="{{flag_name}}" {% if file.is_flag_set(flag_name) %}checked {% endif %} /><br />
+{% endfor %}
+</td>
+</tr>
+{% endif %}
+</table>
+<input type="submit" />
+</form>
+{% endmacro %}
index b5a45b9d8fefabdf29043e525e336385a71cff3f..482f8ea67af2dd8fea209a98e74839f113dd62df 100644 (file)
@@ -2,41 +2,13 @@
 
 
 {% block css %}
 
 
 {% block css %}
-td { width: 100%; }
+td.top_field { width: 100%; }
 td.flags { text-align: right; }
 td.flags { text-align: right; }
+td.tag_checkboxes { width: 1em; }
 {% endblock %}
 
 
 {% block body %}
 {{ macros.nav_head(page_names) }}
 {% endblock %}
 
 
 {% block body %}
 {{ macros.nav_head(page_names) }}
-<form action="/{{page_names.file}}/{{file.digest.b64}}" method="POST" />
-<table>
-<tr><th>path:</th><td>{{file.rel_path}}</td></tr>
-<tr><th>YouTube ID:</th><td><a href="/{{page_names.yt_result}}/{{file.yt_id}}">{{file.yt_id}}</a></tr>
-<tr><th>present:</th><td>{% if file.present %}<a href="/{{page_names.download}}/{{file.yt_id}}">yes</a>{% else %}no{% endif %}</td></tr>
-<tr>
-<th>flags:</th>
-<td class="flags">
-{% for flag_name in flag_names %}
-{{ flag_name }}: <input type="checkbox" name="flags" value="{{flag_name}}" {% if file.is_flag_set(flag_name) %}checked {% endif %} /><br />
-{% endfor %}
-</td>
-</tr>
-<tr>
-<th>tags</th>
-<td>
-{% for tag in file.tags %}
-<input type="checkbox" name="tags" value="{{tag}}" checked /> {{tag}}<br />
-{% endfor %}
-<input name="tags" list="unused_tags" autocomplete="off" />
-<datalist id="unused_tags" />
-{% for tag in unused_tags %}
-<option value="{{tag}}">{{tag}}</option>
-{% endfor %}
-</datalist>
-</td>
-</tr>
-</table>
-<input type="submit" />
-</form>
+{{ macros.file_data_form(file, unused_tags, page_names, flag_names) }}
 {% endblock %}
 {% endblock %}
index f8286e54f3172335d73e29b92926e73558d61ae5..c4c452d82d41b65107697016c353f4e4f1bdf0ee 100644 (file)
@@ -15,7 +15,7 @@ show absent: <input type="checkbox" name="show_absent" {% if show_absent %}check
 <tr><th>size</th><th>actions</th><th>tags</th><th>path</th></tr>
 {% for file in files %}
 <tr>
 <tr><th>size</th><th>actions</th><th>tags</th><th>path</th></tr>
 {% for file in files %}
 <tr>
-<td>{{ file.size | round(3) }}</td>
+<td>{{ file.size | round(1) }}</td>
 <td><input type="submit" name="play_{{file.digest.b64}}" value="play" {% if not file.present %}disabled {% endif %}/></td>
 <td>{% for tag in file.tags %}{{tag}} {%endfor %}</td>
 <td><a href="/{{page_names.file}}/{{file.digest.b64}}">{{file.rel_path}}</a></td>
 <td><input type="submit" name="play_{{file.digest.b64}}" value="play" {% if not file.present %}disabled {% endif %}/></td>
 <td>{% for tag in file.tags %}{{tag}} {%endfor %}</td>
 <td><a href="/{{page_names.file}}/{{file.digest.b64}}">{{file.rel_path}}</a></td>
index e82aff18270209f40d74da9c065107dac15c62d1..c60236217251e88345edd9c60518a93f8a3d1bce 100644 (file)
@@ -29,50 +29,53 @@ window.onload = keep_updated;
 
 
 {% block css %}
 
 
 {% block css %}
-#status { text-align: center; font-weight: bold; }
-th { text-align: center; }
-td.history { width: 50%; }
+#status { font-weight: bold; }
+td.screen_half { width: 50%; }
+tr.screen_half_titles>th { text-align: center; }
 td.entry_buttons { width: 5em; }
 td.entry_buttons { width: 5em; }
+td.tag_checkboxes { width: 1em; }
 {% endblock %}
 
 
 {% endblock %}
 
 
-{% macro playlist_entries(files_w_idx, reverse) %}
-<td class="history">
-<table>
-{% for idx, file in files_w_idx %}
-<tr>
-<td class="entry_buttons">
-<input type="submit" name="jump_{{idx}}" value=">" />
-<input type="submit" name="up_{{idx}}" value="{% if reverse %}v{% else %}^{% endif %}" />
-<input type="submit" name="down_{{idx}}" value="{% if reverse %}^{% else %}v{% endif %}" />
-</td>
-<td><a href="/{{page_names.file}}/{{file.digest.b64}}">{{ file.rel_path }}</a></td>
-</tr>
-{% endfor %}
-</table>
-</td>
-{% endmacro %}
-
-
 {% block body %}
 {{ macros.nav_head(page_names, "playlist") }}
 <table>
 <tr><td id="status" colspan=2>
 {% block body %}
 {{ macros.nav_head(page_names, "playlist") }}
 <table>
 <tr><td id="status" colspan=2>
-{% if running %}{% if pause %}PAUSED{% else %}PLAYING{% endif %}{% else %}STOPPED{% endif %}:<br />
-<a href="/{{page_names.file}}/{{current_video.digest.b64}}">{{ current_video.rel_path }}</a><br />
 <form action="/{{page_names.playlist}}" method="POST">
 <input type="submit" name="pause" autofocus value="{% if paused %}resume{% else %}pause{% endif %}">
 <input type="submit" name="prev" value="prev">
 <input type="submit" name="next" value="next">
 <input type="submit" name="stop" value="{% if running %}stop{% else %}start{% endif %}">
 <input type="submit" name="reload" value="reload">
 <form action="/{{page_names.playlist}}" method="POST">
 <input type="submit" name="pause" autofocus value="{% if paused %}resume{% else %}pause{% endif %}">
 <input type="submit" name="prev" value="prev">
 <input type="submit" name="next" value="next">
 <input type="submit" name="stop" value="{% if running %}stop{% else %}start{% endif %}">
 <input type="submit" name="reload" value="reload">
+{% if running %}{% if pause %}PAUSED{% else %}PLAYING{% endif %}{% else %}STOPPED{% endif %}
+</form>
 </td></tr>
 </td></tr>
-<tr><th>past</th><th>future</th></tr>
+<tr class="screen_half_titles"><th>current selection</th><th>playlist</th></tr>
 <tr>
 <tr>
-{{ playlist_entries(prev_files_w_idx, reverse=true) }}
-{{ playlist_entries(next_files_w_idx, reverse=false) }}
+<td class="screen_half">
+{{ macros.file_data_form(current_file, unused_tags, page_names, playlist_view=true) }}
+</td>
+<td class="screen_half">
+<form action="/{{page_names.playlist}}" method="POST">
+<table>
+{% for idx, file in files_w_idx %}
+<tr>
+<td class="entry_buttons">
+{% if file.digest == current_file.digest %}
+PLAYING
+{% else %}
+<input type="submit" name="jump_{{idx}}" value=">" />
+<input type="submit" name="up_{{idx}}" value="{% if reverse %}v{% else %}^{% endif %}" />
+<input type="submit" name="down_{{idx}}" value="{% if reverse %}^{% else %}v{% endif %}" />
+{% endif %}
+</td>
+<td><a href="/{{page_names.file}}/{{file.digest.b64}}">{{ file.rel_path }}</a></td>
 </tr>
 </tr>
+{% endfor %}
+</table>
 </form>
 </form>
+</td>
+</tr>
 </table>
 {% endblock %}
 
 </table>
 {% endblock %}
 
index d61b75ca2c92b582d81fd19bd4d3839ffe953a48..b0aea6445f409c1bd4da8270156ab87586a347d8 100644 (file)
@@ -28,7 +28,7 @@ _TemplateContext: TypeAlias = dict[
         None | bool
         | FilesWithIndex | _PageNames | _FilterStr | Path | PlayerUpdateId
         | QueryText | QuotaCost | UrlStr | 'VideoFile' | YoutubeId
         None | bool
         | FilesWithIndex | _PageNames | _FilterStr | Path | PlayerUpdateId
         | QueryText | QuotaCost | UrlStr | 'VideoFile' | YoutubeId
-        | 'YoutubeVideo' | list[FlagName] | list['Tag'] | list['VideoFile']
+        | 'YoutubeVideo' | list[FlagName] | set['Tag'] | list['VideoFile']
         | list['YoutubeVideo'] | list['YoutubeQuery']
 ]
 
         | list['YoutubeVideo'] | list['YoutubeQuery']
 ]
 
@@ -151,9 +151,7 @@ class _TaskHandler(BaseHTTPRequestHandler):
             file.save(conn)
             conn.commit()
         file.ensure_absence_if_deleted()
             file.save(conn)
             conn.commit()
         file.ensure_absence_if_deleted()
-        self._redirect(Path('/')
-                       .joinpath(PAGE_NAMES['file'])
-                       .joinpath(digest.b64))
+        self._redirect(Path(postvars['redir'][0]))
 
     def _receive_yt_query(self, query_txt: QueryText) -> None:
         with DbConn() as conn:
 
     def _receive_yt_query(self, query_txt: QueryText) -> None:
         with DbConn() as conn:
@@ -273,11 +271,11 @@ class _TaskHandler(BaseHTTPRequestHandler):
     def _send_file_data(self, digest: Hash) -> None:
         with DbConn() as conn:
             file = VideoFile.get_one(conn, digest)
     def _send_file_data(self, digest: Hash) -> None:
         with DbConn() as conn:
             file = VideoFile.get_one(conn, digest)
-            all_tags = VideoFile.get_all_tags(conn)
-        self._send_rendered_template(
-                _NAME_TEMPLATE_FILE_DATA,
-                {'file': file, 'flag_names': list(FILE_FLAGS),
-                 'unused_tags': [t for t in all_tags if t not in file.tags]})
+            unused_tags = file.unused_tags(conn)
+        self._send_rendered_template(_NAME_TEMPLATE_FILE_DATA,
+                                     {'file': file,
+                                      'flag_names': list(FILE_FLAGS),
+                                      'unused_tags': unused_tags})
 
     def _send_files_index(self, params: dict[str, list[str]]) -> None:
         filter_path = _FilterStr(params.get('filter_path', [''])[0])
 
     def _send_files_index(self, params: dict[str, list[str]]) -> None:
         filter_path = _FilterStr(params.get('filter_path', [''])[0])
@@ -312,11 +310,18 @@ class _TaskHandler(BaseHTTPRequestHandler):
     def _send_playlist(self) -> None:
         if self.server.player.empty:
             self.server.player.load_files()
     def _send_playlist(self) -> None:
         if self.server.player.empty:
             self.server.player.load_files()
+        current_file, unused_tags = None, set()
+        if self.server.player.current_file_digest:
+            with DbConn() as conn:
+                current_file = VideoFile.get_one(
+                        conn, self.server.player.current_file_digest)
+                unused_tags = current_file.unused_tags(conn)
         self._send_rendered_template(
                 _NAME_TEMPLATE_PLAYLIST,
                 {'last_update': self.server.player.last_update,
                  'running': self.server.player.is_running,
                  'paused': self.server.player.is_paused,
         self._send_rendered_template(
                 _NAME_TEMPLATE_PLAYLIST,
                 {'last_update': self.server.player.last_update,
                  'running': self.server.player.is_running,
                  'paused': self.server.player.is_paused,
-                 'current_video': self.server.player.current_file,
-                 'prev_files_w_idx': self.server.player.prev_files_w_idx,
-                 'next_files_w_idx': self.server.player.next_files_w_idx})
+                 'current_file': current_file,
+                 'unused_tags': unused_tags,
+                 'files_w_idx': list(enumerate(self.server.player.files))
+                 })
index d0b55e894dc7ecc9b541d0198d872826c7fd538c..438a8cde6b922d54efc4002fc4eb1167baf0696b 100644 (file)
@@ -278,7 +278,6 @@ class VideoFile(DbData):
                     f'{self.yt_id}|{self.last_update}|{self.tags_str}')
 
     def _renew_last_update(self):
                     f'{self.yt_id}|{self.last_update}|{self.tags_str}')
 
     def _renew_last_update(self):
-        print("DEBUG calling_renew_last_update", self.rel_path)
         self.last_update = DatetimeStr(datetime.now().strftime(TIMESTAMP_FMT))
         self._hash_on_last_update = hash(self)
 
         self.last_update = DatetimeStr(datetime.now().strftime(TIMESTAMP_FMT))
         self._hash_on_last_update = hash(self)
 
@@ -297,12 +296,11 @@ class VideoFile(DbData):
             raise NotFoundException(f'no entry for file to Youtube ID {yt_id}')
         return cls._from_table_row(row)
 
             raise NotFoundException(f'no entry for file to Youtube ID {yt_id}')
         return cls._from_table_row(row)
 
-    @classmethod
-    def get_all_tags(cls, conn: BaseDbConn) -> set[Tag]:
-        """Return all tags used among VideoFiles."""
+    def unused_tags(self, conn: BaseDbConn) -> set[Tag]:
+        """Return tags used among other VideoFiles, not in self."""
         tags = set()
         tags = set()
-        for file in cls.get_all(conn):
-            for tag in file.tags:
+        for file in self.get_all(conn):
+            for tag in [t for t in file.tags if t not in self.tags]:
                 tags.add(tag)
         return tags
 
                 tags.add(tag)
         return tags
 
@@ -423,11 +421,11 @@ class Player:
         """Collect files in PATH_DOWNLOADS DB-known and of legal extension."""
         with DbConn() as conn:
             known_files = {f.full_path: f for f in VideoFile.get_all(conn)}
         """Collect files in PATH_DOWNLOADS DB-known and of legal extension."""
         with DbConn() as conn:
             known_files = {f.full_path: f for f in VideoFile.get_all(conn)}
-        self._files = [known_files[p] for p in PATH_DOWNLOADS.iterdir()
-                       if p in known_files
-                       and p.is_file()
-                       and p.suffix[1:] in LEGAL_EXTENSIONS]
-        shuffle(self._files)
+        self.files = [known_files[p] for p in PATH_DOWNLOADS.iterdir()
+                      if p in known_files
+                      and p.is_file()
+                      and p.suffix[1:] in LEGAL_EXTENSIONS]
+        shuffle(self.files)
         self._idx = 0
 
     def _signal_update(self) -> None:
         self._idx = 0
 
     def _signal_update(self) -> None:
@@ -439,7 +437,7 @@ class Player:
                         config=True)
         self._monitor_kill()
         self._mpv.observe_property('pause', lambda a, b: self._signal_update())
                         config=True)
         self._monitor_kill()
         self._mpv.observe_property('pause', lambda a, b: self._signal_update())
-        for path in [f.full_path for f in self._files]:
+        for path in [f.full_path for f in self.files]:
             self._mpv.command('loadfile', path, 'append')
 
         @self._mpv.event_callback('start-file')
             self._mpv.command('loadfile', path, 'append')
 
         @self._mpv.event_callback('start-file')
@@ -471,28 +469,18 @@ class Player:
     @property
     def empty(self) -> bool:
         """Return if playlist empty."""
     @property
     def empty(self) -> bool:
         """Return if playlist empty."""
-        return 0 == len(self._files)
+        return 0 == len(self.files)
 
     @property
 
     @property
-    def current_file(self) -> Optional[VideoFile]:
-        """Return what we assume is the currently playing file."""
-        if not self._files:
-            return None
-        return self._files[self._idx]
+    def current_file_digest(self) -> Optional[Hash]:
+        """Return .digest of what we assume is the currently playing file.
 
 
-    @property
-    def _files_w_idx(self) -> FilesWithIndex:
-        return list(enumerate(self._files))
-
-    @property
-    def prev_files_w_idx(self) -> FilesWithIndex:
-        """List 'past' files of playlist."""
-        return list(reversed(self._files_w_idx[:self._idx]))
-
-    @property
-    def next_files_w_idx(self) -> FilesWithIndex:
-        """List 'coming' files of playlist."""
-        return self._files_w_idx[self._idx + 1:]
+        We don't return the actual file object because we cannot guarantee its
+        data's up-to-date-ness, it being cached from the last .load_files call.
+        """
+        if not self.files:
+            return None
+        return self.files[self._idx].digest
 
     @property
     def is_running(self) -> bool:
 
     @property
     def is_running(self) -> bool:
@@ -528,7 +516,7 @@ class Player:
 
     def next(self) -> None:
         """Move player to next item in playlist."""
 
     def next(self) -> None:
         """Move player to next item in playlist."""
-        if self._idx < len(self._files) - 1:
+        if self._idx < len(self.files) - 1:
             self._idx += 1
         self._play_at_index()
 
             self._idx += 1
         self._play_at_index()
 
@@ -543,16 +531,16 @@ class Player:
                 or (upwards and start_idx == self._idx + 1)
                 or ((not upwards) and start_idx == self._idx - 1)
                 or (upwards and start_idx < 1)
                 or (upwards and start_idx == self._idx + 1)
                 or ((not upwards) and start_idx == self._idx - 1)
                 or (upwards and start_idx < 1)
-                or ((not upwards) and start_idx > len(self._files) - 2)):
+                or ((not upwards) and start_idx > len(self.files) - 2)):
             return
         i0, i1 = start_idx, start_idx + (-1 if upwards else 1)
         if self._mpv:
             # NB: a functional playlist-move would do this in a single step,
             # but for some reason I don't seem to get it to do anything
             return
         i0, i1 = start_idx, start_idx + (-1 if upwards else 1)
         if self._mpv:
             # NB: a functional playlist-move would do this in a single step,
             # but for some reason I don't seem to get it to do anything
-            path = self._files[i1].full_path
+            path = self.files[i1].full_path
             self._mpv.command('playlist-remove', i1)
             self._mpv.command('loadfile', path, 'insert-at', i0)
             self._mpv.command('playlist-remove', i1)
             self._mpv.command('loadfile', path, 'insert-at', i0)
-        self._files[i0], self._files[i1] = self._files[i1], self._files[i0]
+        self.files[i0], self.files[i1] = self.files[i1], self.files[i0]
 
     def reload(self) -> None:
         """Close MPV, re-read (and re-shuffle) filenames, then re-start MPV."""
 
     def reload(self) -> None:
         """Close MPV, re-read (and re-shuffle) filenames, then re-start MPV."""
@@ -563,9 +551,9 @@ class Player:
 
     def inject_and_play(self, file: VideoFile) -> None:
         """Inject file after current title, then jump to it."""
 
     def inject_and_play(self, file: VideoFile) -> None:
         """Inject file after current title, then jump to it."""
-        if self._files:
+        if self.files:
             self._idx += 1
             self._idx += 1
-        self._files.insert(self._idx, file)
+        self.files.insert(self._idx, file)
         if self._mpv:
             self._mpv.command('loadfile', file.full_path,
                               'insert-at', self._idx)
         if self._mpv:
             self._mpv.command('loadfile', file.full_path,
                               'insert-at', self._idx)