@@ -73,6 +73,11 @@ enum class ErrorMode {
7373 * @sample myaa.subkt.tasks.samples.mergeSample
7474 */
7575open class Merge : ASSTask () {
76+ data class LineSpecification (
77+ @get:Input val field : EventLineAccessor <String >,
78+ @get:Input val value : String
79+ )
80+
7681 /* *
7782 * Defines how the files associated with this specification should be merged.
7883 */
@@ -89,29 +94,57 @@ open class Merge : ASSTask() {
8994 @get:Input
9095 val shiftBy = defaultProperty(Duration .ZERO )
9196
97+ private val _syncSourceLine =
98+ defaultProperty(LineSpecification (EventLineAccessor .EFFECT , " sync" ))
99+
100+ @get:Nested
101+ @get:Optional
102+ val syncSourceLine: Provider <LineSpecification > = _syncSourceLine
103+
92104 /* *
93- * Shifts the lines of the source file so that the start time of the sync line,
94- * as specified by [syncLine] and [syncField], lines up with the specified [Duration].
105+ * Specifies a line in the included file (e.g. a song file) to serve as
106+ * a reference for shifting all lines in this file. All lines
107+ * will be shifted so that the start time of the specified source line
108+ * matches the time specified by [syncTargetTime] or the start time
109+ * of the line specified by [syncTargetLine].
110+ *
111+ * @param fieldValue The value which identifies the source sync line
112+ * @param fieldName The field in which to look for [fieldValue]
95113 */
96- @get:Input
114+ fun syncSourceLine (
115+ fieldValue : String ,
116+ fieldName : EventLineAccessor <String > = EventLineAccessor .EFFECT
117+ ) {
118+ _syncSourceLine .set(LineSpecification (fieldName, fieldValue))
119+ }
120+
121+ private val _syncTargetLine = project.objects.property<LineSpecification >()
122+
123+ @get:Nested
97124 @get:Optional
98- val syncTo = project.objects.property< Duration >()
125+ val syncTargetLine : Provider < LineSpecification > = _syncTargetLine
99126
100127 /* *
101- * An [EventLineAccessor] specifying what field to match the value specified by
102- * [syncLine] to in order to identify the sync line.
103- * Defaults to the effect field.
128+ * Specifies a target line which the line specified by [syncSourceLine]
129+ * should be shifted to. The target line may be present anywhere in the merged file.
130+ *
131+ * @param fieldValue The value which identifies the target sync line
132+ * @param fieldName The field in which to look for [fieldValue]
104133 */
105- @get:Input
106- val syncField = defaultProperty<EventLineAccessor <String >>(EventLineAccessor .EFFECT )
134+ fun syncTargetLine (
135+ fieldValue : String ,
136+ fieldName : EventLineAccessor <String > = EventLineAccessor .EFFECT
137+ ) {
138+ _syncTargetLine .set(LineSpecification (fieldName, fieldValue))
139+ }
107140
108141 /* *
109- * The value that identifies the sync line used by [syncTo]. The sync line
110- * must have the specified value in the field specified by [syncField].
111- * Defaults to `sync`.
142+ * Specifies a target time which the line specified by [syncSourceLine]
143+ * should be shifted to.
112144 */
113145 @get:Input
114- val syncLine = defaultProperty(" sync" )
146+ @get:Optional
147+ val syncTargetTime = project.objects.property<Duration >()
115148 }
116149
117150 /* *
@@ -206,7 +239,7 @@ open class Merge : ASSTask() {
206239 val spec = MergeSpecification ().apply {
207240 incrementLayer(line.layer)
208241 if (line.effect == " import-shifted" ) {
209- syncTo (line.start)
242+ syncTargetTime (line.start)
210243 }
211244 }
212245 val merged = template.resolveSibling(line.text)
@@ -238,31 +271,65 @@ open class Merge : ASSTask() {
238271 }
239272
240273 override fun buildAss (): ASSFile {
274+ val targetLineSpecs = mutableListOf<LineSpecification >()
275+
276+ // read ass files, preliminary processing, collect target sync line specifications
241277 val files = _sources .get().flatMap { (files, spec) ->
242- project.files(files).map { file -> ASSFile (file).also { ass ->
278+ project.files(files).map { file ->
279+ val ass = ASSFile (file)
280+
243281 ass.events.lines.forEach { line ->
244282 line.layer + = spec.incrementLayer.get()
245283 line.start + = spec.shiftBy.get()
246284 line.end + = spec.shiftBy.get()
247285 }
248286
249- spec.syncTo.orNull?.let { syncTo ->
250- val referenceLine = ass.events.lines.find {
251- getMaybeComment(it, spec.syncField.get()) == spec.syncLine.get()
252- } ? : error(" Could not find sync line in ${file.name} " )
287+ spec.syncTargetLine.orNull?.let {
288+ targetLineSpecs.add(it)
289+ }
253290
254- val delta = syncTo - referenceLine.start
291+ ass to spec
292+ }
293+ }
255294
256- ass.events.lines.forEach {
257- it.start + = delta
258- it.end + = delta
295+ // find lines corresponding to target sync line specifications
296+ val targetLines = files.flatMap { (ass, _) -> ass.events.lines }.mapNotNull { line ->
297+ targetLineSpecs.find { getMaybeComment(line, it.field) == it.value }?.let { it to line }
298+ }.groupBy({ it.first }, { it.second }).mapValues { (spec, lines) ->
299+ when {
300+ lines.size > 1 -> error(" duplicate target sync lines with value ${spec.value} " +
301+ " in field ${spec.field} " )
302+ else -> lines[0 ]
303+ }
304+ }
305+
306+ // shift lines based on start time of target lines or explicit target times
307+ files.forEach { (ass, spec) ->
308+ val targetTime = spec.syncTargetTime.orNull
309+ ? : spec.syncTargetLine.orNull?.let { targetSpec ->
310+ targetLines[targetSpec]?.start ? : error(
311+ " could not find target sync line with value ${targetSpec.value} " +
312+ " in field ${targetSpec.field} in merged file" )
259313 }
314+
315+ if (targetTime != null ) {
316+ val sourceSpec = spec.syncSourceLine.get()
317+ val sourceLine = ass.events.lines.find {
318+ getMaybeComment(it, sourceSpec.field) == sourceSpec.value
319+ } ? : error(" could not find source sync line with value ${sourceSpec.value} " +
320+ " in field ${sourceSpec.field} in file ${ass.file?.name} " )
321+ val sourceTime = sourceLine.start
322+
323+ val delta = targetTime - sourceTime
324+ ass.events.lines.forEach {
325+ it.start + = delta
326+ it.end + = delta
260327 }
261- } }
328+ }
262329 }
263330
264331 val conflictingInfoSet = conflictingScriptInfo.get().toSet()
265- val outAss = files.foldIndexed(ASSFile ()) { i, acc, assFile ->
332+ val outAss = files.map { (ass, _) -> ass }. foldIndexed(ASSFile ()) { i, acc, assFile ->
266333 val existingStyles = acc.styles.lines.associate {
267334 it.name to acc.styles.serializeLine(it)
268335 }
0 commit comments