~ubuntu-branches/ubuntu/trusty/blender/trusty

« back to all changes in this revision

Viewing changes to release/scripts/addons/io_sequencer_edl/parse_edl.py

  • Committer: Package Import Robot
  • Author(s): Jeremy Bicha
  • Date: 2013-03-06 12:08:47 UTC
  • mfrom: (1.5.1) (14.1.8 experimental)
  • Revision ID: package-import@ubuntu.com-20130306120847-frjfaryb2zrotwcg
Tags: 2.66a-1ubuntu1
* Resynchronize with Debian (LP: #1076930, #1089256, #1052743, #999024,
  #1122888, #1147084)
* debian/control:
  - Lower build-depends on libavcodec-dev since we're not
    doing the libav9 transition in Ubuntu yet

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# ##### BEGIN GPL LICENSE BLOCK #####
 
2
#
 
3
#  This program is free software; you can redistribute it and/or
 
4
#  modify it under the terms of the GNU General Public License
 
5
#  as published by the Free Software Foundation; either version 2
 
6
#  of the License, or (at your option) any later version.
 
7
#
 
8
#  This program is distributed in the hope that it will be useful,
 
9
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
#  GNU General Public License for more details.
 
12
#
 
13
#  You should have received a copy of the GNU General Public License
 
14
#  along with this program; if not, write to the Free Software Foundation,
 
15
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 
16
#
 
17
# ##### END GPL LICENSE BLOCK #####
 
18
 
 
19
# <pep8 compliant>
 
20
 
 
21
"""
 
22
This is a pure python module (no blender deps),
 
23
that parses EDL files and could be used outside of blender.
 
24
"""
 
25
 
 
26
class TimeCode:
 
27
    """
 
28
    Simple timecode class
 
29
    also supports conversion from other time strings used by EDL
 
30
    """
 
31
    __slots__ = (
 
32
        "fps",
 
33
        "hours",
 
34
        "minutes",
 
35
        "seconds",
 
36
        "frame",
 
37
    )
 
38
 
 
39
    def __init__(self, data, fps):
 
40
        self.fps = fps
 
41
        if type(data) == str:
 
42
            self.from_string(data)
 
43
            frame = self.as_frame()
 
44
            self.from_frame(frame)
 
45
        else:
 
46
            self.from_frame(data)
 
47
 
 
48
    def from_string(self, text):
 
49
        # hh:mm:ss:ff
 
50
        # No dropframe support yet
 
51
 
 
52
        if text.lower().endswith("mps"):  # 5.2mps
 
53
            return self.from_frame(int(float(text[:-3]) * self.fps))
 
54
        elif text.lower().endswith("s"):  # 5.2s
 
55
            return self.from_frame(int(float(text[:-1]) * self.fps))
 
56
        elif text.isdigit():  # 1234
 
57
            return self.from_frame(int(text))
 
58
        elif ":" in text:  # hh:mm:ss:ff
 
59
            text = text.replace(";", ":").replace(",", ":").replace(".", ":")
 
60
            text = text.split(":")
 
61
 
 
62
            self.hours = int(text[0])
 
63
            self.minutes = int(text[1])
 
64
            self.seconds = int(text[2])
 
65
            self.frame = int(text[3])
 
66
            return self
 
67
        else:
 
68
            print("ERROR: could not convert this into timecode %r" % text)
 
69
            return self
 
70
 
 
71
    def from_frame(self, frame):
 
72
 
 
73
        if frame < 0:
 
74
            frame = -frame
 
75
            neg = True
 
76
        else:
 
77
            neg = False
 
78
 
 
79
        fpm = 60 * self.fps
 
80
        fph = 60 * fpm
 
81
 
 
82
        if frame < fph:
 
83
            self.hours = 0
 
84
        else:
 
85
            self.hours = int(frame / fph)
 
86
            frame = frame % fph
 
87
 
 
88
        if frame < fpm:
 
89
            self.minutes = 0
 
90
        else:
 
91
            self.minutes = int(frame / fpm)
 
92
            frame = frame % fpm
 
93
 
 
94
        if frame < self.fps:
 
95
            self.seconds = 0
 
96
        else:
 
97
            self.seconds = int(frame / self.fps)
 
98
            frame = frame % self.fps
 
99
 
 
100
        self.frame = frame
 
101
 
 
102
        if neg:
 
103
            self.frame = -self.frame
 
104
            self.seconds = -self.seconds
 
105
            self.minutes = -self.minutes
 
106
            self.hours = -self.hours
 
107
 
 
108
        return self
 
109
 
 
110
    def as_frame(self):
 
111
        abs_frame = self.frame
 
112
        abs_frame += self.seconds * self.fps
 
113
        abs_frame += self.minutes * 60 * self.fps
 
114
        abs_frame += self.hours * 60 * 60 * self.fps
 
115
 
 
116
        return abs_frame
 
117
 
 
118
    def as_string(self):
 
119
        self.from_frame(int(self))
 
120
        return "%.2d:%.2d:%.2d:%.2d" % (self.hours, self.minutes, self.seconds, self.frame)
 
121
 
 
122
    def __repr__(self):
 
123
        return self.as_string()
 
124
 
 
125
    # Numeric stuff, may as well have this
 
126
    def __neg__(self):
 
127
        return TimeCode(-int(self), self.fps)
 
128
 
 
129
    def __int__(self):
 
130
        return self.as_frame()
 
131
 
 
132
    def __sub__(self, other):
 
133
        return TimeCode(int(self) - int(other), self.fps)
 
134
 
 
135
    def __add__(self, other):
 
136
        return TimeCode(int(self) + int(other), self.fps)
 
137
 
 
138
    def __mul__(self, other):
 
139
        return TimeCode(int(self) * int(other), self.fps)
 
140
 
 
141
    def __div__(self, other):
 
142
        return TimeCode(int(self) // int(other), self.fps)
 
143
 
 
144
    def __abs__(self):
 
145
        return TimeCode(abs(int(self)), self.fps)
 
146
 
 
147
    def __iadd__(self, other):
 
148
        return self.from_frame(int(self) + int(other))
 
149
 
 
150
    def __imul__(self, other):
 
151
        return self.from_frame(int(self) * int(other))
 
152
 
 
153
    def __idiv__(self, other):
 
154
        return self.from_frame(int(self) // int(other))
 
155
# end timecode
 
156
 
 
157
 
 
158
"""Comments
 
159
Comments can appear at the beginning of the EDL file (header) or between the edit lines in the EDL. The first block of comments in the file is defined to be the header comments and they are associated with the EDL as a whole. Subsequent comments in the EDL file are associated with the first edit line that appears after them.
 
160
Edit Entries
 
161
<filename|tag>  <EditMode>  <TransitionType>[num]  [duration]  [srcIn]  [srcOut]  [recIn]  [recOut]
 
162
 
 
163
    * <filename|tag>: Filename or tag value. Filename can be for an MPEG file, Image file, or Image file template. Image file templates use the same pattern matching as for command line glob, and can be used to specify images to encode into MPEG. i.e. /usr/data/images/image*.jpg
 
164
    * <EditMode>: 'V' | 'A' | 'VA' | 'B' | 'v' | 'a' | 'va' | 'b' which equals Video, Audio, Video_Audio edits (note B or b can be used in place of VA or va).
 
165
    * <TransitonType>: 'C' | 'D' | 'E' | 'FI' | 'FO' | 'W' | 'c' | 'd' | 'e' | 'fi' | 'fo' | 'w'. which equals Cut, Dissolve, Effect, FadeIn, FadeOut, Wipe.
 
166
    * [num]: if TransitionType = Wipe, then a wipe number must be given. At the moment only wipe 'W0' and 'W1' are supported.
 
167
    * [duration]: if the TransitionType is not equal to Cut, then an effect duration must be given. Duration is in frames.
 
168
    * [srcIn]: Src in. If no srcIn is given, then it defaults to the first frame of the video or the first frame in the image pattern. If srcIn isn't specified, then srcOut, recIn, recOut can't be specified.
 
169
    * [srcOut]: Src out. If no srcOut is given, then it defaults to the last frame of the video - or last image in the image pattern. if srcOut isn't given, then recIn and recOut can't be specified.
 
170
    * [recIn]: Rec in. If no recIn is given, then it is calculated based on its position in the EDL and the length of its input.
 
171
      [recOut]: Rec out. If no recOut is given, then it is calculated based on its position in the EDL and the length of its input. first frame of the video.
 
172
 
 
173
For srcIn, srcOut, recIn, recOut, the values can be specified as either timecode, frame number, seconds, or mps seconds. i.e.
 
174
[tcode | fnum | sec | mps], where:
 
175
 
 
176
    * tcode : SMPTE timecode in hh:mm:ss:ff
 
177
    * fnum : frame number (the first decodable frame in the video is taken to be frame 0).
 
178
    * sec : seconds with 's' suffix (e.g. 5.2s)
 
179
    * mps : seconds with 'mps' suffix (e.g. 5.2mps). This corresponds to the 'seconds' value displayed by Windows MediaPlayer.
 
180
 
 
181
More notes,
 
182
Key
 
183
 
 
184
"""
 
185
 
 
186
enum = 0
 
187
TRANSITION_UNKNOWN = enum
 
188
TRANSITION_CUT = enum
 
189
enum += 1
 
190
TRANSITION_DISSOLVE = enum
 
191
enum += 1
 
192
TRANSITION_EFFECT = enum
 
193
enum += 1
 
194
TRANSITION_FADEIN = enum
 
195
enum += 1
 
196
TRANSITION_FADEOUT = enum
 
197
enum += 1
 
198
TRANSITION_WIPE = enum
 
199
enum += 1
 
200
TRANSITION_KEY = enum
 
201
enum += 1
 
202
 
 
203
TRANSITION_DICT = {
 
204
    "c": TRANSITION_CUT,
 
205
    "d": TRANSITION_DISSOLVE,
 
206
    "e": TRANSITION_EFFECT,
 
207
    "fi": TRANSITION_FADEIN,
 
208
    "fo": TRANSITION_FADEOUT,
 
209
    "w": TRANSITION_WIPE,
 
210
    "k": TRANSITION_KEY,
 
211
    }
 
212
 
 
213
enum = 0
 
214
EDIT_UNKNOWN = 1 << enum
 
215
enum += 1
 
216
EDIT_VIDEO = 1 << enum
 
217
enum += 1
 
218
EDIT_AUDIO = 1 << enum
 
219
enum += 1
 
220
EDIT_AUDIO_STEREO = 1 << enum
 
221
enum += 1
 
222
EDIT_VIDEO_AUDIO = 1 << enum
 
223
enum += 1
 
224
 
 
225
EDIT_DICT = {
 
226
    "none": 0,  # TODO, investigate this more.
 
227
    "v": EDIT_VIDEO,
 
228
    "a": EDIT_AUDIO,
 
229
    "aa": EDIT_AUDIO_STEREO,
 
230
    "va": EDIT_VIDEO_AUDIO,
 
231
    "b": EDIT_VIDEO_AUDIO,
 
232
    }
 
233
 
 
234
 
 
235
enum = 0
 
236
WIPE_UNKNOWN = enum
 
237
WIPE_0 = enum
 
238
enum += 1
 
239
WIPE_1 = enum
 
240
enum += 1
 
241
 
 
242
enum = 0
 
243
KEY_UNKNOWN = enum
 
244
KEY_BG = enum  # K B
 
245
enum += 1
 
246
KEY_IN = enum  # This is assumed if no second type is set
 
247
enum += 1
 
248
KEY_OUT = enum  # K O
 
249
enum += 1
 
250
 
 
251
BLACK_ID = {
 
252
    "bw",
 
253
    "bl",
 
254
    "blk",
 
255
    "black",
 
256
    }
 
257
 
 
258
 
 
259
"""
 
260
Most sytems:
 
261
Non-dropframe: 1:00:00:00 - colon in last position
 
262
Dropframe: 1:00:00;00 - semicolon in last position
 
263
PAL/SECAM: 1:00:00:00 - colon in last position
 
264
 
 
265
SONY:
 
266
Non-dropframe: 1:00:00.00 - period in last position
 
267
Dropframe: 1:00:00,00 - comma in last position
 
268
PAL/SECAM: 1:00:00.00 - period in last position
 
269
"""
 
270
 
 
271
"""
 
272
t = abs(timecode('-124:-12:-43:-22', 25))
 
273
t /= 2
 
274
print t
 
275
"""
 
276
 
 
277
class EditDecision:
 
278
    __slots__ = (
 
279
        "number",
 
280
        "reel",
 
281
        "transition_duration",
 
282
        "edit_type",
 
283
        "transition_type",
 
284
        "wipe_type",
 
285
        "key_type",
 
286
        "key_fade",
 
287
        "srcIn",
 
288
        "srcOut",
 
289
        "recIn",
 
290
        "recOut",
 
291
        "m2",
 
292
        "filename",
 
293
        "custom_data",
 
294
    )
 
295
 
 
296
    @staticmethod
 
297
    def edit_flags_to_text(flag):
 
298
        return "/".join([item for item, val in EDIT_DICT.items() if val & flag])
 
299
 
 
300
    @staticmethod
 
301
    def strip_digits(text):
 
302
        return "".join(filter(lambda x: not x.isdigit(), text))
 
303
 
 
304
    def __init__(self, text=None, fps=25):
 
305
        # print text
 
306
        self.number = -1
 
307
        self.reel = ""  # Uniqie name for this 'file' but not filename, when BL signifies black
 
308
        self.transition_duration = 0
 
309
        self.edit_type = EDIT_UNKNOWN
 
310
        self.transition_type = TRANSITION_UNKNOWN
 
311
        self.wipe_type = WIPE_UNKNOWN
 
312
        self.key_type = KEY_UNKNOWN
 
313
        self.key_fade = -1  # true/false
 
314
        self.srcIn = None   # Where on the original field recording the event begins
 
315
        self.srcOut = None  # Where on the original field recording the event ends
 
316
        self.recIn = None   # Beginning of the original event in the edited program
 
317
        self.recOut = None  # End of the original event in the edited program
 
318
        self.m2 = None      # fps set by the m2 command
 
319
        self.filename = ""
 
320
 
 
321
        self.custom_data = []  # use for storing any data you want (blender strip for eg)
 
322
 
 
323
        if text is not None:
 
324
            self.read(text, fps)
 
325
 
 
326
    def __repr__(self):
 
327
        txt = "num: %d, " % self.number
 
328
        txt += "reel: %s, " % self.reel
 
329
        txt += "edit_type: "
 
330
        txt += EditDecision.edit_flags_to_text(self.edit_type) + ", "
 
331
 
 
332
        txt += "trans_type: "
 
333
        for item, val in TRANSITION_DICT.items():
 
334
            if val == self.transition_type:
 
335
                txt += item + ", "
 
336
                break
 
337
 
 
338
        txt += "m2: "
 
339
        if self.m2:
 
340
            txt += "%g" % float(self.m2.fps)
 
341
            txt += "\n\t"
 
342
            txt += self.m2.data
 
343
        else:
 
344
            txt += "nil"
 
345
 
 
346
        txt += ", "
 
347
        txt += "recIn: " + str(self.recIn) + ", "
 
348
        txt += "recOut: " + str(self.recOut) + ", "
 
349
        txt += "srcIn: " + str(self.srcIn) + ", "
 
350
        txt += "srcOut: " + str(self.srcOut) + ", "
 
351
 
 
352
        return txt
 
353
 
 
354
    def read(self, line, fps):
 
355
        line = line.split()
 
356
        index = 0
 
357
        self.number = int(line[index])
 
358
        index += 1
 
359
        self.reel = line[index].lower()
 
360
        index += 1
 
361
 
 
362
        # AA/V can be an edit type
 
363
        self.edit_type = 0
 
364
        for edit_type in line[index].lower().split("/"):
 
365
            # stripping digits is done because we don't do 'a1, a2...'
 
366
            self.edit_type |= EDIT_DICT[EditDecision.strip_digits(edit_type)]
 
367
        index += 1
 
368
 
 
369
        tx_name = "".join([c for c in line[index].lower() if not c.isdigit()])
 
370
        self.transition_type = TRANSITION_DICT[tx_name]  # advance the index later
 
371
 
 
372
        if self.transition_type == TRANSITION_WIPE:
 
373
            tx_num = "".join([c for c in line[index].lower() if c.isdigit()])
 
374
            if tx_num:
 
375
                tx_num = int(tx_num)
 
376
            else:
 
377
                tx_num = 0
 
378
 
 
379
            self.wipe_type = tx_num
 
380
 
 
381
        elif self.transition_type == TRANSITION_KEY:  # UNTESTED
 
382
 
 
383
            val = line[index + 1].lower()
 
384
 
 
385
            if val == "b":
 
386
                self.key_type = KEY_BG
 
387
                index += 1
 
388
            elif val == "o":
 
389
                self.key_type = KEY_OUT
 
390
                index += 1
 
391
            else:
 
392
                self.key_type = KEY_IN  # if no args given
 
393
 
 
394
            # there may be an (F) after, eg 'K B (F)'
 
395
            # in the docs this should only be after K B but who knows, it may be after K O also?
 
396
            val = line[index + 1].lower()
 
397
            if val == "(f)":
 
398
                index += 1
 
399
                self.key_fade = True
 
400
            else:
 
401
                self.key_fade = False
 
402
 
 
403
        index += 1
 
404
 
 
405
        if self.transition_type in {TRANSITION_DISSOLVE, TRANSITION_EFFECT, TRANSITION_FADEIN, TRANSITION_FADEOUT, TRANSITION_WIPE}:
 
406
            self.transition_duration = TimeCode(line[index], fps)
 
407
            index += 1
 
408
 
 
409
        if index < len(line):
 
410
            self.srcIn = TimeCode(line[index], fps)
 
411
            index += 1
 
412
        if index < len(line):
 
413
            self.srcOut = TimeCode(line[index], fps)
 
414
            index += 1
 
415
 
 
416
        if index < len(line):
 
417
            self.recIn = TimeCode(line[index], fps)
 
418
            index += 1
 
419
        if index < len(line):
 
420
            self.recOut = TimeCode(line[index], fps)
 
421
            index += 1
 
422
 
 
423
    def renumber(self):
 
424
        self.edits.sort(key=lambda e: int(e.recIn))
 
425
        for i, edit in enumerate(self.edits):
 
426
            edit.number = i
 
427
 
 
428
    def clean(self):
 
429
        """
 
430
        Clean up double ups
 
431
        """
 
432
        self.renumber()
 
433
 
 
434
        # TODO
 
435
    def as_name(self):
 
436
        cut_type = "nil"
 
437
        for k, v in TRANSITION_DICT.items():
 
438
            if v == self.transition_type:
 
439
                cut_type = k
 
440
                break
 
441
 
 
442
        return "%d_%s_%s" % (self.number, self.reel, cut_type)
 
443
 
 
444
 
 
445
class M2:
 
446
    __slots__ = (
 
447
        "reel",
 
448
        "fps",
 
449
        "time",
 
450
        "data",
 
451
        "index",
 
452
        "tot",
 
453
    )
 
454
 
 
455
    def __init__(self):
 
456
        self.reel = None
 
457
        self.fps = None
 
458
        self.time = None
 
459
        self.data = None
 
460
 
 
461
        self.index = -1
 
462
        self.tot = -1
 
463
 
 
464
    def read(self, line, fps):
 
465
 
 
466
        # M2   TAPEC          050.5                00:08:11:08
 
467
        words = line.split()
 
468
 
 
469
        self.reel = words[1].lower()
 
470
        self.fps = float(words[2])
 
471
        self.time = TimeCode(words[3], fps)
 
472
 
 
473
        self.data = line
 
474
 
 
475
 
 
476
class EditList:
 
477
    __slots__ = (
 
478
        "edits",
 
479
        "title",
 
480
    )
 
481
 
 
482
    def __init__(self):
 
483
        self.edits = []
 
484
        self.title = ""
 
485
 
 
486
    def parse(self, filename, fps):
 
487
        try:
 
488
            file = open(filename, "r", encoding="utf-8")
 
489
        except:
 
490
            return False
 
491
 
 
492
        self.edits = []
 
493
        edits_m2 = []  # edits with m2's
 
494
 
 
495
        has_m2 = False
 
496
 
 
497
        for line in file:
 
498
            line = " ".join(line.split())
 
499
 
 
500
            if not line or line.startswith(("*", "#")):
 
501
                continue
 
502
            elif line.startswith("TITLE:"):
 
503
                self.title = " ".join(line.split()[1:])
 
504
            elif line.split()[0].lower() == "m2":
 
505
                has_m2 = True
 
506
                m2 = M2()
 
507
                m2.read(line, fps)
 
508
                edits_m2.append(m2)
 
509
            elif not line.split()[0].isdigit():
 
510
                print("Ignoring:", line)
 
511
            else:
 
512
                self.edits.append(EditDecision(line, fps))
 
513
                edits_m2.append(self.edits[-1])
 
514
 
 
515
        if has_m2:
 
516
            # Group indexes
 
517
            i = 0
 
518
            for item in edits_m2:
 
519
                if isinstance(item, M2):
 
520
                    item.index = i
 
521
                    i += 1
 
522
                else:
 
523
                    # not an m2
 
524
                    i = 0
 
525
 
 
526
            # Set total group indexes
 
527
            for item in reversed(edits_m2):
 
528
                if isinstance(item, M2):
 
529
                    if tot_m2 == -1:
 
530
                        tot_m2 = item.index + 1
 
531
 
 
532
                    item.tot = tot_m2
 
533
                else:
 
534
                    # not an m2
 
535
                    tot_m2 = -1
 
536
 
 
537
            for i, item in enumerate(edits_m2):
 
538
                if isinstance(item, M2):
 
539
                    # make a list of all items that match the m2's reel name
 
540
                    edits_m2_tmp = [item_tmp for item_tmp in edits_m2 if (isinstance(item, M2) or item_tmp.reel == item.reel)]
 
541
 
 
542
                    # get the new index
 
543
                    i_tmp = edits_m2_tmp.index(item)
 
544
 
 
545
                    # Seek back to get the edit.
 
546
                    edit = edits_m2[i_tmp - item.tot]
 
547
 
 
548
                    # Note, docs say time should also match with edit start time
 
549
                    # but from final cut pro, this seems not to be the case
 
550
                    if not isinstance(edit, EditDecision):
 
551
                        print("ERROR!", "M2 incorrect")
 
552
                    else:
 
553
                        edit.m2 = item
 
554
 
 
555
        file.close()
 
556
        return True
 
557
 
 
558
    def overlap_test(self, edit_test):
 
559
        recIn = int(edit_test.recIn)
 
560
        recOut = int(edit_test.recOut)
 
561
 
 
562
        for edit in self.edits:
 
563
            if edit is edit_test:
 
564
                break
 
565
 
 
566
            recIn_other = int(edit.recIn)
 
567
            recOut_other = int(edit.recOut)
 
568
 
 
569
            if recIn_other < recIn < recOut_other:
 
570
                return True
 
571
            if recIn_other < recOut < recOut_other:
 
572
                return True
 
573
 
 
574
            if recIn < recIn_other < recOut:
 
575
                return True
 
576
            if recIn < recOut_other < recOut:
 
577
                return True
 
578
 
 
579
        return False
 
580
 
 
581
    def reels_as_dict(self):
 
582
        reels = {}
 
583
        for edit in self.edits:
 
584
            reels.setdefault(edit.reel, []).append(edit)
 
585
 
 
586
        return reels