1
- import functools
2
1
import json
3
2
import os
4
3
from abc import ABCMeta
5
4
from abc import abstractmethod
6
5
from datetime import timedelta
7
6
from distutils .dir_util import copy_tree
8
- from distutils .dir_util import create_tree
9
7
from distutils .dir_util import remove_tree
10
8
11
9
from PIL import Image
12
10
11
+ from .pathtools import ensure_tree
12
+ from .pathtools import extract_name
13
+ from .pathtools import metadata_path
14
+
13
15
14
16
def register_thumbnail (typename ):
15
- """Register a new thumbnail formatter to the factory."""
17
+ """Register a new type of thumbnail generator into the factory."""
16
18
17
19
def _register_factory (cls ):
18
20
if not issubclass (cls , Thumbnail ):
19
- raise ValueError ("The formatter must implement the Thumbnail interface." )
21
+ raise ValueError ("%s should be a Thumbnail." % cls . __name__ )
20
22
21
23
cls .extension = typename
22
24
ThumbnailFactory .thumbnails [typename ] = cls
@@ -30,7 +32,7 @@ class ThumbnailExistsError(Exception):
30
32
31
33
32
34
class Thumbnail (metaclass = ABCMeta ):
33
- """Any thumbnail describing format should implement the base Formatter ."""
35
+ """Any thumbnail describing format should implement the base Thumbnail ."""
34
36
35
37
extension = None
36
38
@@ -39,73 +41,63 @@ def __init__(self, video, base, skip, output):
39
41
self .base = base
40
42
self .skip = skip
41
43
self .output = output
44
+ self .thumbnail_dir = self .calc_thumbnail_dir ()
45
+ self .metadata_path = self ._get_metadata_path ()
42
46
self ._perform_skip ()
43
47
self .extract_frames ()
44
48
49
+ def _get_metadata_path (self ):
50
+ """Initiates the name of the thumbnail metadata file."""
51
+ return metadata_path (self .filepath , self .output , self .extension )
52
+
45
53
def _perform_skip (self ):
46
- """Raises ThumbnailExistsError to skip."""
47
- if os .path .exists (self .get_metadata_path () ) and self .skip :
54
+ """Checks the file existence and decide whether to skip or not ."""
55
+ if os .path .exists (self .metadata_path ) and self .skip :
48
56
raise ThumbnailExistsError
49
- basedir , file = os .path .split (self .get_metadata_path ())
50
- create_tree (basedir , [file ])
57
+ ensure_tree (self .metadata_path )
51
58
52
59
def __getattr__ (self , item ):
53
- """Delegate all other attributes to the video."""
60
+ """Delegates all other attributes to the video."""
54
61
return getattr (self .video , item )
55
62
56
- def get_metadata_path (self ):
57
- """Return the name of the thumbnail file."""
58
- return self .metadata_path (self .filepath , self .output , self .extension )
59
-
60
- @staticmethod
61
- @functools .cache
62
- def metadata_path (path , out , fmt ):
63
- """Calculate the thumbnail metadata output path."""
64
- out = os .path .abspath (out or os .path .dirname (path ))
65
- filename = os .path .splitext (os .path .basename (path ))[0 ]
66
- return os .path .join (out , "%s.%s" % (filename , fmt ))
67
-
68
63
@abstractmethod
69
- def thumbnail_dir (self ):
70
- """Creates and returns the thumbnail's output directory."""
64
+ def calc_thumbnail_dir (self ):
65
+ """Calculates and returns the thumbnail's output directory."""
71
66
72
67
@abstractmethod
73
68
def prepare_frames (self ):
74
- """Prepare the thumbnails before generating the output."""
69
+ """Prepares the thumbnail frames before generating the output."""
75
70
76
71
@abstractmethod
77
72
def generate (self ):
78
- """Generate the thumbnails for the given video."""
73
+ """Generates the thumbnail metadata for the given video."""
79
74
80
75
81
76
class ThumbnailFactory :
82
- """A factory for creating thumbnail formatter ."""
77
+ """A factory for creating a thumbnail for a particular format ."""
83
78
84
79
thumbnails = {}
85
80
86
81
@classmethod
87
82
def create_thumbnail (cls , typename , * args , ** kwargs ) -> Thumbnail :
88
- """Create a new thumbnail formatter by the given typename."""
83
+ """Create a Thumbnail instance by the given typename."""
89
84
try :
90
85
return cls .thumbnails [typename ](* args , ** kwargs )
91
86
except KeyError :
92
- raise ValueError ("The formatter type '%s' is not registered." % typename )
87
+ raise ValueError ("The thumbnail type '%s' is not registered." % typename )
93
88
94
89
95
90
@register_thumbnail ("vtt" )
96
91
class ThumbnailVTT (Thumbnail ):
97
92
"""Implements the methods for generating thumbnails in the WebVTT format."""
98
93
99
- def thumbnail_dir (self ):
100
- basedir = self .output or os .path .dirname (self .filepath )
101
- create_tree (os .path .abspath (basedir ), [self .filepath ])
102
- return os .path .abspath (basedir )
94
+ def calc_thumbnail_dir (self ):
95
+ return ensure_tree (self .output or os .path .dirname (self .filepath ), True )
103
96
104
97
def prepare_frames (self ):
105
98
thumbnails = self .thumbnails (True )
106
99
master = Image .new (mode = "RGBA" , size = next (thumbnails ))
107
- master_name = os .path .splitext (os .path .basename (self .filepath ))[0 ]
108
- master_path = os .path .join (self .thumbnail_dir (), master_name + ".png" )
100
+ master_path = os .path .join (self .thumbnail_dir , extract_name (self .filepath ) + ".png" )
109
101
110
102
for frame , * _ , x , y in self .thumbnails ():
111
103
with Image .open (frame ) as image :
@@ -121,8 +113,8 @@ def format_time(secs):
121
113
return ("0%s.000" % delta )[:12 ]
122
114
123
115
metadata = ["WEBVTT\n \n " ]
124
- prefix = self .base or os .path .relpath (self .thumbnail_dir () )
125
- route = os .path .join (prefix , os . path . basename (self .filepath ))
116
+ prefix = self .base or os .path .relpath (self .thumbnail_dir )
117
+ route = os .path .join (prefix , extract_name (self .filepath ) + ".png" )
126
118
127
119
for _ , start , end , x , y in self .thumbnails ():
128
120
thumbnail_data = "%s --> %s\n %s#xywh=%d,%d,%d,%d\n \n " % (
@@ -131,40 +123,38 @@ def format_time(secs):
131
123
)
132
124
metadata .append (thumbnail_data )
133
125
134
- with open (self .get_metadata_path () , "w" ) as fp :
126
+ with open (self .metadata_path , "w" ) as fp :
135
127
fp .writelines (metadata )
136
128
137
129
138
130
@register_thumbnail ("json" )
139
131
class ThumbnailJSON (Thumbnail ):
140
132
"""Implements the methods for generating thumbnails in the JSON format."""
141
133
142
- def thumbnail_dir (self ):
134
+ def calc_thumbnail_dir (self ):
143
135
basedir = os .path .abspath (self .output or os .path .dirname (self .filepath ))
144
- subdir = os .path .splitext (os .path .basename (self .filepath ))[0 ]
145
- basedir = os .path .join (basedir , subdir )
146
- create_tree (basedir , [self .filepath ])
147
- return basedir
136
+ return ensure_tree (os .path .join (basedir , extract_name (self .filepath )), True )
148
137
149
138
def prepare_frames (self ):
150
- thumbnail_dir = self .thumbnail_dir ()
151
- if os .path .exists (thumbnail_dir ):
152
- remove_tree (thumbnail_dir )
153
- copy_tree (self .tempdir .name , thumbnail_dir )
139
+ if os .path .exists (self .thumbnail_dir ):
140
+ remove_tree (self .thumbnail_dir )
141
+ copy_tree (self .tempdir .name , self .thumbnail_dir )
154
142
self .tempdir .cleanup ()
155
143
156
144
def generate (self ):
157
145
metadata = {}
158
146
159
147
for frame , start , * _ in self .thumbnails ():
160
- frame = os .path .join (self .thumbnail_dir () , os .path .basename (frame ))
148
+ frame = os .path .join (self .thumbnail_dir , os .path .basename (frame ))
161
149
with Image .open (frame ) as image :
162
150
image .resize ((self .width , self .height ), Image .ANTIALIAS ).save (frame )
151
+ prefix = self .base or os .path .relpath (self .thumbnail_dir )
152
+ route = os .path .join (prefix , os .path .basename (frame ))
163
153
thumbnail_data = {
164
- "src" : self . base + frame ,
154
+ "src" : route ,
165
155
"width" : "%spx" % self .width ,
166
156
}
167
157
metadata [int (start )] = thumbnail_data
168
158
169
- with open (self .get_metadata_path () , "w" ) as fp :
159
+ with open (self .metadata_path , "w" ) as fp :
170
160
json .dump (metadata , fp , indent = 2 )
0 commit comments