Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6c8ffee
feat: add AssetType and AssetCodec choices for #1420
P-r-e-m-i-u-m Mar 15, 2026
a70a85b
feat: add Codec model for supported asset codecs for #1421
P-r-e-m-i-u-m Mar 15, 2026
be46672
Update tubesync/sync/choices.py
tcely Mar 15, 2026
3240997
Update tubesync/sync/choices.py
tcely Mar 15, 2026
d837e02
Update tubesync/sync/choices.py
tcely Mar 15, 2026
941e355
feat: add PNG thumbnail, SUBTITLE type and subtitle codecs to AssetTy…
P-r-e-m-i-u-m Mar 15, 2026
b679f55
Update tubesync/sync/choices.py
tcely Mar 15, 2026
bd8b593
Update tubesync/sync/choices.py
tcely Mar 15, 2026
49ef52b
Update tubesync/sync/choices.py
tcely Mar 15, 2026
64dae1e
Merge pull request #1422 from P-r-e-m-i-u-m/issue/1420/asset-types-ch…
tcely Apr 14, 2026
9fcda96
chore: unrestrict the `Codec` table
tcely Apr 14, 2026
c6cd6f5
fix: pin `huey` to version 2.6
tcely Apr 15, 2026
350409d
Merge remote-tracking branch 'upstream/main' into issue/1419/design-g…
tcely Apr 15, 2026
5370f70
Add the `description` field to `Codec`
tcely Apr 18, 2026
31423d8
Create 0038_codec.py
tcely Apr 18, 2026
52d9f19
chore: add subtitle codecs data to the migration
tcely Apr 18, 2026
a440186
chore: add thumbnail codecs data to the migration
tcely Apr 18, 2026
6c031ab
feat: add Subtitle model (closes #1453)
jishanahmed-shaikh Apr 19, 2026
7958672
Add issue/1419/* branch to pull request triggers
tcely Apr 19, 2026
acfce72
Add noqa comment to Codec import in admin.py
tcely Apr 19, 2026
de9a21c
Add hypothesis to dev-packages in Pipfile
tcely Apr 19, 2026
ddfc345
fix: register CodecAdmin instead of silencing unused import
jishanahmed-shaikh Apr 19, 2026
7294f80
fix: apply tcely review suggestions - extension before language order…
jishanahmed-shaikh Apr 19, 2026
310abbe
Merge pull request #1462 from jishanahmed-shaikh/main
tcely Apr 22, 2026
028d2a5
Merge pull request #1469 from meeb/main
tcely Apr 22, 2026
7a17db1
chore(dependencies): unpin `huey`
tcely Apr 22, 2026
42696c9
Merge branch 'main' into issue/1419/design-generic-models
tcely May 30, 2026
19a0495
chore: refactor asset_type choices to use AssetType enum
tcely Jun 1, 2026
a0f64c1
chore: refactor asset_type assignment to use AssetType enum
tcely Jun 1, 2026
374a45c
chore: update asset_type to use AssetType enum
tcely Jun 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:
branches:
- 'main'
- 'test-*'
- 'issue/1419/*'
types:
- opened
- reopened
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ Pipfile.lock
# Ignore Jetbrains IDE files
.idea/

# Kiro IDE files (specs, configs, steering — not for the repo)
.kiro/

# Task queue DBs
/tubesync/tasks/*.db
/tubesync/tasks/*.db-shm
Expand Down
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ verify_ssl = true

[dev-packages]
autopep8 = "*"
hypothesis = "*"
pycodestyle = "*"

[packages]
Expand Down
24 changes: 23 additions & 1 deletion tubesync/sync/admin.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from django.contrib import admin
from .models import (
Codec,
Source,
Media,
Metadata,
MetadataFormat,
MediaServer
MediaServer,
Subtitle,
)


Expand Down Expand Up @@ -45,9 +47,29 @@ class MetadataFormatAdmin(admin.ModelAdmin):
search_fields = ('uuid', 'metadata__uuid', 'metadata__media__uuid', 'key')


@admin.register(Codec)
class CodecAdmin(admin.ModelAdmin):

ordering = ('asset_type', 'codec')
list_display = ('uuid', 'asset_type', 'codec', 'description')
list_filter = ('asset_type',)
readonly_fields = ('uuid',)
search_fields = ('codec', 'description')


@admin.register(MediaServer)
class MediaServerAdmin(admin.ModelAdmin):

ordering = ('host', 'port')
list_display = ('pk', 'server_type', 'host', 'port', 'use_https', 'verify_https')
search_fields = ('host',)


@admin.register(Subtitle)
class SubtitleAdmin(admin.ModelAdmin):

ordering = ('extension', 'language')
list_display = ('id', 'extension', 'original_language', 'language', 'machine_generated', 'codec')
list_filter = ('machine_generated',)
search_fields = ('extension', 'language', 'original_language')

7 changes: 7 additions & 0 deletions tubesync/sync/choices.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,13 @@ class YouTube_VideoCodec(models.TextChoices):
AVC1 = 'AVC1', _('AVC1 (H.264)')


class AssetType(models.TextChoices):
THUMBNAIL = 'thumbnail', _('Thumbnail')
AUDIO = 'audio', _('Audio')
VIDEO = 'video', _('Video')
SUBTITLE = 'subtitle', _('Subtitle')


SourceResolutionInteger = SourceResolution._integer_mapping()
youtube_long_source_types = YouTube_SourceType._long_type_mapping()
youtube_validation_urls = YouTube_SourceType._validation_urls()
Expand Down
38 changes: 38 additions & 0 deletions tubesync/sync/migrations/0038_codec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 6.0.4 on 2026-04-18 01:42

import uuid
from django.db import migrations, models
from sync.choices import AssetType

class Migration(migrations.Migration):

dependencies = [
('sync', '0037_alter_source_fallback'),
]

operations = [
migrations.CreateModel(
name='Codec',
fields=[
('uuid', models.UUIDField(
default=uuid.uuid4, editable=False, help_text='UUID of the codec', primary_key=True, serialize=False, verbose_name='uuid'),
),
('asset_type', models.CharField(
choices=AssetType.choices,
db_index=True, help_text='The type of asset this codec is used for', max_length=16, verbose_name='asset type'),
),
('codec', models.CharField(
db_index=True, help_text='The codec extension', max_length=16, verbose_name='codec'),
),
('description', models.CharField(
help_text='A text description / label for this codec', max_length=128, verbose_name='description'),
),
],
options={
'verbose_name': 'Codec',
'verbose_name_plural': 'Codecs',
'unique_together': {('asset_type', 'codec')},
},
),
]

123 changes: 123 additions & 0 deletions tubesync/sync/migrations/0039_codec_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# data migration added manually by: tcely

from uuid import UUID
from django.db import migrations
from sync.choices import AssetType

subtitle_codecs = [
{
'uuid': UUID('07f0e7df-75e6-4b07-9430-5332e5207c67'),
'codec': 'vtt', 'description': 'Web Video Text Tracks',
},
{
'uuid': UUID('426e8edc-daf2-48b3-b44d-a18fd7bb68d0'),
'codec': 'ttml', 'description': 'Timed Text Markup Language',
},
{
'uuid': UUID('019b1fdd-a031-4491-99eb-fb3418b92ce2'),
'codec': 'srt', 'description': 'SubRip Text',
},
{
'uuid': UUID('d73a36e5-372d-46d4-9f84-a9f6d19b6646'),
'codec': 'ass', 'description': 'Advanced SubStation Alpha',
},
{
'uuid': UUID('bd0fb776-15e4-43a6-86dc-3c39a89a3cfe'),
'codec': 'ssa', 'description': 'SubStation Alpha',
},
{
'uuid': UUID('3395bc25-2bc9-4e0d-9943-60e3d2572abb'),
'codec': 'scc', 'description': 'Scenarist Closed Caption',
},
{
'uuid': UUID('4027ecfd-6c1c-43d0-b37c-add5f516b629'),
'codec': 'sbv', 'description': 'SubViewer',
},
{
'uuid': UUID('383b89a9-06fc-48aa-bd77-f87cd4cee211'),
'codec': 'json3', 'description': 'YouTube (proprietary) Timed Text Markup Language',
},
{
'uuid': UUID('475c7470-6af0-49f9-ad15-11ffc2eecd5e'),
'codec': 'srv3', 'description': 'YouTube (proprietary) Timed Text Markup Language',
},
{
'uuid': UUID('1ccd5388-f64c-4ccf-9ec8-5474e9843c30'),
'codec': 'srv2', 'description': 'YouTube (proprietary) Timed Text Markup Language',
},
{
'uuid': UUID('f15d5d2a-0a49-4146-8707-91d3bb1930bc'),
'codec': 'srv1', 'description': 'YouTube (proprietary) Timed Text Markup Language',
}
]

thumbnail_codecs = [
{
'uuid': UUID('8f0e3044-2d12-4b1c-9a05-39d29d8a493c'),
'codec': 'jpg', 'description': 'Joint Photographic Experts Group (ISO/IEC 10918)',
},
{
'uuid': UUID('6c428761-aa82-4916-b571-6eb00dcdb5a6'),
'codec': 'png', 'description': 'Portable Network Graphics',
},
{
'uuid': UUID('2f6771e9-9eb2-458e-9be6-f75b98b28758'),
'codec': 'webp', 'description': 'WebP',
},
]

def create_codecs(query_set, arg_dict):
return query_set.bulk_create(
[ query_set.model(**d) for d in arg_dict ],
update_conflicts=True,
update_fields=['description'],
unique_fields=['asset_type', 'codec'],
)

def add_subtitle_codecs(manager, db_alias):
qs = manager.using(db_alias)
for d in subtitle_codecs:
d['asset_type'] = AssetType.SUBTITLE.value
create_codecs(qs, subtitle_codecs)

def add_thumbnail_codecs(manager, db_alias):
qs = manager.using(db_alias)
for d in thumbnail_codecs:
d['asset_type'] = AssetType.THUMBNAIL.value
create_codecs(qs, thumbnail_codecs)

def forwards_func(apps, schema_editor):
# We get the model from the versioned app registry;
# if we directly import it, it'll be the wrong version.
Codec = apps.get_model("sync", "Codec")
db_alias = schema_editor.connection.alias
manager = Codec.objects
add_subtitle_codecs(manager, db_alias)
add_thumbnail_codecs(manager, db_alias)

def reverse_func(apps, schema_editor):
# Delete the rows added by `forwards_func`.
Codec = apps.get_model("sync", "Codec")
db_alias = schema_editor.connection.alias
qs = Codec.objects.using(db_alias)
# Delete the subtitle codecs.
qs.filter(
asset_type=AssetType.SUBTITLE.value,
codec__in=[ d['codec'] for d in subtitle_codecs ],
).delete()
# Delete the thumbnail codecs.
qs.filter(
asset_type=AssetType.THUMBNAIL.value,
codec__in=[ d['codec'] for d in thumbnail_codecs ],
).delete()

class Migration(migrations.Migration):

dependencies = [
('sync', '0038_codec'),
]

operations = [
migrations.RunPython(forwards_func, reverse_func),
]

63 changes: 63 additions & 0 deletions tubesync/sync/migrations/0040_subtitle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Generated migration for the Subtitle model (issue #1453)

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('sync', '0039_codec_data'),
]

operations = [
migrations.CreateModel(
name='Subtitle',
fields=[
('id', models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name='ID',
),
),
('extension', models.CharField(
help_text='The file extension of the subtitle (e.g. vtt, srt)',
max_length=8,
verbose_name='extension',
),
),
('language', models.CharField(
help_text='BCP-47 language tag of the subtitle track (e.g. en-US)',
max_length=16,
verbose_name='language',
),
),
('original_language', models.CharField(
blank=False,
default=None,
help_text='BCP-47 language tag of the source language, or NULL if unknown',
max_length=16,
null=True,
verbose_name='original language',
),
),
('machine_generated', models.BooleanField(
default=False,
help_text='Whether the subtitle was automatically generated',
verbose_name='machine generated',
),
),
('codec', models.ForeignKey(
help_text='The codec used for this subtitle track',
null=True,
on_delete=models.deletion.SET_NULL,
related_name='subtitles',
to='sync.codec',
),
),
],
options={
'verbose_name': 'Subtitle',
'verbose_name_plural': 'Subtitles',
'unique_together': {('language', 'extension')},
},
),
]

6 changes: 4 additions & 2 deletions tubesync/sync/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@
# The order starts with independent classes
# then the classes that depend on them follow.

from .codec import Codec
from .media_server import MediaServer

from .source import Source
from .media import Media
from .metadata import Metadata
from .metadata_format import MetadataFormat
from .subtitle import Subtitle

__all__ = [
'get_media_file_path', 'get_media_thumb_path',
'media_file_storage', 'MediaServer', 'Source',
'Media', 'Metadata', 'MetadataFormat',
'media_file_storage', 'Codec', 'MediaServer', 'Source',
'Media', 'Metadata', 'MetadataFormat', 'Subtitle',
]

47 changes: 47 additions & 0 deletions tubesync/sync/models/codec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import uuid
from django.db import models
from django.utils.translation import gettext_lazy as _
from ..choices import AssetType


class Codec(models.Model):
'''
Codec is a supported codec for a given asset type.
'''

uuid = models.UUIDField(
_('uuid'),
primary_key=True,
editable=False,
default=uuid.uuid4,
help_text=_('UUID of the codec'),
)
asset_type = models.CharField(
_('asset type'),
max_length=16,
db_index=True,
choices=AssetType.choices,
help_text=_('The type of asset this codec is used for'),
)
codec = models.CharField(
_('codec'),
max_length=16,
db_index=True,
help_text=_('The codec extension'),
)
description = models.CharField(
_('description'),
max_length=128,
help_text=_('A text description / label for this codec'),
)

def __str__(self):
return f'{self.asset_type} / {self.codec}'

class Meta:
verbose_name = _('Codec')
verbose_name_plural = _('Codecs')
unique_together = (
('asset_type', 'codec'),
)

Loading